From 7e7d6d5bc6ad12ae83075dc706a6bb6a7fa1aa2d Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Tue, 31 Mar 2026 07:01:59 +1100 Subject: [PATCH] a bunch of stuff (#663) Co-authored-by: Chompster86 Co-authored-by: Asval --- CUE4Parse | 2 +- FModel/App.xaml.cs | 6 +++ FModel/Creator/Bases/FN/BaseAssembledMesh.cs | 47 +++++++++++++++++++ FModel/Creator/Bases/FN/BaseIconStats.cs | 10 ++++ FModel/Creator/CreatorPackage.cs | 5 ++ FModel/Resources/Cpp.xshd | 13 ++++- FModel/Settings/UserSettings.cs | 7 +++ .../ApiEndpoints/DillyApiEndpoints.cs | 14 ++++++ .../ApiEndpoints/Models/FModelResponse.cs | 7 +++ FModel/ViewModels/ApplicationViewModel.cs | 3 +- FModel/ViewModels/CUE4ParseViewModel.cs | 44 +++++++++++++---- .../Commands/RightClickMenuCommand.cs | 2 + FModel/ViewModels/SettingsViewModel.cs | 8 +--- FModel/ViewModels/TabControlViewModel.cs | 10 ++++ FModel/ViewModels/UpdateViewModel.cs | 5 +- .../ContextMenus/FolderContextMenu.xaml | 20 ++++++++ .../Resources/Controls/EndpointEditor.xaml.cs | 2 +- .../FileNameWithoutExtensionConverter.cs | 18 +++++++ .../Converters/StringToGameConverter.cs | 1 - .../Converters/TextToRefreshConverter.cs | 23 +++++++++ FModel/Views/Resources/Resources.xaml | 12 +++-- FModel/Views/SettingsView.xaml | 2 +- FModel/Views/SettingsView.xaml.cs | 3 ++ FModel/Views/UpdateView.xaml | 2 +- 24 files changed, 239 insertions(+), 27 deletions(-) create mode 100644 FModel/Creator/Bases/FN/BaseAssembledMesh.cs create mode 100644 FModel/Views/Resources/Converters/FileNameWithoutExtensionConverter.cs create mode 100644 FModel/Views/Resources/Converters/TextToRefreshConverter.cs diff --git a/CUE4Parse b/CUE4Parse index 1320fd09..8fc2c250 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 1320fd09f6373c997758ae8160d6f8035c4c8b93 +Subproject commit 8fc2c250ab0e581878813127610c28e49e947239 diff --git a/FModel/App.xaml.cs b/FModel/App.xaml.cs index c434def6..31da19b9 100644 --- a/FModel/App.xaml.cs +++ b/FModel/App.xaml.cs @@ -92,6 +92,12 @@ public partial class App UserSettings.Default.AudioDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports"); } + if (!Directory.Exists(UserSettings.Default.CodeDirectory)) + { + createMe = true; + UserSettings.Default.CodeDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports"); + } + if (!Directory.Exists(UserSettings.Default.ModelDirectory)) { createMe = true; diff --git a/FModel/Creator/Bases/FN/BaseAssembledMesh.cs b/FModel/Creator/Bases/FN/BaseAssembledMesh.cs new file mode 100644 index 00000000..830b90bf --- /dev/null +++ b/FModel/Creator/Bases/FN/BaseAssembledMesh.cs @@ -0,0 +1,47 @@ +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Assets.Objects; +using CUE4Parse.UE4.Objects.UObject; +using SkiaSharp; + +namespace FModel.Creator.Bases.FN; + +public class BaseAssembledMesh : UCreator +{ + public BaseAssembledMesh(UObject uObject, EIconStyle style) : base(uObject, style) + { + + } + + public override void ParseForInfo() + { + if (Object.TryGetValue(out FInstancedStruct[] additionalData, "AdditionalData")) + { + foreach (var data in additionalData) + { + if (data.NonConstStruct?.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "SmallPreviewImage") == true) + { + Preview = Utils.GetBitmap(largePreview); + } + } + } + } + + public override SKBitmap[] Draw() + { + var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + switch (Style) + { + case EIconStyle.NoBackground: + DrawPreview(c); + break; + default: + DrawBackground(c); + DrawPreview(c); + break; + } + + return new[] { ret }; + } +} diff --git a/FModel/Creator/Bases/FN/BaseIconStats.cs b/FModel/Creator/Bases/FN/BaseIconStats.cs index 13d2bfe1..d495d17d 100644 --- a/FModel/Creator/Bases/FN/BaseIconStats.cs +++ b/FModel/Creator/Bases/FN/BaseIconStats.cs @@ -87,6 +87,7 @@ public class BaseIconStats : BaseIcon weaponRowValue.TryGetValue(out float dmgPb, "DmgPB"); //Damage at point blank weaponRowValue.TryGetValue(out float mdpc, "MaxDamagePerCartridge"); //Max damage a weapon can do in a single hit, usually used for shotguns weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical"); //Headshot multiplier + weaponRowValue.TryGetValue(out float envDmgPb, "EnvDmgPB"); //Structure damage at point blank weaponRowValue.TryGetValue(out int clipSize, "ClipSize"); //Item magazine size weaponRowValue.TryGetValue(out float firingRate, "FiringRate"); //Item firing rate, value is shots per second weaponRowValue.TryGetValue(out float swingTime, "SwingTime"); //Item swing rate, value is swing per second @@ -115,6 +116,15 @@ public class BaseIconStats : BaseIcon _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), dmgPb * dmgCritical * multiplier, 160)); } } + { + var envdmgmultiplier = bpc != 0f ? bpc : 1; + if (envDmgPb != 0f) + + { + _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "11AF67134E0F4E27E5E588806AB475BE", "Structure Damage"), envDmgPb * envdmgmultiplier, 160)); + } + } + if (clipSize > 999f || clipSize == 0f) { _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), Utils.GetLocalizedResource("", "0FAE8E5445029F2AA209ADB0FE49B23C", "Infinite"), -1)); diff --git a/FModel/Creator/CreatorPackage.cs b/FModel/Creator/CreatorPackage.cs index 0fe8c317..b2b12284 100644 --- a/FModel/Creator/CreatorPackage.cs +++ b/FModel/Creator/CreatorPackage.cs @@ -100,12 +100,14 @@ public class CreatorPackage : IDisposable case "FortCodeTokenItemDefinition": case "FortSchematicItemDefinition": case "FortAlterableItemDefinition": + case "SproutHousingItemDefinition": case "SparksKeyboardItemDefinition": case "FortWorldMultiItemDefinition": case "FortAlterationItemDefinition": case "FortExpeditionItemDefinition": case "FortIngredientItemDefinition": case "FortConsumableItemDefinition": + case "SproutBuildingItemDefinition": case "StWFortAccoladeItemDefinition": case "FortAccountBuffItemDefinition": case "FortFOBCoreDecoItemDefinition": @@ -163,6 +165,9 @@ public class CreatorPackage : IDisposable case "JunoAthenaDanceItemOverrideDefinition": creator = new BaseJuno(_object.Value, _style); return true; + case "AssembledMeshSchema": + creator = new BaseAssembledMesh(_object.Value, _style); + return true; case "FortTandemCharacterData": creator = new BaseTandem(_object.Value, _style); return true; diff --git a/FModel/Resources/Cpp.xshd b/FModel/Resources/Cpp.xshd index f61b1264..89c82780 100644 --- a/FModel/Resources/Cpp.xshd +++ b/FModel/Resources/Cpp.xshd @@ -18,6 +18,7 @@ + (\/\/.*|\/\*[\s\S]*?\*\/) @@ -44,10 +45,19 @@ Int16 Int32 Int64 + int8 + int16 + int32 + int64 uint + UInt8 UInt16 UInt32 UInt64 + uint8 + uint16 + uint32 + uint64 float double bool @@ -83,6 +93,7 @@ inline constexpr default + && @@ -120,8 +131,6 @@ [\[\]\{\}] - (\/\/.*|\/\*[\s\S]*?\*\/) - \b[A-Za-z_][A-Za-z0-9_]*\b(?=<) diff --git a/FModel/Settings/UserSettings.cs b/FModel/Settings/UserSettings.cs index 44104264..bca1427d 100644 --- a/FModel/Settings/UserSettings.cs +++ b/FModel/Settings/UserSettings.cs @@ -119,6 +119,13 @@ namespace FModel.Settings set => SetProperty(ref _audioDirectory, value); } + private string _codeDirectory; + public string CodeDirectory + { + get => _codeDirectory; + set => SetProperty(ref _codeDirectory, value); + } + private string _modelDirectory; public string ModelDirectory { diff --git a/FModel/ViewModels/ApiEndpoints/DillyApiEndpoints.cs b/FModel/ViewModels/ApiEndpoints/DillyApiEndpoints.cs index 926061bc..630c0816 100644 --- a/FModel/ViewModels/ApiEndpoints/DillyApiEndpoints.cs +++ b/FModel/ViewModels/ApiEndpoints/DillyApiEndpoints.cs @@ -11,6 +11,7 @@ namespace FModel.ViewModels.ApiEndpoints; public class DillyApiEndpoint : AbstractApiProvider { private Backup[] _backups; + private ManifestInfoDilly[] _manifests; public DillyApiEndpoint(RestClient client) : base(client) { } @@ -27,6 +28,19 @@ public class DillyApiEndpoint : AbstractApiProvider return _backups ??= GetBackupsAsync(token).GetAwaiter().GetResult(); } + public async Task GetManifestsAsync(CancellationToken token) + { + var request = new FRestRequest($"https://export-service-new.dillyapis.com/v1/manifests"); + 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 ManifestInfoDilly[] GetManifests(CancellationToken token) + { + return _manifests ??= GetManifestsAsync(token).GetAwaiter().GetResult(); + } + public async Task>> GetHotfixesAsync(CancellationToken token, string language = "en") { var request = new FRestRequest("https://api.fortniteapi.com/v1/cloudstorage/hotfixes") diff --git a/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs b/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs index 67c6f6a4..598f070c 100644 --- a/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs +++ b/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs @@ -23,6 +23,13 @@ public class Backup [J] public string Url { get; private set; } } +[DebuggerDisplay("{" + nameof(AppName) + "}")] +public class ManifestInfoDilly +{ + [J] public string AppName { get; private set; } + [J] public string DownloadUrl { get; private set; } +} + public class Donator { [J] public string Username { get; private set; } diff --git a/FModel/ViewModels/ApplicationViewModel.cs b/FModel/ViewModels/ApplicationViewModel.cs index 51aa8bfe..42b24d90 100644 --- a/FModel/ViewModels/ApplicationViewModel.cs +++ b/FModel/ViewModels/ApplicationViewModel.cs @@ -104,7 +104,7 @@ public class ApplicationViewModel : ViewModel if (UserSettings.Default.CurrentDir is null) { //If no game is selected, many things will break before a shutdown request is processed in the normal way. - //A hard exit is preferable to an unhandled expection in this case + //A hard exit is preferable to an unhandled exception in this case Environment.Exit(0); } @@ -126,7 +126,6 @@ public class ApplicationViewModel : ViewModel if (sender is not IAesVfsReader reader) return; CUE4Parse.GameDirectory.Disable(reader); }; - CustomDirectories = new CustomDirectoriesViewModel(); SettingsView = new SettingsViewModel(); AesManager = new AesManagerViewModel(CUE4Parse); diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index e415ad62..55d6cd3c 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -225,14 +225,12 @@ public class CUE4ParseViewModel : ViewModel public async Task Initialize() { - await _apiEndpointView.EpicApi.VerifyAuth(CancellationToken.None); await _threadWorkerView.Begin(cancellationToken => { Provider.OnDemandOptions = new IoStoreOnDemandOptions { ChunkHostUri = new Uri("https://download.epicgames.com/", UriKind.Absolute), ChunkCacheDirectory = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data")), - Authorization = new AuthenticationHeaderValue("Bearer", UserSettings.Default.LastAuthResponse.AccessToken), Timeout = TimeSpan.FromSeconds(30) }; @@ -287,6 +285,20 @@ public class CUE4ParseViewModel : ViewModel it => new FRandomAccessStreamArchive(it, manifest.FindFile(it)!.GetStream(), p.Versions)); }); + var manifests = _apiEndpointView.DillyApi.GetManifests(cancellationToken); + var downloadUrl = manifests.First(x => x.AppName == "Fortnite_Studio").DownloadUrl; + + using var client = new HttpClient(); + var manifestBytes = client.GetByteArrayAsync(downloadUrl).GetAwaiter().GetResult(); + + var uefnManifest = FBuildPatchAppManifest.Deserialize(manifestBytes, manifestOptions); + + Parallel.ForEach(uefnManifest.Files.Where(x => _fnLiveRegex.IsMatch(x.FileName)), fileManifest => + { + p.RegisterVfs(fileManifest.FileName, [fileManifest.GetStream()], + it => new FRandomAccessStreamArchive(it, uefnManifest.FindFile(it)!.GetStream(), p.Versions)); + }); + var elapsedTime = Stopwatch.GetElapsedTime(startTs); FLogger.Append(ELog.Information, () => FLogger.Text($"Fortnite [LIVE] has been loaded successfully in {elapsedTime.TotalMilliseconds:F1}ms", Constants.WHITE, true)); @@ -622,6 +634,9 @@ public class CUE4ParseViewModel : ViewModel public void AudioFolder(CancellationToken cancellationToken, TreeItem folder) => BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Audio | EBulkType.Auto)); + public void CodeFolder(CancellationToken cancellationToken, TreeItem folder) + => BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Code | EBulkType.Auto)); + public void Extract(CancellationToken cancellationToken, GameFile entry, bool addNewTab = false, EBulkType bulk = EBulkType.None) { ApplicationService.ApplicationView.IsAssetsExplorerVisible = false; @@ -635,6 +650,7 @@ public class CUE4ParseViewModel : ViewModel var saveProperties = HasFlag(bulk, EBulkType.Properties); var saveTextures = HasFlag(bulk, EBulkType.Textures); var saveAudio = HasFlag(bulk, EBulkType.Audio); + var saveDecompiled = HasFlag(bulk, EBulkType.Code); switch (entry.Extension) { case "uasset": @@ -649,6 +665,13 @@ public class CUE4ParseViewModel : ViewModel if (saveProperties) break; // do not search for viewable exports if we are dealing with jsons } + if (saveDecompiled) + { + if (Decompile(entry, false)) + TabControl.SelectedTab.SaveDecompiled(updateUi); + break; + } + for (var i = result.InclusiveStart; i < result.ExclusiveEnd; i++) { if (CheckExport(cancellationToken, result.Package, i, bulk)) @@ -1365,11 +1388,13 @@ public class CUE4ParseViewModel : ViewModel } - public void Decompile(GameFile entry) + public bool Decompile(GameFile entry, bool AddTab = true) { - ApplicationService.ApplicationView.IsAssetsExplorerVisible = false; - - if (TabControl.CanAddTabs) TabControl.AddTab(entry); + if (TabControl.CanAddTabs && AddTab) + { + ApplicationService.ApplicationView.IsAssetsExplorerVisible = false; + TabControl.AddTab(entry); + } else TabControl.SelectedTab.SoftReset(entry); TabControl.SelectedTab.TitleExtra = "Decompiled"; @@ -1398,18 +1423,21 @@ public class CUE4ParseViewModel : ViewModel if (dummy is not UClass || pointer.Object.Value is not UClass blueprint) continue; - cppList.Add(blueprint.DecompileBlueprintToPseudo(cookedMetaData)); + cppList.Add(blueprint.DecompileBlueprintToPseudo(pkg.Mappings, cookedMetaData)); } + if (cppList.Count == 0) return false; var cpp = cppList.Count > 1 ? string.Join("\n\n", cppList) : cppList.FirstOrDefault() ?? string.Empty; if (entry.Path.Contains("_Verse.uasset")) { cpp = Regex.Replace(cpp, "__verse_0x[a-fA-F0-9]{8}_", ""); // UnmangleCasedName } cpp = Regex.Replace(cpp, @"CallFunc_([A-Za-z0-9_]+)_ReturnValue", "$1"); - + cpp = Regex.Replace(cpp, @"K2Node_DynamicCast_([A-Za-z0-9_]+)", "$1"); + cpp = Regex.Replace(cpp, @"K2Node_([A-Za-z0-9_]+)", "$1"); TabControl.SelectedTab.SetDocumentText(cpp, false, false); + return true; } private void SaveAndPlaySound(CancellationToken cancellationToken, string fullPath, string ext, byte[] data, bool saveAudio, bool updateUi) diff --git a/FModel/ViewModels/Commands/RightClickMenuCommand.cs b/FModel/ViewModels/Commands/RightClickMenuCommand.cs index 6731918d..b490957c 100644 --- a/FModel/ViewModels/Commands/RightClickMenuCommand.cs +++ b/FModel/ViewModels/Commands/RightClickMenuCommand.cs @@ -69,6 +69,7 @@ public class RightClickMenuCommand : ViewModelCommand "Save_Models" => (EAction.Export, EShowAssetType.None, EBulkType.Meshes), "Save_Animations" => (EAction.Export, EShowAssetType.None, EBulkType.Animations), "Save_Audio" => (EAction.Export, EShowAssetType.None, EBulkType.Audio), + "Save_Code" => (EAction.Export, EShowAssetType.None, EBulkType.Code), _ => throw new ArgumentOutOfRangeException("Unsupported asset action."), }; @@ -109,6 +110,7 @@ public class RightClickMenuCommand : ViewModelCommand EBulkType.Meshes => (UserSettings.Default.ModelDirectory, "models"), EBulkType.Animations => (UserSettings.Default.ModelDirectory, "animations"), EBulkType.Audio => (UserSettings.Default.AudioDirectory, "audio files"), + EBulkType.Code => (UserSettings.Default.CodeDirectory, "code files"), _ => (null, null), }; diff --git a/FModel/ViewModels/SettingsViewModel.cs b/FModel/ViewModels/SettingsViewModel.cs index becbf3a2..626f4227 100644 --- a/FModel/ViewModels/SettingsViewModel.cs +++ b/FModel/ViewModels/SettingsViewModel.cs @@ -195,6 +195,7 @@ public class SettingsViewModel : ViewModel private string _propertiesSnapshot; private string _textureSnapshot; private string _audioSnapshot; + private string _codeSnapshot; private string _modelSnapshot; private string _gameSnapshot; private ETexturePlatform _uePlatformSnapshot; @@ -227,6 +228,7 @@ public class SettingsViewModel : ViewModel _propertiesSnapshot = UserSettings.Default.PropertiesDirectory; _textureSnapshot = UserSettings.Default.TextureDirectory; _audioSnapshot = UserSettings.Default.AudioDirectory; + _codeSnapshot = UserSettings.Default.CodeDirectory; _modelSnapshot = UserSettings.Default.ModelDirectory; _gameSnapshot = UserSettings.Default.GameDirectory; _uePlatformSnapshot = UserSettings.Default.CurrentDir.TexturePlatform; @@ -303,12 +305,6 @@ public class SettingsViewModel : ViewModel if (_ueGameSnapshot != SelectedUeGame || _customVersionsSnapshot != SelectedCustomVersions || _uePlatformSnapshot != SelectedUePlatform || _optionsSnapshot != SelectedOptions || // combobox _mapStructTypesSnapshot != SelectedMapStructTypes || - _outputSnapshot != UserSettings.Default.OutputDirectory || // textbox - _rawDataSnapshot != UserSettings.Default.RawDataDirectory || // textbox - _propertiesSnapshot != UserSettings.Default.PropertiesDirectory || // textbox - _textureSnapshot != UserSettings.Default.TextureDirectory || // textbox - _audioSnapshot != UserSettings.Default.AudioDirectory || // textbox - _modelSnapshot != UserSettings.Default.ModelDirectory || // textbox _gameSnapshot != UserSettings.Default.GameDirectory) // textbox restart = true; diff --git a/FModel/ViewModels/TabControlViewModel.cs b/FModel/ViewModels/TabControlViewModel.cs index 748dd1ae..dbeb4423 100644 --- a/FModel/ViewModels/TabControlViewModel.cs +++ b/FModel/ViewModels/TabControlViewModel.cs @@ -409,7 +409,17 @@ public class TabItem : ViewModel Application.Current.Dispatcher.Invoke(() => File.WriteAllText(directory, Document.Text)); SaveCheck(directory, fileName, updateUi); } + public void SaveDecompiled(bool updateUi) + { + var fileName = Path.ChangeExtension(Entry.Name, ".cpp"); + var directory = Path.Combine(UserSettings.Default.PropertiesDirectory, + UserSettings.Default.KeepDirectoryStructure ? Entry.Directory : "", fileName).Replace('\\', '/'); + Directory.CreateDirectory(directory.SubstringBeforeLast('/')); + + Application.Current.Dispatcher.Invoke(() => File.WriteAllText(directory, Document.Text)); + SaveCheck(directory, fileName, updateUi); + } private void SaveCheck(string path, string fileName, bool updateUi) { if (File.Exists(path)) diff --git a/FModel/ViewModels/UpdateViewModel.cs b/FModel/ViewModels/UpdateViewModel.cs index adbc79bd..26acdb88 100644 --- a/FModel/ViewModels/UpdateViewModel.cs +++ b/FModel/ViewModels/UpdateViewModel.cs @@ -81,6 +81,9 @@ public partial class UpdateViewModel : ViewModel if (username.Equals("Asval", StringComparison.OrdinalIgnoreCase)) { username = "4sval"; // found out the hard way co-authored usernames can't be trusted + } else if (username.Equals("Krowe Moh", StringComparison.OrdinalIgnoreCase)) + { + username = "Krowe-moh"; } coAuthorMap[commit].Add(username); @@ -101,7 +104,7 @@ public partial class UpdateViewModel : ViewModel } catch { - // + // Ignore } } diff --git a/FModel/Views/Resources/Controls/ContextMenus/FolderContextMenu.xaml b/FModel/Views/Resources/Controls/ContextMenus/FolderContextMenu.xaml index ca5a5871..26aa4074 100644 --- a/FModel/Views/Resources/Controls/ContextMenus/FolderContextMenu.xaml +++ b/FModel/Views/Resources/Controls/ContextMenus/FolderContextMenu.xaml @@ -65,6 +65,26 @@ + + + + + + + + + + + + + + + diff --git a/FModel/Views/Resources/Controls/EndpointEditor.xaml.cs b/FModel/Views/Resources/Controls/EndpointEditor.xaml.cs index 215010d1..f875d263 100644 --- a/FModel/Views/Resources/Controls/EndpointEditor.xaml.cs +++ b/FModel/Views/Resources/Controls/EndpointEditor.xaml.cs @@ -93,7 +93,7 @@ public partial class EndpointEditor private void OnEvaluator(object sender, RoutedEventArgs e) { - Process.Start(new ProcessStartInfo { FileName = "https://jsonpath.herokuapp.com/", UseShellExecute = true }); + Process.Start(new ProcessStartInfo { FileName = "https://jsonpath.com/", UseShellExecute = true }); } } diff --git a/FModel/Views/Resources/Converters/FileNameWithoutExtensionConverter.cs b/FModel/Views/Resources/Converters/FileNameWithoutExtensionConverter.cs new file mode 100644 index 00000000..c1fac76f --- /dev/null +++ b/FModel/Views/Resources/Converters/FileNameWithoutExtensionConverter.cs @@ -0,0 +1,18 @@ +using System; +using System.Globalization; +using System.IO; +using System.Windows.Data; + +namespace FModel.Views.Resources.Converters +{ + public class FileNameWithoutExtensionConverter : IValueConverter + { + public static readonly FileNameWithoutExtensionConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + => value is string s ? Path.GetFileNameWithoutExtension(s) : value; + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + => throw new NotImplementedException(); + } +} diff --git a/FModel/Views/Resources/Converters/StringToGameConverter.cs b/FModel/Views/Resources/Converters/StringToGameConverter.cs index 70181335..dbd01a96 100644 --- a/FModel/Views/Resources/Converters/StringToGameConverter.cs +++ b/FModel/Views/Resources/Converters/StringToGameConverter.cs @@ -1,7 +1,6 @@ using System; using System.Globalization; using System.Windows.Data; -using FModel.Extensions; namespace FModel.Views.Resources.Converters; diff --git a/FModel/Views/Resources/Converters/TextToRefreshConverter.cs b/FModel/Views/Resources/Converters/TextToRefreshConverter.cs new file mode 100644 index 00000000..0f3f8ec0 --- /dev/null +++ b/FModel/Views/Resources/Converters/TextToRefreshConverter.cs @@ -0,0 +1,23 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace FModel.Views.Resources.Converters; + +public class TextToRefreshConverter : IValueConverter +{ + public static readonly TextToRefreshConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is DateTime dt && dt != DateTime.MaxValue) + return $"Next Refresh: {dt:MMM d, yyyy}"; + + return "Next Refresh: Never"; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} diff --git a/FModel/Views/Resources/Resources.xaml b/FModel/Views/Resources/Resources.xaml index 715a5b1f..c33eb148 100644 --- a/FModel/Views/Resources/Resources.xaml +++ b/FModel/Views/Resources/Resources.xaml @@ -771,9 +771,15 @@ - + + + + diff --git a/FModel/Views/SettingsView.xaml b/FModel/Views/SettingsView.xaml index 965eef13..b38b62d5 100644 --- a/FModel/Views/SettingsView.xaml +++ b/FModel/Views/SettingsView.xaml @@ -695,7 +695,7 @@ -