diff --git a/FModel/Creator/Bases/MV/BasePandaIcon.cs b/FModel/Creator/Bases/MV/BasePandaIcon.cs index a52a9bde..0f139b03 100644 --- a/FModel/Creator/Bases/MV/BasePandaIcon.cs +++ b/FModel/Creator/Bases/MV/BasePandaIcon.cs @@ -212,12 +212,12 @@ public class BasePandaIcon : UCreator return; var x = 450f; - var y = Height / 2 - DisplayNamePaint.TextSize / 4; - while (DisplayNamePaint.MeasureText(DisplayName) > Width - x) + while (DisplayNamePaint.MeasureText(DisplayName) > Width - x / 1.25) { DisplayNamePaint.TextSize -= 1; } + var y = Height / 2 - DisplayNamePaint.TextSize / 4; foreach (var a in DisplayName.Select(character => character.ToString())) { c.DrawText(a, x, y, DisplayNamePaint); diff --git a/FModel/Enums.cs b/FModel/Enums.cs index f5b29a98..b99682d6 100644 --- a/FModel/Enums.cs +++ b/FModel/Enums.cs @@ -141,3 +141,9 @@ public enum EIconStyle // [Description("Community")] // CommunityMade } + +public enum EEndpointType +{ + Aes, + Mapping +} diff --git a/FModel/Framework/FEndpoint.cs b/FModel/Framework/FEndpoint.cs new file mode 100644 index 00000000..f5edc115 --- /dev/null +++ b/FModel/Framework/FEndpoint.cs @@ -0,0 +1,33 @@ +namespace FModel.Framework; + +public class FEndpoint : ViewModel +{ + private string _url; + public string Url + { + get => _url; + set => SetProperty(ref _url, value); + } + + private bool _overwrite; + public bool Overwrite + { + get => _overwrite; + set => SetProperty(ref _overwrite, value); + } + + private string _path; + public string Path + { + get => _path; + set => SetProperty(ref _path, value); + } + + public bool IsEnabled => !string.IsNullOrWhiteSpace(_url); // change this later + + public FEndpoint() {} + public FEndpoint(string url) + { + Url = url; + } +} diff --git a/FModel/Settings/UserSettings.cs b/FModel/Settings/UserSettings.cs index 82d238b1..640dce08 100644 --- a/FModel/Settings/UserSettings.cs +++ b/FModel/Settings/UserSettings.cs @@ -39,6 +39,16 @@ namespace FModel.Settings if (File.Exists(FilePath)) File.Delete(FilePath); } + public static bool TryGetGameCustomEndpoint(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; + } + private bool _showChangelog = true; public bool ShowChangelog { @@ -95,20 +105,6 @@ namespace FModel.Settings set => SetProperty(ref _gameDirectory, value); } - private bool _overwriteMapping; - public bool OverwriteMapping - { - get => _overwriteMapping; - set => SetProperty(ref _overwriteMapping, value); - } - - private string _mappingFilePath; - public string MappingFilePath - { - get => _mappingFilePath; - set => SetProperty(ref _mappingFilePath, value); - } - private int _lastOpenedSettingTab; public int LastOpenedSettingTab { @@ -382,6 +378,41 @@ namespace FModel.Settings set => SetProperty(ref _overridedOptions, value); } + private IDictionary _customEndpoints = new Dictionary + { + {FGame.Unknown, new FEndpoint[]{new (), new ()}}, + { + FGame.FortniteGame, new [] + { + new FEndpoint("https://fortnitecentral.gmatrixgames.ga/api/v1/aes"), + new FEndpoint("https://fortnitecentral.gmatrixgames.ga/api/v1/mappings") + } + }, + {FGame.ShooterGame, new FEndpoint[]{new (), new ()}}, + {FGame.DeadByDaylight, new FEndpoint[]{new (), new ()}}, + {FGame.OakGame, new FEndpoint[]{new (), new ()}}, + {FGame.Dungeons, new FEndpoint[]{new (), new ()}}, + {FGame.WorldExplorers, new FEndpoint[]{new (), new ()}}, + {FGame.g3, new FEndpoint[]{new (), new ()}}, + {FGame.StateOfDecay2, new FEndpoint[]{new (), new ()}}, + {FGame.Prospect, new FEndpoint[]{new (), new ()}}, + {FGame.Indiana, new FEndpoint[]{new (), new ()}}, + {FGame.RogueCompany, new FEndpoint[]{new (), new ()}}, + {FGame.SwGame, new FEndpoint[]{new (), new ()}}, + {FGame.Platform, new FEndpoint[]{new (), new ()}}, + {FGame.BendGame, new FEndpoint[]{new (), new ()}}, + {FGame.TslGame, new FEndpoint[]{new (), new ()}}, + {FGame.PortalWars, new FEndpoint[]{new (), new ()}}, + {FGame.Gameface, new FEndpoint[]{new (), new ()}}, + {FGame.Athena, new FEndpoint[]{new (), new ()}}, + {FGame.PandaGame, new FEndpoint[]{new (), new ()}} + }; + public IDictionary CustomEndpoints + { + get => _customEndpoints; + set => SetProperty(ref _customEndpoints, value); + } + private IDictionary> _customDirectories = new Dictionary> { {FGame.Unknown, new List()}, diff --git a/FModel/ViewModels/ApiEndpointViewModel.cs b/FModel/ViewModels/ApiEndpointViewModel.cs index f062266f..c880b891 100644 --- a/FModel/ViewModels/ApiEndpointViewModel.cs +++ b/FModel/ViewModels/ApiEndpointViewModel.cs @@ -22,7 +22,8 @@ public class ApiEndpointViewModel public ValorantApiEndpoint ValorantApi { get; } public FortniteCentralApiEndpoint CentralApi { get; } public EpicApiEndpoint EpicApi { get; } - public FModelApi FModelApi { get; } + public FModelApiEndpoint FModelApi { get; } + public DynamicApiEndpoint DynamicApi { get; } public ApiEndpointViewModel() { @@ -30,7 +31,8 @@ public class ApiEndpointViewModel ValorantApi = new ValorantApiEndpoint(_client); CentralApi = new FortniteCentralApiEndpoint(_client); EpicApi = new EpicApiEndpoint(_client); - FModelApi = new FModelApi(_client); + FModelApi = new FModelApiEndpoint(_client); + DynamicApi = new DynamicApiEndpoint(_client); } public async Task DownloadFileAsync(string fileLink, string installationPath) diff --git a/FModel/ViewModels/ApiEndpoints/DynamicApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/DynamicApiEndpoint.cs new file mode 100644 index 00000000..3c2244e5 --- /dev/null +++ b/FModel/ViewModels/ApiEndpoints/DynamicApiEndpoint.cs @@ -0,0 +1,47 @@ +using System.Threading; +using System.Threading.Tasks; +using FModel.Framework; +using FModel.ViewModels.ApiEndpoints.Models; +using RestSharp; +using Serilog; + +namespace FModel.ViewModels.ApiEndpoints; + +public class DynamicApiEndpoint : AbstractApiProvider +{ + public DynamicApiEndpoint(RestClient client) : base(client) + { + } + + public async Task GetAesKeysAsync(CancellationToken token, string url) + { + var request = new FRestRequest(url) + { + OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; } + }; + var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); + Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString); + return response.Data; + } + + public AesResponse GetAesKeys(CancellationToken token, string url) + { + return GetAesKeysAsync(token, url).GetAwaiter().GetResult(); + } + + public async Task GetMappingsAsync(CancellationToken token, string url) + { + var request = new FRestRequest(url) + { + OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; } + }; + var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); + Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString); + return response.Data; + } + + public MappingsResponse[] GetMappings(CancellationToken token, string url) + { + return GetMappingsAsync(token, url).GetAwaiter().GetResult(); + } +} diff --git a/FModel/ViewModels/ApiEndpoints/FModelApi.cs b/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs similarity index 98% rename from FModel/ViewModels/ApiEndpoints/FModelApi.cs rename to FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs index b0b5c2ba..bbd4ec09 100644 --- a/FModel/ViewModels/ApiEndpoints/FModelApi.cs +++ b/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs @@ -20,7 +20,7 @@ using MessageBoxResult = AdonisUI.Controls.MessageBoxResult; namespace FModel.ViewModels.ApiEndpoints; -public class FModelApi : AbstractApiProvider +public class FModelApiEndpoint : AbstractApiProvider { private News _news; private Info _infos; @@ -29,7 +29,7 @@ public class FModelApi : AbstractApiProvider private readonly IDictionary _communityDesigns = new Dictionary(); private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; - public FModelApi(RestClient client) : base(client) + public FModelApiEndpoint(RestClient client) : base(client) { } diff --git a/FModel/ViewModels/ApiEndpoints/FortniteCentralApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/FortniteCentralApiEndpoint.cs index 2ebb7d22..e5fee91a 100644 --- a/FModel/ViewModels/ApiEndpoints/FortniteCentralApiEndpoint.cs +++ b/FModel/ViewModels/ApiEndpoints/FortniteCentralApiEndpoint.cs @@ -2,7 +2,6 @@ using System.Threading; using System.Threading.Tasks; using FModel.Framework; -using FModel.ViewModels.ApiEndpoints.Models; using RestSharp; using Serilog; @@ -14,38 +13,6 @@ public class FortniteCentralApiEndpoint : AbstractApiProvider { } - public async Task GetAesKeysAsync(CancellationToken token) - { - var request = new FRestRequest("https://fortnitecentral.gmatrixgames.ga/api/v1/aes") - { - OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; } - }; - var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); - Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString); - return response.Data; - } - - public AesResponse GetAesKeys(CancellationToken token) - { - return GetAesKeysAsync(token).GetAwaiter().GetResult(); - } - - public async Task GetMappingsAsync(CancellationToken token) - { - var request = new FRestRequest("https://fortnitecentral.gmatrixgames.ga/api/v1/mappings") - { - OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; } - }; - var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); - Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString); - return response.Data; - } - - public MappingsResponse[] GetMappings(CancellationToken token) - { - return GetMappingsAsync(token).GetAwaiter().GetResult(); - } - public async Task>> GetHotfixesAsync(CancellationToken token, string language = "en") { var request = new FRestRequest("https://fortnitecentral.gmatrixgames.ga/api/v1/hotfixes") diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index c1eeb273..2ba50435 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -293,16 +293,18 @@ public class CUE4ParseViewModel : ViewModel public async Task RefreshAes() { - if (Game == FGame.FortniteGame) // game directory dependent, we don't have the provider game name yet since we don't have aes keys - { - await _threadWorkerView.Begin(cancellationToken => - { - var aes = _apiEndpointView.CentralApi.GetAesKeys(cancellationToken); - if (aes?.MainKey == null && aes?.DynamicKeys == null && aes?.Version == null) return; + // 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.TryGetGameCustomEndpoint(Game, EEndpointType.Aes, out var endpoint)) + return; - UserSettings.Default.AesKeys[Game] = aes; - }); - } + await _threadWorkerView.Begin(cancellationToken => + { + var aes = _apiEndpointView.DynamicApi.GetAesKeys(cancellationToken, endpoint.Url); + if (aes?.MainKey == null && aes?.DynamicKeys == null) return; + + UserSettings.Default.AesKeys[Game] = aes; + }); } public async Task InitInformation() @@ -321,20 +323,21 @@ public class CUE4ParseViewModel : ViewModel public async Task InitBenMappings() { - if (Game != FGame.FortniteGame) return; + if (!UserSettings.TryGetGameCustomEndpoint(Game, EEndpointType.Mapping, out var endpoint)) + return; await _threadWorkerView.Begin(cancellationToken => { - if (UserSettings.Default.OverwriteMapping && File.Exists(UserSettings.Default.MappingFilePath)) + if (endpoint.Overwrite && File.Exists(endpoint.Path)) { - Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(UserSettings.Default.MappingFilePath); + Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(endpoint.Path); FLogger.AppendInformation(); - FLogger.AppendText($"Mappings pulled from '{UserSettings.Default.MappingFilePath.SubstringAfterLast("\\")}'", Constants.WHITE, true); + FLogger.AppendText($"Mappings pulled from '{endpoint.Path.SubstringAfterLast("\\")}'", Constants.WHITE, true); } else { var mappingsFolder = Path.Combine(UserSettings.Default.OutputDirectory, ".data"); - var mappings = _apiEndpointView.CentralApi.GetMappings(cancellationToken); + var mappings = _apiEndpointView.DynamicApi.GetMappings(cancellationToken, endpoint.Url); if (mappings is { Length: > 0 }) { foreach (var mapping in mappings) diff --git a/FModel/ViewModels/SettingsViewModel.cs b/FModel/ViewModels/SettingsViewModel.cs index 99d5171d..3d12be19 100644 --- a/FModel/ViewModels/SettingsViewModel.cs +++ b/FModel/ViewModels/SettingsViewModel.cs @@ -96,6 +96,13 @@ public class SettingsViewModel : ViewModel set => SetProperty(ref _selectedCompressedAudio, value); } + private string _currentMappingFile; + public string CurrentMappingFile // only used so that it updates the UI + { + get => _currentMappingFile; + set => SetProperty(ref _currentMappingFile, value); + } + private EIconStyle _selectedCosmeticStyle; public EIconStyle SelectedCosmeticStyle { @@ -191,6 +198,9 @@ public class SettingsViewModel : ViewModel _optionsSnapshot = UserSettings.Default.OverridedOptions[_game]; } + if (UserSettings.TryGetGameCustomEndpoint(_game, EEndpointType.Mapping, out var endpoint)) + CurrentMappingFile = endpoint.Path; + _assetLanguageSnapshot = UserSettings.Default.AssetLanguage; _compressedAudioSnapshot = UserSettings.Default.CompressedAudioMode; _cosmeticStyleSnapshot = UserSettings.Default.CosmeticStyle; diff --git a/FModel/Views/AesManager.xaml b/FModel/Views/AesManager.xaml index 8b5e8429..442fd17c 100644 --- a/FModel/Views/AesManager.xaml +++ b/FModel/Views/AesManager.xaml @@ -81,16 +81,13 @@ HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="OK" Click="OnClick" /> diff --git a/FModel/Views/Resources/Converters/EndpointOverwriteToBoolConverter.cs b/FModel/Views/Resources/Converters/EndpointOverwriteToBoolConverter.cs new file mode 100644 index 00000000..6fdd553c --- /dev/null +++ b/FModel/Views/Resources/Converters/EndpointOverwriteToBoolConverter.cs @@ -0,0 +1,52 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; +using FModel.Framework; +using FModel.Settings; +using FModel.ViewModels; + +namespace FModel.Views.Resources.Converters; + +public class EndpointOverwriteToBoolConverter : IMultiValueConverter +{ + public static readonly EndpointOverwriteToBoolConverter Instance = new(); + + private ApplicationViewModel _vm; + private EEndpointType _type; + private FEndpoint _endpoint; + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + _vm = values[0] as ApplicationViewModel; + _type = values[1] as EEndpointType? ?? EEndpointType.Mapping; + if (_vm == null || !UserSettings.TryGetGameCustomEndpoint(_vm.CUE4Parse.Game, _type, out _endpoint)) + return default; + + return targetType switch + { + not null when targetType == typeof(bool?) => _endpoint.Overwrite, // IsChecked + not null when targetType == typeof(string) => _endpoint.Path, + not null when targetType == typeof(Visibility) => _endpoint.Overwrite ? Visibility.Visible : Visibility.Collapsed, + _ => throw new NotImplementedException() + }; + } + + public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture) + { + var t = value.GetType(); + switch (t) + { + case not null when t == typeof(bool): + _endpoint.Overwrite = (bool)value; + break; + case not null when t == typeof(string): + _endpoint.Path = (string)value; + break; + default: + throw new NotImplementedException(); + } + + return new object[] { _vm, _type }; + } +} diff --git a/FModel/Views/Resources/Converters/EndpointToTypeConverter.cs b/FModel/Views/Resources/Converters/EndpointToTypeConverter.cs new file mode 100644 index 00000000..730a8d22 --- /dev/null +++ b/FModel/Views/Resources/Converters/EndpointToTypeConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; +using FModel.Settings; +using FModel.ViewModels; + +namespace FModel.Views.Resources.Converters; + +public class EndpointToTypeConverter : IMultiValueConverter +{ + public static readonly EndpointToTypeConverter Instance = new(); + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values[0] is not ApplicationViewModel viewModel || + values[1] is not EEndpointType type) + return false; + + var isEnabled = UserSettings.TryGetGameCustomEndpoint(viewModel.CUE4Parse.Game, type, out _); + return targetType switch + { + not null when targetType == typeof(Visibility) => isEnabled ? Visibility.Visible : Visibility.Collapsed, + _ => throw new NotImplementedException() + }; + } + + public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} diff --git a/FModel/Views/SettingsView.xaml b/FModel/Views/SettingsView.xaml index 39b43582..0a9ffa2d 100644 --- a/FModel/Views/SettingsView.xaml +++ b/FModel/Views/SettingsView.xaml @@ -41,6 +41,7 @@ + @@ -130,17 +131,6 @@ DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" SelectionChanged="OnSelectionChanged" Margin="0 0 0 5"> - - - - - - - - - + + + + + + + + + @@ -174,31 +175,47 @@ - - - - + + + + + + + + + + + + + + + + + + - - - - + + + + + + + @@ -206,90 +223,46 @@ - - + - - - - + + + + + + + + - - - - + + + + + + + + + + + + + + + - - - - + - - - - - - + +