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