diff --git a/FModel/Framework/FEndpoint.cs b/FModel/Framework/FEndpoint.cs index 16fbd42b..cd80f109 100644 --- a/FModel/Framework/FEndpoint.cs +++ b/FModel/Framework/FEndpoint.cs @@ -1,4 +1,9 @@ -namespace FModel.Framework; +using System.Linq; +using FModel.ViewModels.ApiEndpoints; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace FModel.Framework; public class FEndpoint : ViewModel { @@ -6,11 +11,14 @@ public class FEndpoint : ViewModel public string Url { get => _url; - set - { - SetProperty(ref _url, value); - RaisePropertyChanged(nameof(IsEnabled)); - } + set => SetProperty(ref _url, value); + } + + private string _path; + public string Path + { + get => _path; + set => SetProperty(ref _path, value); } private bool _overwrite; @@ -20,18 +28,65 @@ public bool Overwrite set => SetProperty(ref _overwrite, value); } - private string _path; - public string Path + private string _filePath; + public string FilePath { - get => _path; - set => SetProperty(ref _path, value); + get => _filePath; + set => SetProperty(ref _filePath, value); + } + + private bool _isValid; + public bool IsValid + { + get => _isValid; + set + { + SetProperty(ref _isValid, value); + RaisePropertyChanged(nameof(Label)); + } } - public bool IsEnabled => !string.IsNullOrWhiteSpace(_url); // change this later + [JsonIgnore] + public string Label => IsValid ? + "Your endpoint configuration is valid! Please, avoid any unnecessary modifications!" : + "Your endpoint configuration DOES NOT seem to be valid yet! Please, test it out!"; public FEndpoint() {} - public FEndpoint(string url) + public FEndpoint(string url, string path) { Url = url; + Path = path; + IsValid = !string.IsNullOrEmpty(url) && !string.IsNullOrEmpty(path); // be careful with this + } + + public void TryValidate(DynamicApiEndpoint endpoint, EEndpointType type, out JToken response) + { + response = null; + if (string.IsNullOrEmpty(Url) || string.IsNullOrEmpty(Path)) + { + IsValid = false; + } + else switch (type) + { + case EEndpointType.Aes: + { + var r = endpoint.GetAesKeys(default, Url, Path); + response = JToken.FromObject(r); + IsValid = r.IsValid; + break; + } + case EEndpointType.Mapping: + { + var r = endpoint.GetMappings(default, Url, Path); + response = JToken.FromObject(r); + IsValid = r.Any(x => x.IsValid); + break; + } + default: + { + IsValid = false; + break; + } + } } } diff --git a/FModel/Helper.cs b/FModel/Helper.cs index cee8cb20..1f4e55e4 100644 --- a/FModel/Helper.cs +++ b/FModel/Helper.cs @@ -21,6 +21,17 @@ public static bool IAmThePanda(string key) key.StartsWith("mu", StringComparison.OrdinalIgnoreCase) && key.EndsWith("sus", StringComparison.OrdinalIgnoreCase); + public static string FixKey(string key) + { + if (string.IsNullOrEmpty(key)) + return string.Empty; + + if (key.StartsWith("0x")) + key = key[2..]; + + return "0x" + key.ToUpper().Trim(); + } + public static void OpenWindow(string windowName, Action action) where T : Window { if (!IsWindowOpen(windowName)) diff --git a/FModel/Settings/UserSettings.cs b/FModel/Settings/UserSettings.cs index 93edc572..b9242c41 100644 --- a/FModel/Settings/UserSettings.cs +++ b/FModel/Settings/UserSettings.cs @@ -39,14 +39,14 @@ public static void Delete() if (File.Exists(FilePath)) File.Delete(FilePath); } - public static bool IsEndpointEnabled(FGame game, EEndpointType type, out FEndpoint endpoint) + public static bool IsEndpointValid(FGame game, EEndpointType type, out FEndpoint endpoint) { endpoint = null; if (!Default.CustomEndpoints.TryGetValue(game, out var endpoints)) return false; endpoint = endpoints[(int) type]; - return endpoint.IsEnabled; + return endpoint.IsValid; } private bool _showChangelog = true; @@ -384,8 +384,8 @@ public bool SaveMorphTargets { FGame.FortniteGame, new [] { - new FEndpoint("https://fortnitecentral.gmatrixgames.ga/api/v1/aes"), - new FEndpoint("https://fortnitecentral.gmatrixgames.ga/api/v1/mappings") + new FEndpoint("https://fortnitecentral.gmatrixgames.ga/api/v1/aes", "$.['mainKey','dynamicKeys']"), + new FEndpoint("https://fortnitecentral.gmatrixgames.ga/api/v1/mappings", "$.[?(@.meta.compressionMethod=='Oodle')].['url','fileName']") } }, {FGame.ShooterGame, new FEndpoint[]{new (), new ()}}, diff --git a/FModel/ViewModels/AesManagerViewModel.cs b/FModel/ViewModels/AesManagerViewModel.cs index 3b21c3dc..82b37e18 100644 --- a/FModel/ViewModels/AesManagerViewModel.cs +++ b/FModel/ViewModels/AesManagerViewModel.cs @@ -51,7 +51,7 @@ public async Task InitAes() DynamicKeys = null }; - _mainKey.Key = FixKey(_keysFromSettings.MainKey); + _mainKey.Key = Helper.FixKey(_keysFromSettings.MainKey); AesKeys = new FullyObservableCollection(EnumerateAesKeys()); AesKeys.ItemPropertyChanged += AesKeysOnItemPropertyChanged; AesKeysView = new ListCollectionView(AesKeys) { SortDescriptions = { new SortDescription("Name", ListSortDirection.Ascending) } }; @@ -63,11 +63,11 @@ private void AesKeysOnItemPropertyChanged(object sender, ItemPropertyChangedEven if (e.PropertyName != "Key" || sender is not FullyObservableCollection collection) return; - var key = FixKey(collection[e.CollectionIndex].Key); + var key = Helper.FixKey(collection[e.CollectionIndex].Key); if (e.CollectionIndex == 0) { if (!HasChange) - HasChange = FixKey(_keysFromSettings.MainKey) != key; + HasChange = Helper.FixKey(_keysFromSettings.MainKey) != key; _keysFromSettings.MainKey = key; } @@ -87,7 +87,7 @@ private void AesKeysOnItemPropertyChanged(object sender, ItemPropertyChangedEven else if (_keysFromSettings.DynamicKeys.FirstOrDefault(x => x.Guid == collection[e.CollectionIndex].Guid.ToString()) is { } d) { if (!HasChange) - HasChange = FixKey(d.Key) != key; + HasChange = Helper.FixKey(d.Key) != key; d.Key = key; } @@ -117,17 +117,6 @@ public async Task UpdateProvider(bool isLaunch) Log.Information("{@Json}", UserSettings.Default); } - private string FixKey(string key) - { - if (string.IsNullOrEmpty(key)) - return string.Empty; - - if (key.StartsWith("0x")) - key = key[2..]; - - return "0x" + key.ToUpper().Trim(); - } - private IEnumerable EnumerateAesKeys() { yield return _mainKey; @@ -145,7 +134,7 @@ private IEnumerable EnumerateAesKeys() k = dynamicKey.Key; } - file.Key = FixKey(k); + file.Key = Helper.FixKey(k); yield return file; } } diff --git a/FModel/ViewModels/ApiEndpoints/DynamicApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/DynamicApiEndpoint.cs index 3c2244e5..490d4df2 100644 --- a/FModel/ViewModels/ApiEndpoints/DynamicApiEndpoint.cs +++ b/FModel/ViewModels/ApiEndpoints/DynamicApiEndpoint.cs @@ -1,7 +1,9 @@ -using System.Threading; +using System.Linq; +using System.Threading; using System.Threading.Tasks; using FModel.Framework; using FModel.ViewModels.ApiEndpoints.Models; +using Newtonsoft.Json.Linq; using RestSharp; using Serilog; @@ -13,35 +15,55 @@ public DynamicApiEndpoint(RestClient client) : base(client) { } - public async Task GetAesKeysAsync(CancellationToken token, string url) + public async Task GetAesKeysAsync(CancellationToken token, string url, string path) { var request = new FRestRequest(url) { OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; } }; - var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); + var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); + var body = JToken.Parse(response.Content!); Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString); - return response.Data; + + var tokens = body.SelectTokens(path); + var ret = new AesResponse { MainKey = Helper.FixKey(tokens.ElementAtOrDefault(0).ToString()) }; + if (tokens.ElementAtOrDefault(1) is JArray dynamicKeys) + { + foreach (var dynamicKey in dynamicKeys) + { + if (dynamicKey["guid"] is not { } guid || dynamicKey["key"] is not { } key) + continue; + + ret.DynamicKeys.Add(new DynamicKey{Guid = guid.ToString(), Key = Helper.FixKey(key.ToString())}); + } + } + return ret; } - public AesResponse GetAesKeys(CancellationToken token, string url) + public AesResponse GetAesKeys(CancellationToken token, string url, string path) { - return GetAesKeysAsync(token, url).GetAwaiter().GetResult(); + return GetAesKeysAsync(token, url, path).GetAwaiter().GetResult(); } - public async Task GetMappingsAsync(CancellationToken token, string url) + public async Task GetMappingsAsync(CancellationToken token, string url, string path) { var request = new FRestRequest(url) { OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; } }; - var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); + var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); + var body = JToken.Parse(response.Content!); Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString); - return response.Data; + + var tokens = body.SelectTokens(path); + var ret = new MappingsResponse[] {new()}; + ret[0].Url = tokens.ElementAtOrDefault(0).ToString(); + ret[0].FileName = tokens.ElementAtOrDefault(1).ToString(); + return ret; } - public MappingsResponse[] GetMappings(CancellationToken token, string url) + public MappingsResponse[] GetMappings(CancellationToken token, string url, string path) { - return GetMappingsAsync(token, url).GetAwaiter().GetResult(); + return GetMappingsAsync(token, url, path).GetAwaiter().GetResult(); } } diff --git a/FModel/ViewModels/ApiEndpoints/Models/AesResponse.cs b/FModel/ViewModels/ApiEndpoints/Models/AesResponse.cs index 745ca904..5f6aad6b 100644 --- a/FModel/ViewModels/ApiEndpoints/Models/AesResponse.cs +++ b/FModel/ViewModels/ApiEndpoints/Models/AesResponse.cs @@ -19,7 +19,7 @@ public AesResponse() } [I] public bool HasDynamicKeys => DynamicKeys is { Count: > 0 }; - [I] public bool IsValid => !string.IsNullOrEmpty(MainKey); + [I] public bool IsValid => MainKey.Length == 66; } [DebuggerDisplay("{" + nameof(Key) + "}")] @@ -29,6 +29,5 @@ public class DynamicKey [J("guid")] public string Guid { get; set; } [J("key")] public string Key { get; set; } - [I] public bool IsValid => !string.IsNullOrEmpty(Guid) && - !string.IsNullOrEmpty(Key); + [I] public bool IsValid => Guid.Length == 32 && Key.Length == 66; } diff --git a/FModel/ViewModels/ApiEndpoints/Models/MappingsResponse.cs b/FModel/ViewModels/ApiEndpoints/Models/MappingsResponse.cs index 23c1aba6..8ffc6c5b 100644 --- a/FModel/ViewModels/ApiEndpoints/Models/MappingsResponse.cs +++ b/FModel/ViewModels/ApiEndpoints/Models/MappingsResponse.cs @@ -22,8 +22,7 @@ public MappingsResponse() } [I] public bool IsValid => !string.IsNullOrEmpty(Url) && - !string.IsNullOrEmpty(FileName) && - Meta != null; + !string.IsNullOrEmpty(FileName); } [DebuggerDisplay("{" + nameof(CompressionMethod) + "}")] @@ -31,11 +30,4 @@ public class Meta { [I][J] public string Version { get; private set; } [J] public string CompressionMethod { get; set; } - - public Meta() - { - CompressionMethod = "Oodle"; - } - - [I] public bool IsValid => CompressionMethod == "Oodle"; } diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index c3d614c0..3fb70701 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -295,12 +295,12 @@ public async Task RefreshAes() { // game directory dependent, we don't have the provider game name yet since we don't have aes keys // except when this comes from the AES Manager - if (!UserSettings.IsEndpointEnabled(Game, EEndpointType.Aes, out var endpoint)) + if (!UserSettings.IsEndpointValid(Game, EEndpointType.Aes, out var endpoint)) return; await _threadWorkerView.Begin(cancellationToken => { - var aes = _apiEndpointView.DynamicApi.GetAesKeys(cancellationToken, endpoint.Url); + var aes = _apiEndpointView.DynamicApi.GetAesKeys(cancellationToken, endpoint.Url, endpoint.Path); if (aes is not { IsValid: true }) return; UserSettings.Default.AesKeys[Game] = aes; @@ -323,26 +323,26 @@ public async Task InitInformation() public async Task InitBenMappings() { - if (!UserSettings.IsEndpointEnabled(Game, EEndpointType.Mapping, out var endpoint)) + if (!UserSettings.IsEndpointValid(Game, EEndpointType.Mapping, out var endpoint)) return; await _threadWorkerView.Begin(cancellationToken => { - if (endpoint.Overwrite && File.Exists(endpoint.Path)) + if (endpoint.Overwrite && File.Exists(endpoint.FilePath)) { - Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(endpoint.Path); + Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(endpoint.FilePath); FLogger.AppendInformation(); - FLogger.AppendText($"Mappings pulled from '{endpoint.Path.SubstringAfterLast("\\")}'", Constants.WHITE, true); + FLogger.AppendText($"Mappings pulled from '{endpoint.FilePath.SubstringAfterLast("\\")}'", Constants.WHITE, true); } else { var mappingsFolder = Path.Combine(UserSettings.Default.OutputDirectory, ".data"); - var mappings = _apiEndpointView.DynamicApi.GetMappings(cancellationToken, endpoint.Url); + var mappings = _apiEndpointView.DynamicApi.GetMappings(cancellationToken, endpoint.Url, endpoint.Path); if (mappings is { Length: > 0 }) { foreach (var mapping in mappings) { - if (!mapping.IsValid || !mapping.Meta.IsValid) continue; + if (!mapping.IsValid) continue; var mappingPath = Path.Combine(mappingsFolder, mapping.FileName); if (!File.Exists(mappingPath)) diff --git a/FModel/ViewModels/SettingsViewModel.cs b/FModel/ViewModels/SettingsViewModel.cs index 8f0ccc2e..90997f37 100644 --- a/FModel/ViewModels/SettingsViewModel.cs +++ b/FModel/ViewModels/SettingsViewModel.cs @@ -324,12 +324,6 @@ public SettingsOut Save() UserSettings.Default.OverridedOptions[_game] = SelectedOptions; } - if (UserSettings.Default.CustomEndpoints.TryGetValue(_game, out var endpoints)) - { - endpoints[0] = AesEndpoint; - endpoints[1] = MappingEndpoint; - } - UserSettings.Default.AssetLanguage = SelectedAssetLanguage; UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio; UserSettings.Default.CosmeticStyle = SelectedCosmeticStyle; diff --git a/FModel/Views/Resources/Controls/EndpointEditor.xaml b/FModel/Views/Resources/Controls/EndpointEditor.xaml index 6843c062..1dd2d004 100644 --- a/FModel/Views/Resources/Controls/EndpointEditor.xaml +++ b/FModel/Views/Resources/Controls/EndpointEditor.xaml @@ -6,7 +6,7 @@ xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI" xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI" xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI" - WindowStartupLocation="CenterScreen" IconVisibility="Collapsed" ResizeMode="NoResize" + WindowStartupLocation="CenterScreen" IconVisibility="Collapsed" ResizeMode="NoResize" Closing="OnClosing" Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.50'}" Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.30'}"> @@ -23,38 +23,62 @@ - - - - - + + + + + - - - - - - - - + + + + + + + + + + + + + + + - - -