mirror of
https://github.com/4sval/FModel.git
synced 2026-03-22 01:34:37 -05:00
token expiration + automatic dynamic key detection at startup
This commit is contained in:
parent
bfb7e2acd0
commit
1b7a34d376
|
|
@ -151,6 +151,12 @@
|
|||
<setting name="UMCTGalleries" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
<setting name="ExchangeToken" serializeAs="String">
|
||||
<value />
|
||||
</setting>
|
||||
<setting name="TokenExpiration" serializeAs="String">
|
||||
<value>0</value>
|
||||
</setting>
|
||||
</FModel.Properties.Settings>
|
||||
</userSettings>
|
||||
<runtime>
|
||||
|
|
|
|||
|
|
@ -75,10 +75,6 @@ namespace FModel.Forms
|
|||
{
|
||||
DynamicKeysManager.serialize(txtBox.Text.Substring(2).ToUpper(), dCurrentUsedPak);
|
||||
}
|
||||
else
|
||||
{
|
||||
DynamicKeysManager.serialize("", dCurrentUsedPak);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
FModel/Forms/Settings.Designer.cs
generated
2
FModel/Forms/Settings.Designer.cs
generated
|
|
@ -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
|
||||
//
|
||||
|
|
|
|||
|
|
@ -136,6 +136,65 @@ namespace FModel
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
private void checkAndAddDynamicKeys()
|
||||
{
|
||||
if (!File.Exists(DynamicKeysManager.path))
|
||||
{
|
||||
DynamicKeysManager.AESEntries = new List<AESEntry>();
|
||||
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<AESEntry>();
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ namespace FModel
|
|||
{
|
||||
public static List<AESEntry> AESEntries { get; set; }
|
||||
private static XmlSerializer serializer = new XmlSerializer(typeof(List<AESEntry>));
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
||||
/// <summary>
|
||||
/// get url content as string with authentication
|
||||
/// </summary>
|
||||
|
|
@ -19,12 +23,16 @@ namespace FModel
|
|||
/// <returns> url content </returns>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// check the current time and the expiration date of our token
|
||||
/// 60 seconds before it expires, it's considered expired and should be refreshed
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static bool isTokenExpired()
|
||||
{
|
||||
long currentTime = DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
||||
if ((currentTime - 60000) >= Properties.Settings.Default.TokenExpiration)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else { return false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// steps to get our token or refresh
|
||||
/// </summary>
|
||||
public static void refreshToken()
|
||||
{
|
||||
Console.WriteLine("refresh");
|
||||
getAccessToken(Properties.Settings.Default.eEmail, Properties.Settings.Default.ePassword);
|
||||
getAccessCode(AccessToken);
|
||||
getExchangeToken(AccessCode);
|
||||
}
|
||||
|
||||
private static IEnumerable<string> 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// split KeychainPart each 8 letter
|
||||
/// for each of these letters, convert to hexadecimal as string
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
34
FModel/Properties/Settings.Designer.cs
generated
34
FModel/Properties/Settings.Designer.cs
generated
|
|
@ -1,10 +1,10 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// 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é.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,5 +143,11 @@
|
|||
<Setting Name="UMCTGalleries" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">False</Value>
|
||||
</Setting>
|
||||
<Setting Name="ExchangeToken" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="TokenExpiration" Type="System.Int64" Scope="User">
|
||||
<Value Profile="(Default)">0</Value>
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
||||
Loading…
Reference in New Issue
Block a user