Merge with beta build, login seems broken

Brian [2016-07-30 10:56:37]
Merge with beta build, login seems broken
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.cs
PokemonGo/RocketAPI/Window/PokeUi.cs
PokemonGo/RocketAPI/Window/PokemonForm.cs
PokemonGo/RocketAPI/Window/Properties/AssemblyInfo.cs
diff --git a/PokemonGo/RocketAPI/Client.cs b/PokemonGo/RocketAPI/Client.cs
index 5d4cc5f..cf2a86b 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 71e4fbd..8760094 100644
--- a/PokemonGo/RocketAPI/PokemonGo.RocketAPI.csproj
+++ b/PokemonGo/RocketAPI/PokemonGo.RocketAPI.csproj
@@ -71,21 +71,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.cs b/PokemonGo/RocketAPI/Window/MainForm.cs
index 2b255f5..88454b7 100644
--- a/PokemonGo/RocketAPI/Window/MainForm.cs
+++ b/PokemonGo/RocketAPI/Window/MainForm.cs
@@ -261,17 +261,16 @@ namespace PokemonGo.RocketAPI.Window
                 {
                     switch (ClientSettings.AuthType)
                     {
+
                         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,9 +283,9 @@ namespace PokemonGo.RocketAPI.Window
                     ConsoleLevelTitle(profile.Profile.Username, client);

                     // Write the players ingame details
-                    InitializeMap();
                     ColoredConsoleWrite(Color.Yellow, "----------------------------");
-                    if (ClientSettings.AuthType == AuthType.Ptc)
+                    /*// dont actually want to display info but keeping here incase people want to \O_O/
+                     * if (ClientSettings.AuthType == AuthType.Ptc)
                     {
                         ColoredConsoleWrite(Color.Cyan, "Account: " + ClientSettings.PtcUsername);
                         ColoredConsoleWrite(Color.Cyan, "Password: " + ClientSettings.PtcPassword + "\n");
@@ -295,7 +294,7 @@ namespace PokemonGo.RocketAPI.Window
                     {
                         ColoredConsoleWrite(Color.Cyan, "Email: " + ClientSettings.Email);
                         ColoredConsoleWrite(Color.Cyan, "Password: " + ClientSettings.Password + "\n");
-                    }
+                    }*/
                     ColoredConsoleWrite(Color.DarkGray, "Name: " + profile.Profile.Username);
                     ColoredConsoleWrite(Color.DarkGray, "Team: " + profile.Profile.Team);
                     if (profile.Profile.Currency.ToArray()[0].Amount > 0) // If player has any pokecoins it will show how many they have.
@@ -313,6 +312,7 @@ namespace PokemonGo.RocketAPI.Window
                         ColoredConsoleWrite(Color.DarkGray, "Unable to get Country/Place");
                     }

+
                     ColoredConsoleWrite(Color.Yellow, "----------------------------");

                     // I believe a switch is more efficient and easier to read.
@@ -383,11 +383,13 @@ namespace PokemonGo.RocketAPI.Window
                     Execute();
                 }
             }
+
         }

         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=pokemonbot"))
+
+            using (XmlReader reader = XmlReader.Create(@"http://api.geonames.org/findNearby?lat=" + lat + "&lng=" + lon + "&username=pokemongobot"))
             {
                 while (reader.Read())
                 {
@@ -417,6 +419,7 @@ namespace PokemonGo.RocketAPI.Window
             }
             return "Error";
         }
+
         private async Task ExecuteCatchAllNearbyPokemons(Client client)
         {
             var mapObjects = await client.GetMapObjects();
@@ -944,11 +947,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);
@@ -1042,6 +1067,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;
@@ -1199,20 +1233,20 @@ namespace PokemonGo.RocketAPI.Window
         {
             objectListView1.ButtonClick += PokemonListButton_Click;

-             /* pkmnName.ImageGetter = delegate (object rowObject)
-             {
-                 PokemonData pokemon = (PokemonData)rowObject;
+            /* pkmnName.ImageGetter = delegate (object rowObject)
+            {
+                PokemonData pokemon = (PokemonData)rowObject;

-                 String key = pokemon.PokemonId.ToString();
+                String key = pokemon.PokemonId.ToString();

-                 if (!objectListView1.SmallImageList.Images.ContainsKey(key))
-                 {
-                     Image largeImage = GetPokemonImage((int)pokemon.PokemonId);
-                     objectListView1.SmallImageList.Images.Add(key, largeImage);
-                     objectListView1.LargeImageList.Images.Add(key, largeImage);
-                 }
-                 return key;
-             };  */
+                if (!objectListView1.SmallImageList.Images.ContainsKey(key))
+                {
+                    Image largeImage = GetPokemonImage((int)pokemon.PokemonId);
+                    objectListView1.SmallImageList.Images.Add(key, largeImage);
+                    objectListView1.LargeImageList.Images.Add(key, largeImage);
+                }
+                return key;
+            };  */

             objectListView1.CellToolTipShowing += delegate (object sender, ToolTipShowingEventArgs args)
             {
@@ -1248,18 +1282,10 @@ namespace PokemonGo.RocketAPI.Window
             objectListView1.Enabled = false;

             client2 = new Client(ClientSettings);
-
             try
             {
-                switch (ClientSettings.AuthType)
-                {
-                    case AuthType.Ptc:
-                        await client2.DoPtcLogin(ClientSettings.PtcUsername, ClientSettings.PtcPassword);
-                        break;
-                    case AuthType.Google:
-                        await client2.DoGoogleLogin(ClientSettings.Email, ClientSettings.Password);
-                        break;
-                }
+
+               await client2.Login();
                 await client2.SetServer();
                 var inventory = await client2.GetInventory();
                 var pokemons = inventory.InventoryDelta.InventoryItems.Select(i => i.InventoryItemData?.Pokemon).Where(p => p != null && p?.PokemonId > 0).OrderByDescending(key => key.Cp);
diff --git a/PokemonGo/RocketAPI/Window/PokeUi.cs b/PokemonGo/RocketAPI/Window/PokeUi.cs
index 79cc1e1..5518444 100644
--- a/PokemonGo/RocketAPI/Window/PokeUi.cs
+++ b/PokemonGo/RocketAPI/Window/PokeUi.cs
@@ -38,15 +38,9 @@ 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.PtcUsername, ClientSettings.PtcPassword);
-                        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 5411bd3..c558040 100644
--- a/PokemonGo/RocketAPI/Window/PokemonForm.cs
+++ b/PokemonGo/RocketAPI/Window/PokemonForm.cs
@@ -32,22 +32,15 @@ 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.PtcUsername, ClientSettings.PtcPassword);
-                        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")]
You may download the files in Public Git.