Merge branch 'master' into TSP

Patrick Branch [2016-07-30 10:54:31]
Merge branch 'master' into TSP
Filename
PokemonGo/RocketAPI/Client.cs
PokemonGo/RocketAPI/Exceptions/LoginFailedException.cs
PokemonGo/RocketAPI/GPSOAuthSharp.cs
PokemonGo/RocketAPI/Helpers/JsonHelper.cs
PokemonGo/RocketAPI/Login/GoogleLogin.cs
PokemonGo/RocketAPI/Login/ILoginType.cs
PokemonGo/RocketAPI/Login/PtcLogin.cs
PokemonGo/RocketAPI/PokemonGo.RocketAPI.csproj
PokemonGo/RocketAPI/Window/MainForm.Designer.cs
PokemonGo/RocketAPI/Window/MainForm.cs
PokemonGo/RocketAPI/Window/PokeUi.cs
PokemonGo/RocketAPI/Window/PokemonForm.cs
PokemonGo/RocketAPI/Window/Properties/AssemblyInfo.cs
README.md
diff --git a/PokemonGo/RocketAPI/Client.cs b/PokemonGo/RocketAPI/Client.cs
index 00482da..ad87136 100644
--- a/PokemonGo/RocketAPI/Client.cs
+++ b/PokemonGo/RocketAPI/Client.cs
@@ -20,7 +20,7 @@ using System.Threading;
 using PokemonGo.RocketAPI.Exceptions;
 using System.Text;
 using System.IO;
-using DankMemes.GPSOAuthSharp;
+using Newtonsoft.Json;

 #endregion

@@ -32,13 +32,14 @@ namespace PokemonGo.RocketAPI
         private ISettings _settings;
         private string _accessToken;
         private string _apiUrl;
-        private AuthType _authType = AuthType.Google;

         private double _currentLat;
         private double _currentLng;
         private Request.Types.UnknownAuth _unknownAuth;
         public static string AccessToken { get; set; } = string.Empty;

+        private readonly ILoginType login;
+
         public Client(ISettings settings)
         {
             _settings = settings;
@@ -58,6 +59,20 @@ namespace PokemonGo.RocketAPI
             _httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Accept", "*/*");
             _httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type",
                 "application/x-www-form-urlencoded");
+
+            login = CreateLoginType(settings);
+        }
+
+        static ILoginType CreateLoginType(ISettings settings)
+        {
+            switch (settings.AuthType)
+            {
+                case AuthType.Google:
+                    return new GoogleLogin(settings.Email, settings.Password);
+                case AuthType.Ptc:
+                    return new PtcLogin(settings.PtcUsername, settings.PtcPassword);
+            }
+            throw new ArgumentOutOfRangeException(nameof(settings.AuthType), "unknown auth type");
         }

         public async Task<CatchPokemonResponse> CatchPokemon(ulong encounterId, string spawnPointGuid, double pokemonLat,
@@ -86,36 +101,27 @@ namespace PokemonGo.RocketAPI
                         catchPokemonRequest);
         }

-        public async Task DoGoogleLogin(string email, string password)
+        public async Task Login()
         {
-            _authType = AuthType.Google;
-            GPSOAuthClient _GPSOclient = new GPSOAuthClient(email, password);
-            Dictionary<string, string> _GPSOresponse = _GPSOclient.PerformMasterLogin();
-            /* string json = JsonConvert.SerializeObject(_GPSOresponse, Formatting.Indented);
-               Console.WriteLine(json); */
-            if (_GPSOresponse.ContainsKey("Token"))
+            string errorMessage;
+            do
             {
-                string token = _GPSOresponse["Token"];
-                Dictionary<string, string> oauthResponse = _GPSOclient.PerformOAuth(
-                token,
-                "audience:server:client_id:848232511240-7so421jotr2609rmqakceuu1luuq0ptb.apps.googleusercontent.com",
-                "com.nianticlabs.pokemongo",
-                "321187995bc7cdc2b5fc91b11a96e2baa8602c62");
-                /* string oauthJson = JsonConvert.SerializeObject(oauthResponse, Formatting.Indented);
-                  Console.WriteLine(oauthJson); */
-                _accessToken = oauthResponse["Auth"];
-            }
-        }
+                errorMessage = null;
+
+                try
+                {
+                    _accessToken = await login.GetAccessToken().ConfigureAwait(false);
+                }
+                catch (LoginFailedException) { errorMessage = "Login failed - wrong username or password? - Restarting"; }
+                catch (PtcOfflineException) { errorMessage = "PTC login server is down - Restarting"; }
+                catch (JsonReaderException) { errorMessage = "Json Reader Exception - Server down? - Restarting"; }
+                catch (Exception ex) { errorMessage = ex.ToString() + "Exception - Please report - Restarting"; }
+
+                if(errorMessage != null)
+                    ColoredConsoleWrite(ConsoleColor.White, errorMessage);
+
+            } while (errorMessage != null);

-        public async Task DoPtcLogin(string username, string password)
-        {
-            try
-            {
-                _accessToken = await PtcLogin.GetAccessToken(username, password);
-                _authType = AuthType.Ptc;
-            }
-            catch (Newtonsoft.Json.JsonReaderException) { ColoredConsoleWrite(ConsoleColor.White, "Json Reader Exception - Server down? - Restarting"); DoPtcLogin(username, password); }
-            catch (Exception ex) { ColoredConsoleWrite(ConsoleColor.White, ex.ToString() + "Exception - Please report - Restarting"); DoPtcLogin(username, password); }
         }

         public async Task<EncounterResponse> EncounterPokemon(ulong encounterId, string spawnPointGuid)
@@ -326,7 +332,7 @@ namespace PokemonGo.RocketAPI

         public async Task<GetPlayerResponse> GetProfile()
         {
-            var profileRequest = RequestBuilder.GetInitialRequest(_accessToken, _authType, _currentLat, _currentLng, 10,
+            var profileRequest = RequestBuilder.GetInitialRequest(_accessToken, _settings.AuthType, _currentLat, _currentLng, 10,
                 new Request.Types.Requests { Type = (int)RequestType.GET_PLAYER });
             return
                 await _httpClient.PostProtoPayload<Request, GetPlayerResponse>($"https://{_apiUrl}/rpc", profileRequest);
@@ -383,7 +389,7 @@ namespace PokemonGo.RocketAPI

         public async Task SetServer()
         {
-            var serverRequest = RequestBuilder.GetInitialRequest(_accessToken, _authType, _currentLat, _currentLng, 10,
+            var serverRequest = RequestBuilder.GetInitialRequest(_accessToken, _settings.AuthType, _currentLat, _currentLng, 10,
                 RequestType.GET_PLAYER, RequestType.GET_HATCHED_OBJECTS, RequestType.GET_INVENTORY,
                 RequestType.CHECK_AWARDED_BADGES, RequestType.DOWNLOAD_SETTINGS);
             var serverResponse = await _httpClient.PostProto(Resources.RpcUrl, serverRequest);
diff --git a/PokemonGo/RocketAPI/Exceptions/LoginFailedException.cs b/PokemonGo/RocketAPI/Exceptions/LoginFailedException.cs
new file mode 100644
index 0000000..37bd893
--- /dev/null
+++ b/PokemonGo/RocketAPI/Exceptions/LoginFailedException.cs
@@ -0,0 +1,8 @@
+using System;
+
+namespace PokemonGo.RocketAPI.Exceptions
+{
+    public class LoginFailedException : Exception
+    {
+    }
+}
\ No newline at end of file
diff --git a/PokemonGo/RocketAPI/GPSOAuthSharp.cs b/PokemonGo/RocketAPI/GPSOAuthSharp.cs
deleted file mode 100644
index 9415c32..0000000
--- a/PokemonGo/RocketAPI/GPSOAuthSharp.cs
+++ /dev/null
@@ -1,177 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.Specialized;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Security.Cryptography;
-using System.Text;
-
-namespace DankMemes.GPSOAuthSharp
-{
-    // gpsoauth:__init__.py
-    // URL: https://github.com/simon-weber/gpsoauth/blob/master/gpsoauth/__init__.py
-    public class GPSOAuthClient
-    {
-        static string b64Key = "AAAAgMom/1a/v0lblO2Ubrt60J2gcuXSljGFQXgcyZWveWLEwo6prwgi3" +
-            "iJIZdodyhKZQrNWp5nKJ3srRXcUW+F1BD3baEVGcmEgqaLZUNBjm057pK" +
-            "RI16kB0YppeGx5qIQ5QjKzsR8ETQbKLNWgRY0QRNVz34kMJR3P/LgHax/" +
-            "6rmf5AAAAAwEAAQ==";
-        static RSAParameters androidKey = GoogleKeyUtils.KeyFromB64(b64Key);
-
-        static string version = "0.0.5";
-        static string authUrl = "https://android.clients.google.com/auth";
-        static string userAgent = "GPSOAuthSharp/" + version;
-
-        private string email;
-        private string password;
-
-        public GPSOAuthClient(string email, string password)
-        {
-            this.email = email;
-            this.password = password;
-        }
-
-        // _perform_auth_request
-        private Dictionary<string, string> PerformAuthRequest(Dictionary<string, string> data)
-        {
-            NameValueCollection nvc = new NameValueCollection();
-            foreach (var kvp in data)
-            {
-                nvc.Add(kvp.Key.ToString(), kvp.Value.ToString());
-            }
-            using (WebClient client = new WebClient())
-            {
-                client.Headers.Add(HttpRequestHeader.UserAgent, userAgent);
-                string result;
-                try
-                {
-                    byte[] response = client.UploadValues(authUrl, nvc);
-                    result = Encoding.UTF8.GetString(response);
-                }
-                catch (WebException e)
-                {
-                    result = new StreamReader(e.Response.GetResponseStream()).ReadToEnd();
-                }
-                return GoogleKeyUtils.ParseAuthResponse(result);
-            }
-        }
-
-        // perform_master_login
-        public Dictionary<string, string> PerformMasterLogin(string service = "ac2dm",
-            string deviceCountry = "us", string operatorCountry = "us", string lang = "en", int sdkVersion = 21)
-        {
-            string signature = GoogleKeyUtils.CreateSignature(email, password, androidKey);
-            var dict = new Dictionary<string, string> {
-                { "accountType", "HOSTED_OR_GOOGLE" },
-                { "Email", email },
-                { "has_permission", 1.ToString() },
-                { "add_account", 1.ToString() },
-                { "EncryptedPasswd",  signature},
-                { "service", service },
-                { "source", "android" },
-                { "device_country", deviceCountry },
-                { "operatorCountry", operatorCountry },
-                { "lang", lang },
-                { "sdk_version", sdkVersion.ToString() }
-            };
-            return PerformAuthRequest(dict);
-        }
-
-        // perform_oauth
-        public Dictionary<string, string> PerformOAuth(string masterToken, string service, string app, string clientSig,
-            string deviceCountry = "us", string operatorCountry = "us", string lang = "en", int sdkVersion = 21)
-        {
-            var dict = new Dictionary<string, string> {
-                { "accountType", "HOSTED_OR_GOOGLE" },
-                { "Email", email },
-                { "has_permission", 1.ToString() },
-                { "EncryptedPasswd",  masterToken},
-                { "service", service },
-                { "source", "android" },
-                { "app", app },
-                { "client_sig", clientSig },
-                { "device_country", deviceCountry },
-                { "operatorCountry", operatorCountry },
-                { "lang", lang },
-                { "sdk_version", sdkVersion.ToString() }
-            };
-            return PerformAuthRequest(dict);
-        }
-    }
-
-    // gpsoauth:google.py
-    // URL: https://github.com/simon-weber/gpsoauth/blob/master/gpsoauth/google.py
-    class GoogleKeyUtils
-    {
-        // key_from_b64
-        // BitConverter has different endianness, hence the Reverse()
-        public static RSAParameters KeyFromB64(string b64Key)
-        {
-            byte[] decoded = Convert.FromBase64String(b64Key);
-            int modLength = BitConverter.ToInt32(decoded.Take(4).Reverse().ToArray(), 0);
-            byte[] mod = decoded.Skip(4).Take(modLength).ToArray();
-            int expLength = BitConverter.ToInt32(decoded.Skip(modLength + 4).Take(4).Reverse().ToArray(), 0);
-            byte[] exponent = decoded.Skip(modLength + 8).Take(expLength).ToArray();
-            RSAParameters rsaKeyInfo = new RSAParameters();
-            rsaKeyInfo.Modulus = mod;
-            rsaKeyInfo.Exponent = exponent;
-            return rsaKeyInfo;
-        }
-
-        // key_to_struct
-        // Python version returns a string, but we use byte[] to get the same results
-        public static byte[] KeyToStruct(RSAParameters key)
-        {
-            byte[] modLength = { 0x00, 0x00, 0x00, 0x80 };
-            byte[] mod = key.Modulus;
-            byte[] expLength = { 0x00, 0x00, 0x00, 0x03 };
-            byte[] exponent = key.Exponent;
-            return DataTypeUtils.CombineBytes(modLength, mod, expLength, exponent);
-        }
-
-        // parse_auth_response
-        public static Dictionary<string, string> ParseAuthResponse(string text)
-        {
-            Dictionary<string, string> responseData = new Dictionary<string, string>();
-            foreach (string line in text.Split(new string[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries))
-            {
-                string[] parts = line.Split('=');
-                responseData.Add(parts[0], parts[1]);
-            }
-            return responseData;
-        }
-
-        // signature
-        public static string CreateSignature(string email, string password, RSAParameters key)
-        {
-            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
-            rsa.ImportParameters(key);
-            SHA1 sha1 = SHA1.Create();
-            byte[] prefix = { 0x00 };
-            byte[] hash = sha1.ComputeHash(GoogleKeyUtils.KeyToStruct(key)).Take(4).ToArray();
-            byte[] encrypted = rsa.Encrypt(Encoding.UTF8.GetBytes(email + "\x00" + password), true);
-            return DataTypeUtils.UrlSafeBase64(DataTypeUtils.CombineBytes(prefix, hash, encrypted));
-        }
-    }
-
-    class DataTypeUtils
-    {
-        public static string UrlSafeBase64(byte[] byteArray)
-        {
-            return Convert.ToBase64String(byteArray).Replace('+', '-').Replace('/', '_');
-        }
-
-        public static byte[] CombineBytes(params byte[][] arrays)
-        {
-            byte[] rv = new byte[arrays.Sum(a => a.Length)];
-            int offset = 0;
-            foreach (byte[] array in arrays)
-            {
-                Buffer.BlockCopy(array, 0, rv, offset, array.Length);
-                offset += array.Length;
-            }
-            return rv;
-        }
-    }
-}
diff --git a/PokemonGo/RocketAPI/Helpers/JsonHelper.cs b/PokemonGo/RocketAPI/Helpers/JsonHelper.cs
deleted file mode 100644
index 2dd2fcd..0000000
--- a/PokemonGo/RocketAPI/Helpers/JsonHelper.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-#region
-
-using Newtonsoft.Json.Linq;
-
-#endregion
-
-namespace PokemonGo.RocketAPI.Helpers
-{
-    public class JsonHelper
-    {
-        public static string GetValue(string json, string key)
-        {
-            var jObject = JObject.Parse(json);
-            return jObject[key].ToString();
-        }
-    }
-}
\ No newline at end of file
diff --git a/PokemonGo/RocketAPI/Login/GoogleLogin.cs b/PokemonGo/RocketAPI/Login/GoogleLogin.cs
index c91f726..10c9c70 100644
--- a/PokemonGo/RocketAPI/Login/GoogleLogin.cs
+++ b/PokemonGo/RocketAPI/Login/GoogleLogin.cs
@@ -1,115 +1,180 @@
-#region
-
+using PokemonGo.RocketAPI.Exceptions;
 using System;
 using System.Collections.Generic;
-using System.Diagnostics;
-using System.Threading;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Security.Cryptography;
+using System.Text;
 using System.Threading.Tasks;
-using System.Windows.Forms;
-using PokemonGo.RocketAPI.Helpers;
-using System.IO;
-
-
-#endregion

 namespace PokemonGo.RocketAPI.Login
 {
-    public static class GoogleLogin
+    public class GoogleLogin : ILoginType
     {
-        private const string OauthTokenEndpoint = "https://www.googleapis.com/oauth2/v4/token";
-        private const string OauthEndpoint = "https://accounts.google.com/o/oauth2/device/code";
-        private const string ClientId = "848232511240-73ri3t7plvk96pj4f85uj8otdat2alem.apps.googleusercontent.com";
-        private const string ClientSecret = "NCjF1TLi2CcY6t5mt0ZveuL7";
-
-        /// <summary>
-        ///     Gets the access token from Google
-        /// </summary>
-        /// <param name="deviceCode"></param>
-        /// <returns>tokenResponse</returns>
-        public static async Task<TokenResponseModel> GetAccessToken(DeviceCodeModel deviceCode)
-        {
-            //Poll until user submitted code..
-            TokenResponseModel tokenResponse;
-            do
-            {
-                await Task.Delay(2000);
-                tokenResponse = await PollSubmittedToken(deviceCode.device_code);
-            } while (tokenResponse.access_token == null || tokenResponse.refresh_token == null);
+        private readonly string password;
+        private readonly string email;

-            return tokenResponse;
+        public GoogleLogin(string email, string password)
+        {
+            this.email = email;
+            this.password = password;
         }

-        public static async Task<TokenResponseModel> GetAccessToken(string refreshToken)
+        public async Task<string> GetAccessToken()
         {
-            return await HttpClientHelper.PostFormEncodedAsync<TokenResponseModel>(OauthTokenEndpoint,
-                new KeyValuePair<string, string>("access_type", "offline"),
-                new KeyValuePair<string, string>("client_id", ClientId),
-                new KeyValuePair<string, string>("client_secret", ClientSecret),
-                new KeyValuePair<string, string>("refresh_token", refreshToken),
-                new KeyValuePair<string, string>("grant_type", "refresh_token"),
-                new KeyValuePair<string, string>("scope", "openid email https://www.googleapis.com/auth/userinfo.email"));
+            var _GPSOresponse = await PerformMasterLoginAsync(email, password).ConfigureAwait(false);
+            string token;
+            if (!_GPSOresponse.TryGetValue("Token", out token))
+                throw new LoginFailedException();
+
+            var oauthResponse = await PerformOAuthAsync(
+            token,
+            "audience:server:client_id:848232511240-7so421jotr2609rmqakceuu1luuq0ptb.apps.googleusercontent.com",
+            "com.nianticlabs.pokemongo",
+            "321187995bc7cdc2b5fc91b11a96e2baa8602c62",
+            email).ConfigureAwait(false);
+            return oauthResponse["Auth"];
         }

-        public static async Task<DeviceCodeModel> GetDeviceCode()
-        {
-            var deviceCode = await HttpClientHelper.PostFormEncodedAsync<DeviceCodeModel>(OauthEndpoint,
-            new KeyValuePair<string, string>("client_id", ClientId),
-            new KeyValuePair<string, string>("scope", "openid email https://www.googleapis.com/auth/userinfo.email"));
+        private static string b64Key = "AAAAgMom/1a/v0lblO2Ubrt60J2gcuXSljGFQXgcyZWveWLEwo6prwgi3" +
+            "iJIZdodyhKZQrNWp5nKJ3srRXcUW+F1BD3baEVGcmEgqaLZUNBjm057pK" +
+            "RI16kB0YppeGx5qIQ5QjKzsR8ETQbKLNWgRY0QRNVz34kMJR3P/LgHax/" +
+            "6rmf5AAAAAwEAAQ==";
+
+        private static RSAParameters androidKey = GoogleKeyUtils.KeyFromB64(b64Key);

-            try
+        private static string version = "0.0.5";
+        private static string authUrl = "https://android.clients.google.com/auth";
+        private static string userAgent = "GPSOAuthSharp/" + version;
+
+        private async static Task<IDictionary<string, string>> PerformAuthRequestAsync(IDictionary<string, string> data)
+        {
+            var handler = new HttpClientHandler
             {
-                //ColoredConsoleWrite("Google Device Code copied to clipboard");
-                System.Console.WriteLine($"Goto: http://www.google.com/device & enter {deviceCode.user_code}");
-                File.AppendAllText(AppDomain.CurrentDomain.BaseDirectory + @"\Logs.txt", "[" + DateTime.Now.ToString("HH:mm:ss tt") + $"] Goto: http://www.google.com/device & enter {deviceCode.user_code}");
-                Process.Start(@"http://www.google.com/device");
-                var thread = new Thread(() => Clipboard.SetText(deviceCode.user_code)); //Copy device code
-                thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA
-                thread.Start();
-                thread.Join();
-            }
-            catch (Exception)
+                AutomaticDecompression = DecompressionMethods.GZip,
+                AllowAutoRedirect = false
+            };
+            using (var tempHttpClient = new HttpClient(handler))
             {
-                //System.Console.WriteLine("Couldnt copy to clipboard, do it manually");
-                //System.Console.WriteLine($"Goto: http://www.google.com/device & enter {deviceCode.user_code}");
+                tempHttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(userAgent);
+
+                HttpResponseMessage response;
+                using (var formUrlEncodedContent = new FormUrlEncodedContent(data))
+                {
+                    response = await tempHttpClient.PostAsync(authUrl, formUrlEncodedContent).ConfigureAwait(false);
+                }
+
+                var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+                return GoogleKeyUtils.ParseAuthResponse(content);
             }
+        }

-            return deviceCode;
+        private static IDictionary<string, string> GenerateBaseRequest(string email, string encryptedPassword, string service)
+            => new Dictionary<string, string> {
+                { "accountType", "HOSTED_OR_GOOGLE" },
+                { "Email", email },
+                { "has_permission", "1" },
+                { "EncryptedPasswd",  encryptedPassword},
+                { "service", service },
+                { "source", "android" },
+                { "device_country", "us" },
+                { "operatorCountry", "us" },
+                { "lang", "en" },
+                { "sdk_version", "21" }
+            };
+
+        private static Task<IDictionary<string, string>> PerformMasterLoginAsync(string email, string password)
+        {
+            var signature = GoogleKeyUtils.CreateSignature(email, password, androidKey);
+            var request = GenerateBaseRequest(email, signature, "ac2dm");
+            request.Add("add_account", "1");
+            return PerformAuthRequestAsync(request);
         }

-        private static async Task<TokenResponseModel> PollSubmittedToken(string deviceCode)
+        private static Task<IDictionary<string, string>> PerformOAuthAsync(string masterToken, string service, string app, string clientSig, string email)
         {
-            return await HttpClientHelper.PostFormEncodedAsync<TokenResponseModel>(OauthTokenEndpoint,
-                new KeyValuePair<string, string>("client_id", ClientId),
-                new KeyValuePair<string, string>("client_secret", ClientSecret),
-                new KeyValuePair<string, string>("code", deviceCode),
-                new KeyValuePair<string, string>("grant_type", "http://oauth.net/grant_type/device/1.0"),
-                new KeyValuePair<string, string>("scope", "openid email https://www.googleapis.com/auth/userinfo.email"));
+            var request = GenerateBaseRequest(email, masterToken, service);
+            request.Add("app", app);
+            request.Add("client_sig", clientSig);
+            return PerformAuthRequestAsync(request);
         }
+    }

+    internal class GoogleKeyUtils
+    {
+        // key_from_b64
+        // BitConverter has different endianness, hence the Reverse()
+        public static RSAParameters KeyFromB64(string b64Key)
+        {
+            var decoded = Convert.FromBase64String(b64Key);
+            var modLength = BitConverter.ToInt32(decoded.Take(4).Reverse().ToArray(), 0);
+            var mod = decoded.Skip(4).Take(modLength).ToArray();
+            var expLength = BitConverter.ToInt32(decoded.Skip(modLength + 4).Take(4).Reverse().ToArray(), 0);
+            var exponent = decoded.Skip(modLength + 8).Take(expLength).ToArray();
+            var rsaKeyInfo = new RSAParameters
+            {
+                Modulus = mod,
+                Exponent = exponent
+            };
+            return rsaKeyInfo;
+        }

-        internal class ErrorResponseModel
+        // key_to_struct
+        // Python version returns a string, but we use byte[] to get the same results
+        public static byte[] KeyToStruct(RSAParameters key)
         {
-            public string error { get; set; }
-            public string error_description { get; set; }
+            byte[] modLength = { 0x00, 0x00, 0x00, 0x80 };
+            var mod = key.Modulus;
+            byte[] expLength = { 0x00, 0x00, 0x00, 0x03 };
+            var exponent = key.Exponent;
+            return DataTypeUtils.CombineBytes(modLength, mod, expLength, exponent);
         }

-        public class TokenResponseModel
+        // parse_auth_response
+        public static Dictionary<string, string> ParseAuthResponse(string text)
         {
-            public string access_token { get; set; }
-            public string token_type { get; set; }
-            public int expires_in { get; set; }
-            public string refresh_token { get; set; }
-            public string id_token { get; set; }
+            var responseData = new Dictionary<string, string>();
+            foreach (string line in text.Split(new string[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries))
+            {
+                var parts = line.Split('=');
+                responseData.Add(parts[0], parts[1]);
+            }
+            return responseData;
         }

+        // signature
+        public static string CreateSignature(string email, string password, RSAParameters key)
+        {
+            using (var rsa = new RSACryptoServiceProvider())
+            {
+                rsa.ImportParameters(key);
+                var sha1 = SHA1.Create();
+                byte[] prefix = { 0x00 };
+                var hash = sha1.ComputeHash(GoogleKeyUtils.KeyToStruct(key)).Take(4).ToArray();
+                var encrypted = rsa.Encrypt(Encoding.UTF8.GetBytes(email + "\x00" + password), true);
+                return DataTypeUtils.UrlSafeBase64(DataTypeUtils.CombineBytes(prefix, hash, encrypted));
+            }
+        }

-        public class DeviceCodeModel
+        private class DataTypeUtils
         {
-            public string verification_url { get; set; }
-            public int expires_in { get; set; }
-            public int interval { get; set; }
-            public string device_code { get; set; }
-            public string user_code { get; set; }
+            public static string UrlSafeBase64(byte[] byteArray)
+            {
+                return Convert.ToBase64String(byteArray).Replace('+', '-').Replace('/', '_');
+            }
+
+            public static byte[] CombineBytes(params byte[][] arrays)
+            {
+                var rv = new byte[arrays.Sum(a => a.Length)];
+                var offset = 0;
+                foreach (byte[] array in arrays)
+                {
+                    Buffer.BlockCopy(array, 0, rv, offset, array.Length);
+                    offset += array.Length;
+                }
+                return rv;
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/PokemonGo/RocketAPI/Login/ILoginType.cs b/PokemonGo/RocketAPI/Login/ILoginType.cs
new file mode 100644
index 0000000..0c102a0
--- /dev/null
+++ b/PokemonGo/RocketAPI/Login/ILoginType.cs
@@ -0,0 +1,16 @@
+using System.Threading.Tasks;
+
+namespace PokemonGo.RocketAPI.Login
+{
+    /// <summary>
+    /// Interface for the login into the game using either Google or PTC
+    /// </summary>
+    interface ILoginType
+    {
+        /// <summary>
+        /// Gets the access token.
+        /// </summary>
+        /// <returns></returns>
+        Task<string> GetAccessToken();
+    }
+}
\ No newline at end of file
diff --git a/PokemonGo/RocketAPI/Login/PtcLogin.cs b/PokemonGo/RocketAPI/Login/PtcLogin.cs
index 7a3c900..0cf6b04 100644
--- a/PokemonGo/RocketAPI/Login/PtcLogin.cs
+++ b/PokemonGo/RocketAPI/Login/PtcLogin.cs
@@ -1,20 +1,24 @@
-#region
-
+using Newtonsoft.Json;
+using PokemonGo.RocketAPI.Exceptions;
 using System.Collections.Generic;
 using System.Net;
 using System.Net.Http;
 using System.Threading.Tasks;
 using System.Web;
-using PokemonGo.RocketAPI.Exceptions;
-using PokemonGo.RocketAPI.Helpers;
-
-#endregion

 namespace PokemonGo.RocketAPI.Login
 {
-    internal static class PtcLogin
+    class PtcLogin : ILoginType
     {
-        public static async Task<string> GetAccessToken(string username, string password)
+        readonly string password;
+        readonly string username;
+
+        public PtcLogin(string username, string password)
+        {
+            this.username = username;
+            this.password = password;
+        }
+        public async Task<string> GetAccessToken()
         {
             var handler = new HttpClientHandler
             {
@@ -25,44 +29,88 @@ namespace PokemonGo.RocketAPI.Login
             using (var tempHttpClient = new HttpClient(handler))
             {
                 //Get session cookie
-                var sessionResp = await tempHttpClient.GetAsync(Resources.PtcLoginUrl);
-                var data = await sessionResp.Content.ReadAsStringAsync();
-                var lt = JsonHelper.GetValue(data, "lt");
-                var executionId = JsonHelper.GetValue(data, "execution");
+                var sessionData = await GetSessionCookie(tempHttpClient).ConfigureAwait(false);

                 //Login
-                var loginResp = await tempHttpClient.PostAsync(Resources.PtcLoginUrl,
-                    new FormUrlEncodedContent(
-                        new[]
-                        {
-                            new KeyValuePair<string, string>("lt", lt),
-                            new KeyValuePair<string, string>("execution", executionId),
-                            new KeyValuePair<string, string>("_eventId", "submit"),
-                            new KeyValuePair<string, string>("username", username),
-                            new KeyValuePair<string, string>("password", password)
-                        }));
-
-                var ticketId = HttpUtility.ParseQueryString(loginResp.Headers.Location.Query)["ticket"];
-                if (ticketId == null)
-                    throw new PtcOfflineException();
-
-                //Get tokenvar
-                var tokenResp = await tempHttpClient.PostAsync(Resources.PtcLoginOauth,
-                    new FormUrlEncodedContent(
-                        new[]
-                        {
-                            new KeyValuePair<string, string>("client_id", "mobile-app_pokemon-go"),
-                            new KeyValuePair<string, string>("redirect_uri",
-                                "https://www.nianticlabs.com/pokemongo/error"),
-                            new KeyValuePair<string, string>("client_secret",
-                                "w8ScCUXJQc6kXKw8FiOhd8Fixzht18Dq3PEVkUCP5ZPxtgyWsbTvWHFLm2wNY0JR"),
-                            new KeyValuePair<string, string>("grant_type", "refresh_token"),
-                            new KeyValuePair<string, string>("code", ticketId)
-                        }));
-
-                var tokenData = await tokenResp.Content.ReadAsStringAsync();
-                return HttpUtility.ParseQueryString(tokenData)["access_token"];
+                var ticketId = await GetLoginTicket(username, password, tempHttpClient, sessionData).ConfigureAwait(false);
+
+                //Get tokenvar
+                return await GetToken(tempHttpClient, ticketId).ConfigureAwait(false);
+            }
+        }
+
+        private static string ExtracktTicketFromResponse(HttpResponseMessage loginResp)
+        {
+            var location = loginResp.Headers.Location;
+            if (location == null)
+                throw new LoginFailedException();
+
+            var ticketId = HttpUtility.ParseQueryString(location.Query)["ticket"];
+
+            if (ticketId == null)
+                throw new PtcOfflineException();
+
+            return ticketId;
+        }
+
+        private static IDictionary<string, string> GenerateLoginRequest(SessionData sessionData, string user, string pass)
+            => new Dictionary<string, string>
+            {
+                { "lt", sessionData.Lt },
+                { "execution", sessionData.Execution },
+                { "_eventId", "submit" },
+                { "username", user },
+                { "password", pass }
+            };
+
+        private static IDictionary<string, string> GenerateTokenVarRequest(string ticketId)
+            => new Dictionary<string, string>
+            {
+                {"client_id", "mobile-app_pokemon-go"},
+                {"redirect_uri", "https://www.nianticlabs.com/pokemongo/error"},
+                {"client_secret", "w8ScCUXJQc6kXKw8FiOhd8Fixzht18Dq3PEVkUCP5ZPxtgyWsbTvWHFLm2wNY0JR"},
+                {"grant_type", "refresh_token"},
+                {"code", ticketId}
+            };
+
+        private static async Task<string> GetLoginTicket(string username, string password, HttpClient tempHttpClient, SessionData sessionData)
+        {
+            HttpResponseMessage loginResp;
+            var loginRequest = GenerateLoginRequest(sessionData, username, password);
+            using (var formUrlEncodedContent = new FormUrlEncodedContent(loginRequest))
+            {
+                loginResp = await tempHttpClient.PostAsync(Resources.PtcLoginUrl, formUrlEncodedContent).ConfigureAwait(false);
             }
+
+            var ticketId = ExtracktTicketFromResponse(loginResp);
+            return ticketId;
+        }
+
+        private static async Task<SessionData> GetSessionCookie(HttpClient tempHttpClient)
+        {
+            var sessionResp = await tempHttpClient.GetAsync(Resources.PtcLoginUrl).ConfigureAwait(false);
+            var data = await sessionResp.Content.ReadAsStringAsync().ConfigureAwait(false);
+            var sessionData = JsonConvert.DeserializeObject<SessionData>(data);
+            return sessionData;
+        }
+
+        private static async Task<string> GetToken(HttpClient tempHttpClient, string ticketId)
+        {
+            HttpResponseMessage tokenResp;
+            var tokenRequest = GenerateTokenVarRequest(ticketId);
+            using (var formUrlEncodedContent = new FormUrlEncodedContent(tokenRequest))
+            {
+                tokenResp = await tempHttpClient.PostAsync(Resources.PtcLoginOauth, formUrlEncodedContent).ConfigureAwait(false);
+            }
+
+            var tokenData = await tokenResp.Content.ReadAsStringAsync().ConfigureAwait(false);
+            return HttpUtility.ParseQueryString(tokenData)["access_token"];
+        }
+
+        private class SessionData
+        {
+            public string Lt { get; set; }
+            public string Execution { get; set; }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/PokemonGo/RocketAPI/PokemonGo.RocketAPI.csproj b/PokemonGo/RocketAPI/PokemonGo.RocketAPI.csproj
index a7c2734..4c2dd5f 100644
--- a/PokemonGo/RocketAPI/PokemonGo.RocketAPI.csproj
+++ b/PokemonGo/RocketAPI/PokemonGo.RocketAPI.csproj
@@ -72,21 +72,21 @@
     <Compile Include="Enums\AuthType.cs" />
     <Compile Include="Enums\MiscEnums.cs" />
     <Compile Include="Enums\RequestType.cs" />
+    <Compile Include="Exceptions\LoginFailedException.cs" />
     <Compile Include="Exceptions\PtcOfflineException.cs" />
     <Compile Include="Extensions\DateTimeExtensions.cs" />
     <Compile Include="GeneratedCode\AllEnum.cs" />
     <Compile Include="GeneratedCode\Payloads.cs" />
     <Compile Include="GeneratedCode\Request.cs" />
     <Compile Include="GeneratedCode\Response.cs" />
-    <Compile Include="GPSOAuthSharp.cs" />
     <Compile Include="Helpers\HttpClientHelper.cs" />
-    <Compile Include="Helpers\JsonHelper.cs" />
     <Compile Include="Helpers\ProtoHelper.cs" />
     <Compile Include="Helpers\RetryHandler.cs" />
     <Compile Include="Helpers\S2Helper.cs" />
     <Compile Include="Helpers\Utils.cs" />
     <Compile Include="ISettings.cs" />
     <Compile Include="Login\GoogleLogin.cs" />
+    <Compile Include="Login\ILoginType.cs" />
     <Compile Include="Login\PtcLogin.cs" />
     <None Include="app.config" />
     <Compile Include="Client.cs" />
diff --git a/PokemonGo/RocketAPI/Window/MainForm.Designer.cs b/PokemonGo/RocketAPI/Window/MainForm.Designer.cs
index a41b76d..2c3859c 100644
--- a/PokemonGo/RocketAPI/Window/MainForm.Designer.cs
+++ b/PokemonGo/RocketAPI/Window/MainForm.Designer.cs
@@ -33,7 +33,7 @@
             this.statusStrip1 = new System.Windows.Forms.StatusStrip();
             this.statusLabel = new System.Windows.Forms.ToolStripStatusLabel();
             this.menuStrip1 = new System.Windows.Forms.MenuStrip();
-            this.startBotToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+            this.startStopBotToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
             this.mapToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
             this.todoToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
             this.pokemonToolStripMenuItem2 = new System.Windows.Forms.ToolStripMenuItem();
@@ -79,7 +79,7 @@
             // menuStrip1
             //
             this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
-            this.startBotToolStripMenuItem,
+            this.startStopBotToolStripMenuItem,
             this.mapToolStripMenuItem,
             this.todoToolStripMenuItem,
             this.pokemonToolStripMenuItem2,
@@ -93,10 +93,10 @@
             //
             // startBotToolStripMenuItem
             //
-            this.startBotToolStripMenuItem.Name = "startBotToolStripMenuItem";
-            this.startBotToolStripMenuItem.Size = new System.Drawing.Size(64, 20);
-            this.startBotToolStripMenuItem.Text = "Start Bot";
-            this.startBotToolStripMenuItem.Click += new System.EventHandler(this.startBotToolStripMenuItem_Click);
+            this.startStopBotToolStripMenuItem.Name = "startStopBotToolStripMenuItem";
+            this.startStopBotToolStripMenuItem.Size = new System.Drawing.Size(64, 20);
+            this.startStopBotToolStripMenuItem.Text = "Start Bot";
+            this.startStopBotToolStripMenuItem.Click += new System.EventHandler(this.startStopBotToolStripMenuItem_Click);
             //
             // mapToolStripMenuItem
             //
@@ -173,7 +173,7 @@
         private System.Windows.Forms.MenuStrip menuStrip1;
         private System.Windows.Forms.ToolStripMenuItem todoToolStripMenuItem;
         private System.Windows.Forms.ToolStripStatusLabel statusLabel;
-        private System.Windows.Forms.ToolStripMenuItem startBotToolStripMenuItem;
+        private System.Windows.Forms.ToolStripMenuItem startStopBotToolStripMenuItem;
         private System.Windows.Forms.ToolStripMenuItem showAllToolStripMenuItem;
         private System.Windows.Forms.ToolStripMenuItem showAllToolStripMenuItem1;
         private System.Windows.Forms.ToolStripMenuItem pokemonToolStripMenuItem2;
diff --git a/PokemonGo/RocketAPI/Window/MainForm.cs b/PokemonGo/RocketAPI/Window/MainForm.cs
index a0e27f5..bef4091 100644
--- a/PokemonGo/RocketAPI/Window/MainForm.cs
+++ b/PokemonGo/RocketAPI/Window/MainForm.cs
@@ -38,6 +38,7 @@ namespace PokemonGo.RocketAPI.Window
         private static int Currentlevel = -1;
         private static int TotalExperience = 0;
         private static int TotalPokemon = 0;
+        private static bool Stopping = false;
         private static bool ForceUnbanning = false;
         private static bool FarmingStops = false;
         private static bool FarmingPokemons = false;
@@ -110,6 +111,17 @@ namespace PokemonGo.RocketAPI.Window
             }
         }

+        public void ConsoleClear()
+        {
+            if (InvokeRequired)
+            {
+                Invoke(new Action(ConsoleClear));
+                return;
+            }
+
+            logTextBox.Clear();
+        }
+
         public void SetStatusText(string text)
         {
             if (InvokeRequired)
@@ -192,15 +204,13 @@ namespace PokemonGo.RocketAPI.Window
                 {
                     case AuthType.Ptc:
                         ColoredConsoleWrite(Color.Green, "Login Type: Pokemon Trainers Club");
-                        await client.DoPtcLogin(ClientSettings.PtcUsername, ClientSettings.PtcPassword);
                         break;
                     case AuthType.Google:
                         ColoredConsoleWrite(Color.Green, "Login Type: Google");
-                        await client.DoGoogleLogin(ClientSettings.Email, ClientSettings.Password);
-
                         break;
                 }

+                await client.Login();
                 await client.SetServer();
                 var profile = await client.GetProfile();
                 var settings = await client.GetSettings();
@@ -284,22 +294,30 @@ namespace PokemonGo.RocketAPI.Window
                     await Task.Delay(25);

                 // await ForceUnban(client);
-                ColoredConsoleWrite(Color.Red, $"No nearby useful locations found. Please wait 10 seconds.");
-                await Task.Delay(10000);
-                CheckVersion();
-                Execute();
+                if (!Stopping)
+                {
+                    ColoredConsoleWrite(Color.Red, $"No nearby useful locations found. Please wait 10 seconds.");
+                    await Task.Delay(10000);
+                    CheckVersion();
+                    Execute();
+                }
+                else
+                {
+                    confirmBotStopped();
+                }
             }
-            catch (TaskCanceledException) { ColoredConsoleWrite(Color.Red, "Task Canceled Exception - Restarting"); Execute(); }
-            catch (UriFormatException) { ColoredConsoleWrite(Color.Red, "System URI Format Exception - Restarting"); Execute(); }
-            catch (ArgumentOutOfRangeException) { ColoredConsoleWrite(Color.Red, "ArgumentOutOfRangeException - Restarting"); Execute(); }
-            catch (ArgumentNullException) { ColoredConsoleWrite(Color.Red, "Argument Null Refference - Restarting"); Execute(); }
-            catch (NullReferenceException) { ColoredConsoleWrite(Color.Red, "Null Refference - Restarting"); Execute(); }
-            catch (Exception ex) { ColoredConsoleWrite(Color.Red, ex.ToString()); Execute(); }
+
+            catch (TaskCanceledException) { ColoredConsoleWrite(Color.Red, "Task Canceled Exception - Restarting"); if (!Stopping) { Execute(); } else { confirmBotStopped(); } }
+            catch (UriFormatException) { ColoredConsoleWrite(Color.Red, "System URI Format Exception - Restarting"); if (!Stopping) { Execute(); } else { confirmBotStopped(); } }
+            catch (ArgumentOutOfRangeException) { ColoredConsoleWrite(Color.Red, "ArgumentOutOfRangeException - Restarting"); if (!Stopping) { Execute(); } else { confirmBotStopped(); } }
+            catch (ArgumentNullException) { ColoredConsoleWrite(Color.Red, "Argument Null Refference - Restarting"); if (!Stopping) { Execute(); } else { confirmBotStopped(); } }
+            catch (NullReferenceException) { ColoredConsoleWrite(Color.Red, "Null Refference - Restarting"); if (!Stopping) { Execute(); } else { confirmBotStopped(); } }
+            catch (Exception ex) { ColoredConsoleWrite(Color.Red, ex.ToString()); if (!Stopping) { Execute(); } else { confirmBotStopped(); } }
         }

         private static string CallAPI(string elem, double lat, double lon)
         {
-            using (XmlReader reader = XmlReader.Create(@"http://api.geonames.org/findNearby?lat=" + lat + "&lng=" + lon + "&username=demo"))
+            using (XmlReader reader = XmlReader.Create(@"http://api.geonames.org/findNearby?lat=" + lat + "&lng=" + lon + "&username=pokemongobot"))
             {
                 while (reader.Read())
                 {
@@ -329,6 +347,7 @@ namespace PokemonGo.RocketAPI.Window
             }
             return "Error";
         }
+
         private async Task ExecuteCatchAllNearbyPokemons(Client client)
         {
             var mapObjects = await client.GetMapObjects();
@@ -342,7 +361,7 @@ namespace PokemonGo.RocketAPI.Window

             foreach (var pokemon in pokemons)
             {
-                if (ForceUnbanning)
+                if (ForceUnbanning || Stopping)
                     break;

                 FarmingPokemons = true;
@@ -425,12 +444,12 @@ namespace PokemonGo.RocketAPI.Window
             }
             HashSet<FortData> pokeStopSet = new HashSet<FortData>(pokeStops);
             IEnumerable<FortData> nextPokeStopList = null;
-            if (!ForceUnbanning)
+            if (!ForceUnbanning && !Stopping)
                 ColoredConsoleWrite(Color.Cyan, $"Visiting {pokeStops.Count()} PokeStops");
             pokeStops = (IEnumerable<FortData>)TSP<ICoordinate>.getMinimumTour(pokeStops, (new CoordinateMetric()).distance);
             foreach (var pokeStop in pokeStops)
             {
-                if (ForceUnbanning)
+                if (ForceUnbanning || Stopping)
                     break;

                 FarmingStops = true;
@@ -487,7 +506,7 @@ namespace PokemonGo.RocketAPI.Window

         private async Task ForceUnban(Client client)
         {
-            if (!ForceUnbanning)
+            if (!ForceUnbanning && !Stopping)
             {
                 ColoredConsoleWrite(Color.LightGreen, "Waiting for last farming action to be complete...");
                 ForceUnbanning = true;
@@ -540,7 +559,7 @@ namespace PokemonGo.RocketAPI.Window
             }
             else
             {
-                ColoredConsoleWrite(Color.Red, "A force unban attempt is in action... Please wait.");
+                ColoredConsoleWrite(Color.Red, "A action is in play... Please wait.");
             }


@@ -836,11 +855,33 @@ namespace PokemonGo.RocketAPI.Window
             var inventory = await client.GetInventory();
             var stats = inventory.InventoryDelta.InventoryItems.Select(i => i.InventoryItemData?.PlayerStats).ToArray();
             var profile = await client.GetProfile();
+            Int16 hoursLeft = 0; Int16 minutesLeft = 0; Int32 secondsLeft = 0; double xpSec = 0;
             foreach (var v in stats)
                 if (v != null)
                 {
                     int XpDiff = GetXpDiff(client, v.Level);
-                    SetStatusText(string.Format(Username + " | Level: {0:0} - ({2:0} / {3:0}) | Runtime {1} | Stardust: {4:0}", v.Level, _getSessionRuntimeInTimeFormat(), (v.Experience - v.PrevLevelXp - XpDiff), (v.NextLevelXp - v.PrevLevelXp - XpDiff), profile.Profile.Currency.ToArray()[1].Amount) + " | XP/Hour: " + Math.Round(TotalExperience / GetRuntime()) + " | Pokemon/Hour: " + Math.Round(TotalPokemon / GetRuntime()));
+                    //Calculating the exp needed to level up
+                    Single expNextLvl = (v.NextLevelXp - v.Experience);
+                    //Calculating the exp made per second
+                    xpSec = (Math.Round(TotalExperience / GetRuntime()) / 60) / 60;
+                    //Calculating the seconds left to level up
+                    if(xpSec!=0)
+                    secondsLeft = Convert.ToInt32((expNextLvl / xpSec));
+                    //formatting data to make an output like DateFormat
+                    while (secondsLeft > 60)
+                    {
+                        secondsLeft -= 60;
+                        if (minutesLeft < 60)
+                        {
+                            minutesLeft++;
+                        }
+                        else
+                        {
+                            minutesLeft = 0;
+                            hoursLeft++;
+                        }
+                    }
+                    SetStatusText(string.Format(Username + " | Level: {0:0} - ({2:0} / {3:0}) | Runtime {1} | Stardust: {4:0}", v.Level, _getSessionRuntimeInTimeFormat(), (v.Experience - v.PrevLevelXp - XpDiff), (v.NextLevelXp - v.PrevLevelXp - XpDiff), profile.Profile.Currency.ToArray()[1].Amount) + " | XP/Hour: " + Math.Round(TotalExperience / GetRuntime()) + " | Pokemon/Hour: " + Math.Round(TotalPokemon / GetRuntime())+ " | NextLevel in: " + hoursLeft + ":" + minutesLeft + ":" + secondsLeft);
                 }
             await Task.Delay(1000);
             ConsoleLevelTitle(Username, client);
@@ -934,6 +975,15 @@ namespace PokemonGo.RocketAPI.Window
             return 0;
         }

+        public void confirmBotStopped()
+        {
+            //ConsoleClear(); // dont really want the console to be wipped on bot stop, unnecessary
+            ColoredConsoleWrite(Color.Red, $"Bot successfully stopped.");
+            startStopBotToolStripMenuItem.Text = "Start Bot";
+            Stopping = false;
+            bot_started = false;
+        }
+
         private void logTextBox_TextChanged(object sender, EventArgs e)
         {
             logTextBox.SelectionStart = logTextBox.Text.Length;
@@ -946,26 +996,41 @@ namespace PokemonGo.RocketAPI.Window
             settingsForm.Show();
         }

-        private void startBotToolStripMenuItem_Click(object sender, EventArgs e)
+        private static bool bot_started = false;
+        private void startStopBotToolStripMenuItem_Click(object sender, EventArgs e)
         {
-            startBotToolStripMenuItem.Enabled = false;
-            Task.Run(() =>
+            if (!bot_started)
             {
-                try
+                bot_started = true;
+                startStopBotToolStripMenuItem.Text = "Stop Bot";
+                Task.Run(() =>
                 {
-                    //ColoredConsoleWrite(ConsoleColor.White, "Coded by Ferox - edited by NecronomiconCoding");
-                    CheckVersion();
-                    Execute();
-                }
-                catch (PtcOfflineException)
+                    try
+                    {
+                        //ColoredConsoleWrite(ConsoleColor.White, "Coded by Ferox - edited by NecronomiconCoding");
+                        CheckVersion();
+                        Execute();
+                    }
+                    catch (PtcOfflineException)
+                    {
+                        ColoredConsoleWrite(Color.Red, "PTC Servers are probably down OR your credentials are wrong. Try google");
+                    }
+                    catch (Exception ex)
+                    {
+                        ColoredConsoleWrite(Color.Red, $"Unhandled exception: {ex}");
+                    }
+                });
+            } else
+            {
+                if (!ForceUnbanning)
                 {
-                    ColoredConsoleWrite(Color.Red, "PTC Servers are probably down OR your credentials are wrong. Try google");
-                }
-                catch (Exception ex)
+                    Stopping = true;
+                    ColoredConsoleWrite(Color.Red, $"Stopping the bot.. Waiting for the last action to be complete.");
+                } else
                 {
-                    ColoredConsoleWrite(Color.Red, $"Unhandled exception: {ex}");
+                    ColoredConsoleWrite(Color.Red, $"An action is in play, please wait until it's done.");
                 }
-            });
+            }
         }

         private void showAllToolStripMenuItem3_Click(object sender, EventArgs e)
diff --git a/PokemonGo/RocketAPI/Window/PokeUi.cs b/PokemonGo/RocketAPI/Window/PokeUi.cs
index 3ee7962..114ce62 100644
--- a/PokemonGo/RocketAPI/Window/PokeUi.cs
+++ b/PokemonGo/RocketAPI/Window/PokeUi.cs
@@ -38,15 +38,7 @@ namespace PokemonGo.RocketAPI.Window

             try
             {
-                switch (ClientSettings.AuthType)
-                {
-                    case AuthType.Ptc:
-                        await client.DoPtcLogin(ClientSettings.PtcUsername, ClientSettings.PtcPassword);
-                        break;
-                    case AuthType.Google:
-                        await client.DoGoogleLogin(ClientSettings.Email, ClientSettings.Password);
-                        break;
-                }
+                await client.Login();
                 await client.SetServer();
                 var profile = await client.GetProfile();
                 var inventory = await client.GetInventory();
diff --git a/PokemonGo/RocketAPI/Window/PokemonForm.cs b/PokemonGo/RocketAPI/Window/PokemonForm.cs
index 6f36da2..90e17bc 100644
--- a/PokemonGo/RocketAPI/Window/PokemonForm.cs
+++ b/PokemonGo/RocketAPI/Window/PokemonForm.cs
@@ -32,22 +32,13 @@ namespace PokemonGo.RocketAPI.Window

             try
             {
-                switch (ClientSettings.AuthType)
-                {
-                    case AuthType.Ptc:
-                        await client.DoPtcLogin(ClientSettings.PtcUsername, ClientSettings.PtcPassword);
-                        break;
-                    case AuthType.Google:
-                        await client.DoGoogleLogin(ClientSettings.Email, ClientSettings.Password);
-                        break;
-                }
-
+                await client.Login();
                 await client.SetServer();
                 var inventory = await client.GetInventory();
                 var pokemons =
                     inventory.InventoryDelta.InventoryItems.Select(i => i.InventoryItemData?.Pokemon)
                         .Where(p => p != null && p?.PokemonId > 0).OrderByDescending(key => key.Cp);
-
+

                 foreach (var pokemon in pokemons)
                 {
diff --git a/PokemonGo/RocketAPI/Window/Properties/AssemblyInfo.cs b/PokemonGo/RocketAPI/Window/Properties/AssemblyInfo.cs
index 3402d68..754dab1 100644
--- a/PokemonGo/RocketAPI/Window/Properties/AssemblyInfo.cs
+++ b/PokemonGo/RocketAPI/Window/Properties/AssemblyInfo.cs
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.7.0.1")]
+[assembly: AssemblyVersion("1.7.1.1")]
 [assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/README.md b/README.md
index f396ae3..74a65a8 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,9 @@
 # Pokemon-Go-Rocket-API

+# Discusion not controlled by me but by user 1461748123
+* Discord channel for user and developer discussions.
+* https://discord.gg/y6EU2qY
+
 #Window
 ![alt tag](https://github.com/DetectiveSquirrel/Pokemon-Go-Rocket-API/blob/master/MainWindow.png)
 ![alt tag](https://github.com/DetectiveSquirrel/Pokemon-Go-Rocket-API/blob/master/MainPokeUi.png)
@@ -27,17 +31,27 @@ A Pokémon Go bot in C#
 * Logs everything into Logs.txt

 ## Getting Started
-
-Go to PokemonGo\RocketAPI\Window\App.config -> Edit the Settings you like -> Build and Run (CTRL+F5)
+Build and Run (CTRL+F5)

 # Settings
 ## AuthType
-* *Google* - Google login
-* *Ptc* - Pokémon Trainer Club login with username/password combination
+* *google* - Google login
+* *ptc* - Pokémon Trainer Club

 ## PtcUsername
-* *username* for PTC account. No need for when using Google.
-* *password* for PTC account. No need for when using Google.
+* *username* - for PTC account. No need for when using Google.
+
+## PtcPassword
+* *password* - for PTC account. No need for when using Google.
+
+## Email
+* *email@gmail.com* - for Google account. No need for when using PTC.
+
+## Password
+* *password* - for Google account. No need for when using PTC.
+
+## GoogleRefreshToken
+* *token* - for Google account. No need for wen using PTC. (Obsolete)

 ## DefaultLatitude
 * *12.345678* - Latitude of your location you want to use the bot in. Number between -90 and +90. Doesn't matter how many numbers stand after the comma.
@@ -68,7 +82,8 @@ Go to PokemonGo\RocketAPI\Window\App.config -> Edit the Settings you like -> Bui
 * *probability* - Use RazzBerry when Pokémon catch chance is under a specific percentage.

 ## RazzBerrySetting
-* *value* - CP: Use RazzBerry when Pokémon is over this value | Probability Mode: Use Razzberry when % of catching is under this value
+* *cp value* - If RazzBerryMode is cp. Use RazzBerry when Pokémon is over this value
+* *probability value* - If RazzBerryMode is probability. Use Razzberry when % of catching is under this value. Between 0 and 1.

 ## TransferType
 * *none* - disables transferring
@@ -80,6 +95,19 @@ Go to PokemonGo\RocketAPI\Window\App.config -> Edit the Settings you like -> Bui
 ## TransferCPThreshold
 * *CP* - transfers all Pokémon with less CP than this value.

+## TransferIVThreshold
+* *IV* - transfers all Pokémon with less IV than this value. Between 0 and 1.
+
+## TravelSpeed
+* *Speed* - Travel speed in km/h
+
+## ImageSize
+* *px* - Pixel size for Pokémon Thumbnails
+
+## CatchPokemon
+* *true* - Catch Pokémon and get Items from PokéStops
+* *false* - Don't catch Pokémon and get Items from PokéStops
+
 ## EvolveAllGivenPokemons
 * *false* - Evolves no Pokémon.
 * *true* - Evolves all Pokémon.
\ No newline at end of file
You may download the files in Public Git.