From 1b7a34d37631b8fb372da9670ec7972c34fec4e4 Mon Sep 17 00:00:00 2001 From: Asval Date: Sat, 29 Jun 2019 23:50:11 +0200 Subject: [PATCH] token expiration + automatic dynamic key detection at startup --- FModel/App.config | 6 + FModel/Forms/AESManager.cs | 4 - FModel/Forms/Settings.Designer.cs | 2 +- FModel/MainWindow.cs | 149 +++++++++++------- .../Methods/AESManager/DynamicKeysManager.cs | 2 +- FModel/Methods/BackPAKs/DynamicPAKs.cs | 59 +++++-- FModel/Methods/BackPAKs/Parser/TokenParser.cs | 2 +- FModel/Properties/Settings.Designer.cs | 34 +++- FModel/Properties/Settings.settings | 6 + 9 files changed, 184 insertions(+), 80 deletions(-) diff --git a/FModel/App.config b/FModel/App.config index efd12ca3..dae6e858 100644 --- a/FModel/App.config +++ b/FModel/App.config @@ -151,6 +151,12 @@ False + + + + + 0 + diff --git a/FModel/Forms/AESManager.cs b/FModel/Forms/AESManager.cs index d54a64be..e10f04ba 100644 --- a/FModel/Forms/AESManager.cs +++ b/FModel/Forms/AESManager.cs @@ -75,10 +75,6 @@ namespace FModel.Forms { DynamicKeysManager.serialize(txtBox.Text.Substring(2).ToUpper(), dCurrentUsedPak); } - else - { - DynamicKeysManager.serialize("", dCurrentUsedPak); - } } } } diff --git a/FModel/Forms/Settings.Designer.cs b/FModel/Forms/Settings.Designer.cs index 5c225aeb..b1874af1 100644 --- a/FModel/Forms/Settings.Designer.cs +++ b/FModel/Forms/Settings.Designer.cs @@ -394,7 +394,7 @@ namespace FModel.Forms this.groupBox5.Size = new System.Drawing.Size(560, 92); this.groupBox5.TabIndex = 18; this.groupBox5.TabStop = false; - this.groupBox5.Text = "Optional - Backup PAKs 1000+"; + this.groupBox5.Text = "Optional - Automatic Key Detection for Dynamic PAKs"; // // checkBox1 // diff --git a/FModel/MainWindow.cs b/FModel/MainWindow.cs index 9b4c1144..31b3df2a 100644 --- a/FModel/MainWindow.cs +++ b/FModel/MainWindow.cs @@ -136,6 +136,65 @@ namespace FModel } } } + + /// + /// ask the keychain api for all dynamic keys and their guids + /// if an API guid match a local guid, the key is saved and the pak can be opened with this key + /// + private void checkAndAddDynamicKeys() + { + if (!File.Exists(DynamicKeysManager.path)) + { + DynamicKeysManager.AESEntries = new List(); + DynamicKeysManager.serialize("", ""); + } + + string[] _backupDynamicKeys = null; + if (DLLImport.IsInternetAvailable() && (!string.IsNullOrWhiteSpace(Settings.Default.eEmail) && !string.IsNullOrWhiteSpace(Settings.Default.ePassword))) + { + string myContent = DynamicPAKs.GetEndpoint("https://fortnite-public-service-prod11.ol.epicgames.com/fortnite/api/storefront/v2/keychain", true); + + if (myContent.Contains("\"errorCode\": \"errors.com.epicgames.common.authentication.authentication_failed\"")) + { + AppendText("EPIC Authentication Failed.", Color.Red, true); + } + else + { + AppendText("EPIC Authentication Success.", Color.DarkGreen, true); + AppendText("", Color.Green, true); + _backupDynamicKeys = AesKeyParser.FromJson(myContent); + } + } + + if (_backupDynamicKeys != null) + { + DynamicKeysManager.AESEntries = new List(); + foreach (string myString in _backupDynamicKeys) + { + string[] parts = myString.Split(':'); + string apiGuid = DynamicPAKs.getPakGuidFromKeychain(parts); + + string actualPakGuid = ThePak.dynamicPaksList.Where(i => i.thePakGuid == apiGuid).Select(i => i.thePakGuid).FirstOrDefault(); + string actualPakName = ThePak.dynamicPaksList.Where(i => i.thePakGuid == apiGuid).Select(i => i.thePak).FirstOrDefault(); + + bool pakAlreadyExist = DynamicKeysManager.AESEntries.Where(i => i.thePak == actualPakName).Any(); + + if (!string.IsNullOrEmpty(actualPakGuid) && !pakAlreadyExist) + { + byte[] bytes = Convert.FromBase64String(parts[1]); + string aeskey = BitConverter.ToString(bytes).Replace("-", ""); + + DynamicKeysManager.serialize(aeskey.ToUpper(), actualPakName); + + AppendText(actualPakName, Color.SeaGreen); + AppendText(" can be opened.", Color.Black, true); + } + } + AppendText("", Color.Green, true); + } + + DynamicKeysManager.deserialize(); + } //EVENTS private async void MainWindow_Load(object sender, EventArgs e) @@ -155,10 +214,10 @@ namespace FModel Settings.Default.UpdateSettings = false; Settings.Default.Save(); } - DynamicKeysManager.deserialize(); await Task.Run(() => { FillWithPaKs(); + checkAndAddDynamicKeys(); Utilities.colorMyPaks(loadOneToolStripMenuItem); Utilities.SetOutputFolder(); Utilities.SetFolderPermission(App.DefaultOutputPath); @@ -578,24 +637,8 @@ namespace FModel } private void CreateBackupList() { - string[] _backupDynamicKeys = null; StringBuilder sb = new StringBuilder(); - if (DLLImport.IsInternetAvailable() && (!string.IsNullOrWhiteSpace(Settings.Default.eEmail) || !string.IsNullOrWhiteSpace(Settings.Default.ePassword))) - { - string myContent = DynamicPAKs.GetEndpoint("https://fortnite-public-service-prod11.ol.epicgames.com/fortnite/api/storefront/v2/keychain", true); - - if (myContent.Contains("\"errorCode\": \"errors.com.epicgames.common.authentication.authentication_failed\"")) - { - AppendText("EPIC Authentication Failed.", Color.Red, true); - } - else - { - AppendText("Successfully Authenticated.", Color.Green, true); - _backupDynamicKeys = AesKeyParser.FromJson(myContent); - } - } - for (int i = 0; i < ThePak.mainPaksList.Count; i++) { try @@ -605,6 +648,7 @@ namespace FModel catch (Exception) { AppendText("0x" + Settings.Default.AESKey + " doesn't work with the main paks.", Color.Red, true); + JohnWick.MyExtractor.Dispose(); break; } @@ -619,53 +663,40 @@ namespace FModel } UpdateConsole(".PAK mount point: " + JohnWick.MyExtractor.GetMountPoint().Substring(9), Color.FromArgb(255, 244, 132, 66), "Waiting"); } + JohnWick.MyExtractor.Dispose(); } for (int i = 0; i < ThePak.dynamicPaksList.Count; i++) { - if (_backupDynamicKeys != null) + string pakName = DynamicKeysManager.AESEntries.Where(x => x.thePak == ThePak.dynamicPaksList[i].thePak).Select(x => x.thePak).FirstOrDefault(); + string pakKey = DynamicKeysManager.AESEntries.Where(x => x.thePak == ThePak.dynamicPaksList[i].thePak).Select(x => x.theKey).FirstOrDefault(); + + if (!string.IsNullOrEmpty(pakName) && !string.IsNullOrEmpty(pakKey)) { - string oldGuid = string.Empty; - foreach (string myString in _backupDynamicKeys) + try { - string[] parts = myString.Split(':'); - string newGuid = DynamicPAKs.getPakGuidFromKeychain(parts); - - /*** - * if same guid several time in keychain do not backup twice - * it works fine that way because of the loop through all the paks - * even if in keychain we do "found 1004" -> "found 1001" -> "found 1004" through the paks we do 1000 -> 1001 -> 1002... - ***/ - if (newGuid == ThePak.dynamicPaksList[i].thePakGuid && oldGuid != newGuid) - { - byte[] bytes = Convert.FromBase64String(parts[1]); - string aeskey = BitConverter.ToString(bytes).Replace("-", ""); - oldGuid = newGuid; - - try - { - JohnWick.MyExtractor = new PakExtractor(Settings.Default.PAKsPath + "\\" + ThePak.dynamicPaksList[i].thePak, aeskey); - } - catch (Exception) - { - AppendText("0x" + aeskey + " doesn't work with " + ThePak.dynamicPaksList[i].thePak, Color.Red, true); //this should never be triggered - continue; - } - - string[] CurrentUsedPakLines = JohnWick.MyExtractor.GetFileList().ToArray(); - if (CurrentUsedPakLines != null) - { - for (int ii = 0; ii < CurrentUsedPakLines.Length; ii++) - { - CurrentUsedPakLines[ii] = JohnWick.MyExtractor.GetMountPoint().Substring(6) + CurrentUsedPakLines[ii]; - - sb.Append(CurrentUsedPakLines[ii] + "\n"); - } - AppendText("Backing up ", Color.Black); - AppendText(ThePak.dynamicPaksList[i].thePak, Color.DarkRed, true); - } - } + JohnWick.MyExtractor = new PakExtractor(Settings.Default.PAKsPath + "\\" + pakName, pakKey); } + catch (Exception) + { + AppendText("0x" + pakKey + " doesn't work with " + ThePak.dynamicPaksList[i].thePak, Color.Red, true); + JohnWick.MyExtractor.Dispose(); + continue; + } + + string[] CurrentUsedPakLines = JohnWick.MyExtractor.GetFileList().ToArray(); + if (CurrentUsedPakLines != null) + { + for (int ii = 0; ii < CurrentUsedPakLines.Length; ii++) + { + CurrentUsedPakLines[ii] = JohnWick.MyExtractor.GetMountPoint().Substring(6) + CurrentUsedPakLines[ii]; + + sb.Append(CurrentUsedPakLines[ii] + "\n"); + } + AppendText("Backing up ", Color.Black); + AppendText(ThePak.dynamicPaksList[i].thePak, Color.DarkRed, true); + } + JohnWick.MyExtractor.Dispose(); } } @@ -1648,7 +1679,6 @@ namespace FModel { CopySelectedFile(); } - private void CopySelectedFile(bool isName = false, bool withExtension = true) { if (listBox1.SelectedItem != null) @@ -1676,7 +1706,6 @@ namespace FModel { SaveAsJSON(); } - private void SaveAsJSON() { if (!string.IsNullOrEmpty(scintilla1.Text)) @@ -1704,6 +1733,7 @@ namespace FModel } #endregion + #region RIGHT CLICK private void copyFileToolStripMenuItem_Click(object sender, EventArgs e) { CopySelectedFile(); @@ -1733,5 +1763,6 @@ namespace FModel { SaveAsJSON(); } + #endregion } } diff --git a/FModel/Methods/AESManager/DynamicKeysManager.cs b/FModel/Methods/AESManager/DynamicKeysManager.cs index a6fb4300..dbcc66f7 100644 --- a/FModel/Methods/AESManager/DynamicKeysManager.cs +++ b/FModel/Methods/AESManager/DynamicKeysManager.cs @@ -8,7 +8,7 @@ namespace FModel { public static List AESEntries { get; set; } private static XmlSerializer serializer = new XmlSerializer(typeof(List)); - private static string path = Properties.Settings.Default.ExtractOutput + "\\AESManager.xml"; + public static string path = Properties.Settings.Default.ExtractOutput + "\\AESManager.xml"; public static void serialize(string key, string pak) { diff --git a/FModel/Methods/BackPAKs/DynamicPAKs.cs b/FModel/Methods/BackPAKs/DynamicPAKs.cs index 20cdf5cf..e965fb4e 100644 --- a/FModel/Methods/BackPAKs/DynamicPAKs.cs +++ b/FModel/Methods/BackPAKs/DynamicPAKs.cs @@ -6,11 +6,15 @@ using System.Globalization; using System.Collections.Generic; using System.Linq; using System.Text; +using System; namespace FModel { static class DynamicPAKs { + private static string AccessToken { get; set; } + private static string AccessCode { get; set; } + /// /// get url content as string with authentication /// @@ -19,12 +23,16 @@ namespace FModel /// url content public static string GetEndpoint(string url, bool auth) { + if (string.IsNullOrEmpty(Properties.Settings.Default.ExchangeToken) || isTokenExpired()) + { + refreshToken(); + } + RestClient EndpointClient = new RestClient(url); RestRequest EndpointRequest = new RestRequest(Method.GET); - if (auth) { - EndpointRequest.AddHeader("Authorization", "bearer " + getExchangeToken(getAccessCode(getAccessToken(Properties.Settings.Default.eEmail, Properties.Settings.Default.ePassword)))); + EndpointRequest.AddHeader("Authorization", "bearer " + Properties.Settings.Default.ExchangeToken); } var response = EndpointClient.Execute(EndpointRequest); @@ -32,7 +40,9 @@ namespace FModel return content; } - private static string getAccessToken(string email, string password) + + + private static void getAccessToken(string email, string password) { RestClient getAccessTokenClient = new RestClient("https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/token"); RestRequest getAccessTokenRequest = new RestRequest(Method.POST); @@ -45,18 +55,18 @@ namespace FModel getAccessTokenRequest.AddHeader("Authorization", "basic MzQ0NmNkNzI2OTRjNGE0NDg1ZDgxYjc3YWRiYjIxNDE6OTIwOWQ0YTVlMjVhNDU3ZmI5YjA3NDg5ZDMxM2I0MWE="); getAccessTokenRequest.AddHeader("Content-Type", "application/x-www-form-urlencoded"); - return TokenParser.FromJson(getAccessTokenClient.Execute(getAccessTokenRequest).Content).AccessToken; + AccessToken = TokenParser.FromJson(getAccessTokenClient.Execute(getAccessTokenRequest).Content).AccessToken; } - private static string getAccessCode(string accessToken) + private static void getAccessCode(string accessToken) { RestClient getAccessCodeClient = new RestClient("https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/exchange"); RestRequest getAccessCodeRequest = new RestRequest(Method.GET); getAccessCodeRequest.AddHeader("Authorization", "bearer " + accessToken); - return AccessCodeParser.FromJson(getAccessCodeClient.Execute(getAccessCodeRequest).Content).Code; + AccessCode = AccessCodeParser.FromJson(getAccessCodeClient.Execute(getAccessCodeRequest).Content).Code; } - private static string getExchangeToken(string accessCode) + private static void getExchangeToken(string accessCode) { RestClient getExchangeTokenClient = new RestClient("https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/token"); RestRequest getExchangeTokenRequest = new RestRequest(Method.POST); @@ -65,10 +75,40 @@ namespace FModel getExchangeTokenRequest.AddHeader("Content-Type", "application/x-www-form-urlencoded"); getExchangeTokenRequest.AddParameter("grant_type", "exchange_code"); getExchangeTokenRequest.AddParameter("exchange_code", accessCode); - getExchangeTokenRequest.AddParameter("includePerms", true); + getExchangeTokenRequest.AddParameter("includePerms", "true"); getExchangeTokenRequest.AddParameter("token_type", "eg1"); - return TokenParser.FromJson(getExchangeTokenClient.Execute(getExchangeTokenRequest).Content).AccessToken; + string content = getExchangeTokenClient.Execute(getExchangeTokenRequest).Content; + + Properties.Settings.Default.ExchangeToken = TokenParser.FromJson(content).AccessToken; + Properties.Settings.Default.TokenExpiration = DateTimeOffset.Parse(TokenParser.FromJson(content).ExpiresAt).ToUnixTimeMilliseconds(); + Properties.Settings.Default.Save(); + } + + /// + /// check the current time and the expiration date of our token + /// 60 seconds before it expires, it's considered expired and should be refreshed + /// + /// + private static bool isTokenExpired() + { + long currentTime = DateTimeOffset.Now.ToUnixTimeMilliseconds(); + if ((currentTime - 60000) >= Properties.Settings.Default.TokenExpiration) + { + return true; + } + else { return false; } + } + + /// + /// steps to get our token or refresh + /// + public static void refreshToken() + { + Console.WriteLine("refresh"); + getAccessToken(Properties.Settings.Default.eEmail, Properties.Settings.Default.ePassword); + getAccessCode(AccessToken); + getExchangeToken(AccessCode); } private static IEnumerable SplitGuid(string str, int chunkSize) @@ -76,6 +116,7 @@ namespace FModel return Enumerable.Range(0, str.Length / chunkSize) .Select(i => str.Substring(i * chunkSize, chunkSize)); } + /// /// split KeychainPart each 8 letter /// for each of these letters, convert to hexadecimal as string diff --git a/FModel/Methods/BackPAKs/Parser/TokenParser.cs b/FModel/Methods/BackPAKs/Parser/TokenParser.cs index 082ef63d..e9bbc196 100644 --- a/FModel/Methods/BackPAKs/Parser/TokenParser.cs +++ b/FModel/Methods/BackPAKs/Parser/TokenParser.cs @@ -23,7 +23,7 @@ namespace FModel.Methods.BackupPAKs.Parser.TokenParser public long ExpiresIn { get; set; } [JsonProperty("expires_at")] - public DateTimeOffset ExpiresAt { get; set; } + public string ExpiresAt { get; set; } [JsonProperty("token_type")] public string TokenType { get; set; } diff --git a/FModel/Properties/Settings.Designer.cs b/FModel/Properties/Settings.Designer.cs index 30d2e11e..b513af8d 100644 --- a/FModel/Properties/Settings.Designer.cs +++ b/FModel/Properties/Settings.Designer.cs @@ -1,10 +1,10 @@ //------------------------------------------------------------------------------ // -// Este código fue generado por una herramienta. -// Versión de runtime:4.0.30319.42000 +// Ce code a été généré par un outil. +// Version du runtime :4.0.30319.42000 // -// Los cambios en este archivo podrían causar un comportamiento incorrecto y se perderán si -// se vuelve a generar el código. +// Les modifications apportées à ce fichier peuvent provoquer un comportement incorrect et seront perdues si +// le code est régénéré. // //------------------------------------------------------------------------------ @@ -12,7 +12,7 @@ namespace FModel.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.0.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -586,5 +586,29 @@ namespace FModel.Properties { this["UMCTGalleries"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string ExchangeToken { + get { + return ((string)(this["ExchangeToken"])); + } + set { + this["ExchangeToken"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0")] + public long TokenExpiration { + get { + return ((long)(this["TokenExpiration"])); + } + set { + this["TokenExpiration"] = value; + } + } } } diff --git a/FModel/Properties/Settings.settings b/FModel/Properties/Settings.settings index ce4f1c16..f0008e4c 100644 --- a/FModel/Properties/Settings.settings +++ b/FModel/Properties/Settings.settings @@ -143,5 +143,11 @@ False + + + + + 0 + \ No newline at end of file