From b88109ba09b99fb72563870bb5e1f4eebcafb4ee Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Fri, 31 Jul 2026 15:44:29 +1000 Subject: [PATCH 01/17] Added "Cloud" Archives --- FModel/MainWindow.xaml.cs | 1 + .../ApiEndpoints/Models/FModelResponse.cs | 5 ++ FModel/ViewModels/CUE4ParseViewModel.cs | 59 +++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index fbed15d40..b9b79b379 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -116,6 +116,7 @@ await Task.WhenAll( await Task.WhenAll( _applicationView.CUE4Parse.VerifyConsoleVariables(), _applicationView.CUE4Parse.VerifyOnDemandArchives(), + _applicationView.CUE4Parse.VerifyCloudArchives(), _applicationView.CUE4Parse.InitMappings(), ApplicationViewModel.InitDetex(), ApplicationViewModel.InitVgmStream(), diff --git a/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs b/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs index 598f070c3..d9fd084f6 100644 --- a/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs +++ b/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs @@ -30,6 +30,11 @@ public class ManifestInfoDilly [J] public string DownloadUrl { get; private set; } } +public class CloudContent +{ + public string ManifestPath { get; private set; } +} + public class Donator { [J] public string Username { get; private set; } diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 07772cc34..f7155852a 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -75,6 +75,7 @@ using FModel.Framework; using FModel.Services; using FModel.Settings; +using FModel.ViewModels.ApiEndpoints.Models; using FModel.Views; using FModel.Views.Resources.Controls; using FModel.Views.Snooper; @@ -88,6 +89,7 @@ using UE4Config.Parsing; using Application = System.Windows.Application; using FGuid = CUE4Parse.UE4.Objects.Core.Misc.FGuid; +using Version = System.Version; namespace FModel.ViewModels; @@ -372,6 +374,22 @@ private void RegisterFortniteLiveArchives(StreamedFileProvider provider, FBuildP } } + private void RegisterArchivesFromManifest(DefaultFileProvider provider, FBuildPatchAppManifest manifest) + { + var archiveFiles = manifest.Files.Where(x => + _fnLiveRegex.IsMatch(x.FileName) && + (x.FileName.EndsWith(".pak", StringComparison.OrdinalIgnoreCase) || + x.FileName.EndsWith(".utoc", StringComparison.OrdinalIgnoreCase) || + x.FileName.EndsWith(".uondemandtoc", StringComparison.OrdinalIgnoreCase))).ToList(); + + Parallel.ForEach(archiveFiles.Where(x => !x.FileName.EndsWith(".uondemandtoc", StringComparison.OrdinalIgnoreCase)), fileManifest => + { + provider.RegisterVfs(fileManifest.FileName, [fileManifest.GetStream()], + it => new FRandomAccessStreamArchive(it, manifest.FindFile(it)!.GetStream(), provider.Versions)); + }); + } + + /// /// load virtual files system from GameDirectory /// @@ -551,6 +569,47 @@ public Task VerifyOnDemandArchives() }); } + public Task VerifyCloudArchives() + { + if (Provider is not DefaultFileProvider p || !Provider.ProjectName.Equals("FortniteGame", StringComparison.OrdinalIgnoreCase)) + return Task.CompletedTask; + + var cloudContentPath = Path.Combine(UserSettings.Default.GameDirectory, "..\\..\\..\\Cloud\\cloudcontent.json"); + if (!File.Exists(cloudContentPath)) + return Task.CompletedTask; + + return Task.Run(async () => + { + var startTs = Stopwatch.GetTimestamp(); + + var cloudContent = JsonConvert.DeserializeObject(await File.ReadAllTextAsync(cloudContentPath)); + if (cloudContent is null || string.IsNullOrEmpty(cloudContent.ManifestPath)) + return; + + var manifestBytes = await _chunkClient.GetByteArrayAsync("https://egdownload.fastly-edge.com/" + cloudContent.ManifestPath); + + var manifestOptions = new ManifestParseOptions + { + ChunkCacheDirectory = CacheManager.ChunksDirectory, + ManifestCacheDirectory = CacheManager.ManifestsDirectory, + ChunkBaseUrl = "https://egdownload.fastly-edge.com/Builds/Fortnite/CloudDir/", + Decompressor = Compression.Decompressor, + Client = _chunkClient, + CacheChunksAsIs = false + }; + + var contentManifest = FBuildPatchAppManifest.Deserialize(manifestBytes, manifestOptions); + + RegisterArchivesFromManifest(p, contentManifest); + + var cloudCount = await Provider.MountAsync(); + var elapsedTime = Stopwatch.GetElapsedTime(startTs); + + FLogger.Append(ELog.Information, () => + FLogger.Text($"{cloudCount} cloud archive{(cloudCount > 1 ? "s" : "")} streamed via epicgames.com in {elapsedTime.TotalMilliseconds:F1}ms", Constants.WHITE, true)); + }); + } + public int LocalizedResourcesCount { get; set; } public bool LocalResourcesDone { get; set; } public bool HotfixedResourcesDone { get; set; } From c1c578a818ee139cec0f4ad7ed39ff9e911d1a04 Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Fri, 31 Jul 2026 15:45:39 +1000 Subject: [PATCH 02/17] useless check --- FModel/ViewModels/CUE4ParseViewModel.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index f7155852a..7b22009db 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -379,10 +379,9 @@ private void RegisterArchivesFromManifest(DefaultFileProvider provider, FBuildPa var archiveFiles = manifest.Files.Where(x => _fnLiveRegex.IsMatch(x.FileName) && (x.FileName.EndsWith(".pak", StringComparison.OrdinalIgnoreCase) || - x.FileName.EndsWith(".utoc", StringComparison.OrdinalIgnoreCase) || - x.FileName.EndsWith(".uondemandtoc", StringComparison.OrdinalIgnoreCase))).ToList(); + x.FileName.EndsWith(".utoc", StringComparison.OrdinalIgnoreCase))).ToList(); - Parallel.ForEach(archiveFiles.Where(x => !x.FileName.EndsWith(".uondemandtoc", StringComparison.OrdinalIgnoreCase)), fileManifest => + Parallel.ForEach(archiveFiles, fileManifest => { provider.RegisterVfs(fileManifest.FileName, [fileManifest.GetStream()], it => new FRandomAccessStreamArchive(it, manifest.FindFile(it)!.GetStream(), provider.Versions)); From 88bf332de2f2372c5dd32021618649b7bd7b562b Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Tue, 18 Aug 2026 09:16:31 +1000 Subject: [PATCH 03/17] Cleaned up includes --- FModel/App.xaml.cs | 7 +++---- FModel/Creator/Bases/FN/BaseBundle.cs | 2 -- FModel/Creator/Bases/FN/BaseIcon.cs | 2 +- FModel/Creator/Bases/FN/BaseQuest.cs | 3 --- FModel/Creator/Bases/FN/BaseUserControl.cs | 1 - FModel/Creator/Bases/MV/BaseFighter.cs | 3 +-- FModel/Creator/CreatorPackage.cs | 3 ++- FModel/Creator/Utils.cs | 6 +++--- FModel/Extensions/AvalonExtensions.cs | 3 ++- FModel/Extensions/ClipboardExtensions.cs | 4 ++-- .../Themes/HighlightingThemeExtensions.cs | 1 - FModel/Framework/ImGuiController.cs | 8 +++++--- FModel/Framework/SerilogEnricher.cs | 4 ++-- FModel/MainWindow.xaml | 3 +-- FModel/MainWindow.xaml.cs | 1 - FModel/Settings/UserSettings.cs | 4 ++-- .../ViewModels/ApiEndpoints/EpicApiEndpoint.cs | 4 ---- .../ApiEndpoints/FModelApiEndpoint.cs | 5 +++-- .../ApiEndpoints/FortniteApiEndpoint.cs | 4 ++-- .../ApiEndpoints/ValorantApiEndpoint.cs | 3 --- FModel/ViewModels/ApplicationViewModel.cs | 2 +- FModel/ViewModels/AssetsFolderViewModel.cs | 1 - FModel/ViewModels/CUE4ParseViewModel.cs | 7 ++++--- FModel/ViewModels/Commands/CopyCommand.cs | 1 - FModel/ViewModels/Commands/ImageCommand.cs | 4 ++-- FModel/ViewModels/Commands/LoadCommand.cs | 1 - .../Commands/RightClickMenuCommand.cs | 1 - FModel/ViewModels/ExportOptionsViewModel.cs | 4 ++-- FModel/ViewModels/GameDirectoryViewModel.cs | 2 +- FModel/ViewModels/GameFileViewModel.cs | 8 +------- FModel/ViewModels/GameSelectorViewModel.cs | 6 +++--- FModel/ViewModels/SettingsViewModel.cs | 4 ---- FModel/ViewModels/TabControlViewModel.cs | 2 +- FModel/Views/AesManager.xaml | 3 +-- FModel/Views/DirectorySelector.xaml.cs | 2 +- FModel/Views/ExportSessionWindow.xaml | 1 - FModel/Views/ImageMerger.xaml.cs | 17 +++++++++-------- .../Controls/Aed/GamePathElementGenerator.cs | 1 - .../Controls/Aed/GamePathVisualLineText.cs | 2 -- .../Controls/Aed/JumpVisualLineText.cs | 2 -- .../Resources/Controls/AvalonEditor.xaml.cs | 6 ++++-- .../ContextMenus/FolderContextMenu.xaml.cs | 1 - .../Views/Resources/Controls/DropOverlay.xaml | 1 - .../Resources/Controls/DropOverlay.xaml.cs | 1 - .../Explorer/ListExplorer/FolderRow.xaml | 1 - .../Resources/Controls/PropertiesPopout.xaml.cs | 6 +++--- .../Converters/EnumToStringConverter.cs | 4 ++-- .../Converters/SizeToStringConverter.cs | 4 ++-- FModel/Views/SettingsView.xaml | 14 +++++--------- FModel/Views/UpdateView.xaml.cs | 1 - 50 files changed, 71 insertions(+), 110 deletions(-) diff --git a/FModel/App.xaml.cs b/FModel/App.xaml.cs index 5cd426a36..5000293d6 100644 --- a/FModel/App.xaml.cs +++ b/FModel/App.xaml.cs @@ -1,18 +1,17 @@ -using AdonisUI.Controls; -using Microsoft.Win32; -using Serilog; using System; using System.Globalization; using System.IO; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Threading; -using CUE4Parse; +using AdonisUI.Controls; using FModel.Framework; using FModel.Services; using FModel.Settings; using FModel.Views.Snooper; +using Microsoft.Win32; using Newtonsoft.Json; +using Serilog; using Serilog.Events; using Serilog.Sinks.SystemConsole.Themes; using MessageBox = AdonisUI.Controls.MessageBox; diff --git a/FModel/Creator/Bases/FN/BaseBundle.cs b/FModel/Creator/Bases/FN/BaseBundle.cs index 90c9de2b0..eab425aa3 100644 --- a/FModel/Creator/Bases/FN/BaseBundle.cs +++ b/FModel/Creator/Bases/FN/BaseBundle.cs @@ -4,9 +4,7 @@ using CUE4Parse.UE4.Objects.Core.i18N; using CUE4Parse.UE4.Objects.UObject; using CUE4Parse.Utils; -using FModel.Framework; using SkiaSharp; -using SkiaSharp.HarfBuzz; namespace FModel.Creator.Bases.FN; diff --git a/FModel/Creator/Bases/FN/BaseIcon.cs b/FModel/Creator/Bases/FN/BaseIcon.cs index cd57824c9..d669aa1fc 100644 --- a/FModel/Creator/Bases/FN/BaseIcon.cs +++ b/FModel/Creator/Bases/FN/BaseIcon.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Windows; +using CUE4Parse_Conversion.Textures; using CUE4Parse.GameTypes.FN.Enums; using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports.Engine; @@ -12,7 +13,6 @@ using CUE4Parse.UE4.Objects.Core.Math; using CUE4Parse.UE4.Objects.GameplayTags; using CUE4Parse.UE4.Objects.UObject; -using CUE4Parse_Conversion.Textures; using FModel.Settings; using SkiaSharp; diff --git a/FModel/Creator/Bases/FN/BaseQuest.cs b/FModel/Creator/Bases/FN/BaseQuest.cs index 6d2e9b876..b1ea74076 100644 --- a/FModel/Creator/Bases/FN/BaseQuest.cs +++ b/FModel/Creator/Bases/FN/BaseQuest.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports.Engine; using CUE4Parse.UE4.Assets.Exports.Material; @@ -9,9 +8,7 @@ using CUE4Parse.UE4.Objects.Core.i18N; using CUE4Parse.UE4.Objects.UObject; using CUE4Parse.Utils; -using FModel.Framework; using SkiaSharp; -using SkiaSharp.HarfBuzz; namespace FModel.Creator.Bases.FN; diff --git a/FModel/Creator/Bases/FN/BaseUserControl.cs b/FModel/Creator/Bases/FN/BaseUserControl.cs index a4e5bd860..1cc063d47 100644 --- a/FModel/Creator/Bases/FN/BaseUserControl.cs +++ b/FModel/Creator/Bases/FN/BaseUserControl.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Globalization; using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Objects; using CUE4Parse.UE4.Objects.Core.i18N; diff --git a/FModel/Creator/Bases/MV/BaseFighter.cs b/FModel/Creator/Bases/MV/BaseFighter.cs index 8dd0ca3b7..ec6374037 100644 --- a/FModel/Creator/Bases/MV/BaseFighter.cs +++ b/FModel/Creator/Bases/MV/BaseFighter.cs @@ -1,7 +1,7 @@ using System; -using System.Linq; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports.Engine; using CUE4Parse.UE4.Assets.Exports.Material; @@ -9,7 +9,6 @@ using CUE4Parse.UE4.Objects.Core.i18N; using CUE4Parse.UE4.Objects.Core.Math; using CUE4Parse.UE4.Objects.UObject; -using FModel.Extensions; using SkiaSharp; namespace FModel.Creator.Bases.MV; diff --git a/FModel/Creator/CreatorPackage.cs b/FModel/Creator/CreatorPackage.cs index 82f814270..667516b26 100644 --- a/FModel/Creator/CreatorPackage.cs +++ b/FModel/Creator/CreatorPackage.cs @@ -5,6 +5,7 @@ using FModel.Creator.Bases; using FModel.Creator.Bases.FN; using FModel.Creator.Bases.MV; +using BaseQuest = FModel.Creator.Bases.FN.BaseQuest; namespace FModel.Creator; @@ -214,7 +215,7 @@ when _pkgName.Contains("/MI_OfferImages/", StringComparison.OrdinalIgnoreCase) | case "FortQuestItemDefinition_Campaign": case "AthenaDailyQuestDefinition": case "FortUrgentQuestItemDefinition": - creator = new Bases.FN.BaseQuest(_object.Value, _style); + creator = new BaseQuest(_object.Value, _style); return true; case "FortCompendiumItemDefinition": case "FortCompendiumBundleDefinition": diff --git a/FModel/Creator/Utils.cs b/FModel/Creator/Utils.cs index 3d44c40e8..8d88db494 100644 --- a/FModel/Creator/Utils.cs +++ b/FModel/Creator/Utils.cs @@ -5,16 +5,16 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; +using CUE4Parse_Conversion.Textures; using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports.Material; using CUE4Parse.UE4.Assets.Exports.Texture; +using CUE4Parse.UE4.Assets.Objects; using CUE4Parse.UE4.Objects.UObject; using CUE4Parse.UE4.Versions; -using CUE4Parse_Conversion.Textures; -using CUE4Parse.UE4.Assets.Objects; using CUE4Parse.Utils; -using FModel.Framework; using FModel.Extensions; +using FModel.Framework; using FModel.Services; using FModel.Settings; using FModel.ViewModels; diff --git a/FModel/Extensions/AvalonExtensions.cs b/FModel/Extensions/AvalonExtensions.cs index 6cc7968ab..0faf65344 100644 --- a/FModel/Extensions/AvalonExtensions.cs +++ b/FModel/Extensions/AvalonExtensions.cs @@ -2,6 +2,7 @@ using System.Runtime.CompilerServices; using System.Xml; using FModel.Extensions.Themes; +using FModel.Settings; using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.AvalonEdit.Highlighting.Xshd; @@ -61,7 +62,7 @@ public static IHighlightingDefinition HighlighterSelector(string ext) case "po": return null; default: - _jsonHighlighter.ApplyJsonTheme(Settings.UserSettings.Default.JsonHighlightTheme); + _jsonHighlighter.ApplyJsonTheme(UserSettings.Default.JsonHighlightTheme); return _jsonHighlighter; } } diff --git a/FModel/Extensions/ClipboardExtensions.cs b/FModel/Extensions/ClipboardExtensions.cs index 571e91567..34bf83505 100644 --- a/FModel/Extensions/ClipboardExtensions.cs +++ b/FModel/Extensions/ClipboardExtensions.cs @@ -1,4 +1,3 @@ -using SkiaSharp; using System; using System.Drawing; using System.Drawing.Imaging; @@ -6,6 +5,7 @@ using System.Runtime.CompilerServices; using System.Text; using System.Windows; +using SkiaSharp; namespace FModel.Extensions; @@ -195,4 +195,4 @@ private static string GenerateHTMLFragment(string html) return sb.ToString(); } -} \ No newline at end of file +} diff --git a/FModel/Extensions/Themes/HighlightingThemeExtensions.cs b/FModel/Extensions/Themes/HighlightingThemeExtensions.cs index 8edbac2d2..d8782102b 100644 --- a/FModel/Extensions/Themes/HighlightingThemeExtensions.cs +++ b/FModel/Extensions/Themes/HighlightingThemeExtensions.cs @@ -1,7 +1,6 @@ using System.Windows; using System.Windows.Media; using ICSharpCode.AvalonEdit.Highlighting; -using static FModel.Extensions.AvalonExtensions; using static FModel.Extensions.Themes.JsonHighlightThemes; namespace FModel.Extensions.Themes; diff --git a/FModel/Framework/ImGuiController.cs b/FModel/Framework/ImGuiController.cs index 0768de97f..7514537a5 100644 --- a/FModel/Framework/ImGuiController.cs +++ b/FModel/Framework/ImGuiController.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Numerics; +using System.Reflection; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Forms; @@ -10,10 +10,12 @@ using ImGuiNET; using ImGuizmoNET; using OpenTK.Graphics.OpenGL4; +using OpenTK.Mathematics; using OpenTK.Windowing.Desktop; using OpenTK.Windowing.GraphicsLibraryFramework; using ErrorCode = OpenTK.Graphics.OpenGL4.ErrorCode; using Keys = OpenTK.Windowing.GraphicsLibraryFramework.Keys; +using Vector2 = System.Numerics.Vector2; namespace FModel.Framework; @@ -67,7 +69,7 @@ public ImGuiController(int width, int height) io.NativePtr->IniFilename = (byte*)iniFileNamePtr; } - var assembly = System.Reflection.Assembly.GetExecutingAssembly(); + var assembly = Assembly.GetExecutingAssembly(); var assemblyName = assembly.GetName().Name; byte[] LoadFont(string name) { @@ -421,7 +423,7 @@ private void RenderImDrawData(ImDrawDataPtr draw_data) // Setup orthographic projection matrix into our constant buffer ImGuiIOPtr io = ImGui.GetIO(); - var mvp = OpenTK.Mathematics.Matrix4.CreateOrthographicOffCenter( + var mvp = Matrix4.CreateOrthographicOffCenter( 0.0f, io.DisplaySize.X, io.DisplaySize.Y, diff --git a/FModel/Framework/SerilogEnricher.cs b/FModel/Framework/SerilogEnricher.cs index 3567d5d0d..adbc9c518 100644 --- a/FModel/Framework/SerilogEnricher.cs +++ b/FModel/Framework/SerilogEnricher.cs @@ -1,6 +1,6 @@ -using System; using System.Diagnostics; using System.Reflection; +using Serilog; using Serilog.Core; using Serilog.Events; @@ -14,7 +14,7 @@ protected bool TryGetCaller(out MethodBase method) { method = null; - var serilogAssembly = typeof(Serilog.Log).Assembly; + var serilogAssembly = typeof(Log).Assembly; var stack = new StackTrace(3); foreach (var frame in stack.GetFrames()) diff --git a/FModel/MainWindow.xaml b/FModel/MainWindow.xaml index 6c63f0d73..b79b19c99 100644 --- a/FModel/MainWindow.xaml +++ b/FModel/MainWindow.xaml @@ -384,8 +384,7 @@ Style="{StaticResource AssetsFolderTreeView}" SelectedItemChanged="OnAssetsTreeSelectedItemChanged" PreviewKeyDown="OnFoldersPreviewKeyDown" - PreviewMouseDoubleClick="OnAssetsTreeMouseDoubleClick"> - + PreviewMouseDoubleClick="OnAssetsTreeMouseDoubleClick" /> diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index d8b2bbaf1..88d1cb82a 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -1,7 +1,6 @@ using System; using System.Collections; using System.ComponentModel; -using System.IO; using System.Linq; using System.Threading.Tasks; using System.Windows; diff --git a/FModel/Settings/UserSettings.cs b/FModel/Settings/UserSettings.cs index aff57f4ce..0dbf6c0bf 100644 --- a/FModel/Settings/UserSettings.cs +++ b/FModel/Settings/UserSettings.cs @@ -3,11 +3,11 @@ using System.IO; using System.Windows; using System.Windows.Input; -using CUE4Parse.UE4.Assets.Exports.Material; -using CUE4Parse.UE4.Versions; using CUE4Parse_Conversion.Options; using CUE4Parse_Conversion.Writers.UEFormat.Enums; +using CUE4Parse.UE4.Assets.Exports.Material; using CUE4Parse.UE4.Lua.unluac; +using CUE4Parse.UE4.Versions; using FModel.Extensions.Themes; using FModel.Framework; using FModel.ViewModels; diff --git a/FModel/ViewModels/ApiEndpoints/EpicApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/EpicApiEndpoint.cs index 57a8c57aa..542400b58 100644 --- a/FModel/ViewModels/ApiEndpoints/EpicApiEndpoint.cs +++ b/FModel/ViewModels/ApiEndpoints/EpicApiEndpoint.cs @@ -1,14 +1,10 @@ using System.Threading; using System.Threading.Tasks; - using EpicManifestParser.Api; - using FModel.Framework; using FModel.Settings; using FModel.ViewModels.ApiEndpoints.Models; - using RestSharp; - using Serilog; namespace FModel.ViewModels.ApiEndpoints; diff --git a/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs index db2882ed6..a3ceb9536 100644 --- a/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs +++ b/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs @@ -1,9 +1,9 @@ using System; -using AdonisUI.Controls; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using System.Windows; +using AdonisUI.Controls; using AutoUpdaterDotNET; using CUE4Parse.Utils; using FModel.Extensions; @@ -18,6 +18,7 @@ using MessageBox = AdonisUI.Controls.MessageBox; using MessageBoxButton = AdonisUI.Controls.MessageBoxButton; using MessageBoxImage = AdonisUI.Controls.MessageBoxImage; +using Version = System.Version; namespace FModel.ViewModels.ApiEndpoints; @@ -139,7 +140,7 @@ private void CheckForUpdateEvent(UpdateInfoEventArgs args) return; } - var currentVersion = new System.Version(args.CurrentVersion); + var currentVersion = new Version(args.CurrentVersion); UserSettings.Default.ShowChangelog = currentVersion != args.InstalledVersion; const string message = "A new update is available!"; diff --git a/FModel/ViewModels/ApiEndpoints/FortniteApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/FortniteApiEndpoint.cs index 34028ba8e..87f59a90d 100644 --- a/FModel/ViewModels/ApiEndpoints/FortniteApiEndpoint.cs +++ b/FModel/ViewModels/ApiEndpoints/FortniteApiEndpoint.cs @@ -1,8 +1,8 @@ using System; -using FModel.ViewModels.ApiEndpoints.Models; -using RestSharp; using System.Threading.Tasks; using FModel.Framework; +using FModel.ViewModels.ApiEndpoints.Models; +using RestSharp; namespace FModel.ViewModels.ApiEndpoints; diff --git a/FModel/ViewModels/ApiEndpoints/ValorantApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/ValorantApiEndpoint.cs index 7cc156609..095d728e7 100644 --- a/FModel/ViewModels/ApiEndpoints/ValorantApiEndpoint.cs +++ b/FModel/ViewModels/ApiEndpoints/ValorantApiEndpoint.cs @@ -8,13 +8,10 @@ using System.Text; using System.Threading; using System.Threading.Tasks; - using CUE4Parse.Compression; using CUE4Parse.UE4.Exceptions; using CUE4Parse.UE4.Readers; - using FModel.Framework; -using FModel.Settings; using OffiUtils; using RestSharp; diff --git a/FModel/ViewModels/ApplicationViewModel.cs b/FModel/ViewModels/ApplicationViewModel.cs index 242379f93..55483f300 100644 --- a/FModel/ViewModels/ApplicationViewModel.cs +++ b/FModel/ViewModels/ApplicationViewModel.cs @@ -4,6 +4,7 @@ using System.IO; using System.IO.Compression; using System.Linq; +using System.Runtime.Intrinsics.X86; using System.Threading.Tasks; using System.Windows; using CUE4Parse_Conversion.Textures.BC; @@ -22,7 +23,6 @@ using MessageBox = AdonisUI.Controls.MessageBox; using MessageBoxButton = AdonisUI.Controls.MessageBoxButton; using MessageBoxImage = AdonisUI.Controls.MessageBoxImage; -using System.Runtime.Intrinsics.X86; namespace FModel.ViewModels; diff --git a/FModel/ViewModels/AssetsFolderViewModel.cs b/FModel/ViewModels/AssetsFolderViewModel.cs index 845a930a0..fb257b563 100644 --- a/FModel/ViewModels/AssetsFolderViewModel.cs +++ b/FModel/ViewModels/AssetsFolderViewModel.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index b2641e936..57f09bf73 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -11,6 +11,8 @@ using System.Threading.Tasks; using System.Windows; using AdonisUI.Controls; +using CUE4Parse_Conversion.Exporters; +using CUE4Parse_Conversion.Sounds; using CUE4Parse; using CUE4Parse.Compression; using CUE4Parse.Encryption.Aes; @@ -67,8 +69,6 @@ using CUE4Parse.UE4.Versions; using CUE4Parse.UE4.Wwise; using CUE4Parse.Utils; -using CUE4Parse_Conversion.Exporters; -using CUE4Parse_Conversion.Sounds; using EpicManifestParser; using EpicManifestParser.UE; using FModel.Creator; @@ -82,6 +82,7 @@ using FModel.Views.Snooper; using Newtonsoft.Json; using Newtonsoft.Json.Converters; +using OpenTK.Mathematics; using OpenTK.Windowing.Common; using OpenTK.Windowing.Desktop; using Serilog; @@ -134,7 +135,7 @@ public Snooper SnooperViewer new GameWindowSettings { UpdateFrequency = htz }, new NativeWindowSettings { - ClientSize = new OpenTK.Mathematics.Vector2i( + ClientSize = new Vector2i( Convert.ToInt32(SystemParameters.MaximizedPrimaryScreenWidth * .75 * scale), Convert.ToInt32(SystemParameters.MaximizedPrimaryScreenHeight * .85 * scale)), NumberOfSamples = Constants.SAMPLES_COUNT, diff --git a/FModel/ViewModels/Commands/CopyCommand.cs b/FModel/ViewModels/Commands/CopyCommand.cs index 97177db3d..fa1126141 100644 --- a/FModel/ViewModels/Commands/CopyCommand.cs +++ b/FModel/ViewModels/Commands/CopyCommand.cs @@ -1,5 +1,4 @@ using System.Collections; -using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; diff --git a/FModel/ViewModels/Commands/ImageCommand.cs b/FModel/ViewModels/Commands/ImageCommand.cs index df4b7735a..9d5083875 100644 --- a/FModel/ViewModels/Commands/ImageCommand.cs +++ b/FModel/ViewModels/Commands/ImageCommand.cs @@ -1,9 +1,9 @@ +using System.Windows; +using System.Windows.Media; using AdonisUI.Controls; using FModel.Extensions; using FModel.Framework; using FModel.Views.Resources.Controls; -using System.Windows; -using System.Windows.Media; using FModel.Views.Resources.Converters; namespace FModel.ViewModels.Commands; diff --git a/FModel/ViewModels/Commands/LoadCommand.cs b/FModel/ViewModels/Commands/LoadCommand.cs index c29a695ee..dba6eb4b1 100644 --- a/FModel/ViewModels/Commands/LoadCommand.cs +++ b/FModel/ViewModels/Commands/LoadCommand.cs @@ -6,7 +6,6 @@ using System.IO; using System.Linq; using System.Threading; -using System.Threading.Tasks; using AdonisUI.Controls; using CUE4Parse.FileProvider.Objects; using CUE4Parse.UE4.Readers; diff --git a/FModel/ViewModels/Commands/RightClickMenuCommand.cs b/FModel/ViewModels/Commands/RightClickMenuCommand.cs index 7ad31b6fd..3eb37a3c8 100644 --- a/FModel/ViewModels/Commands/RightClickMenuCommand.cs +++ b/FModel/ViewModels/Commands/RightClickMenuCommand.cs @@ -1,6 +1,5 @@ using System; using System.Collections; -using System.Data; using System.IO; using System.Linq; using System.Threading; diff --git a/FModel/ViewModels/ExportOptionsViewModel.cs b/FModel/ViewModels/ExportOptionsViewModel.cs index c2f808bcc..058837161 100644 --- a/FModel/ViewModels/ExportOptionsViewModel.cs +++ b/FModel/ViewModels/ExportOptionsViewModel.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Windows.Threading; -using CUE4Parse.UE4.Assets.Exports.Material; -using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse_Conversion.Options; using CUE4Parse_Conversion.Writers.UEFormat.Enums; +using CUE4Parse.UE4.Assets.Exports.Material; +using CUE4Parse.UE4.Assets.Exports.Texture; using FModel.Framework; using FModel.Settings; diff --git a/FModel/ViewModels/GameDirectoryViewModel.cs b/FModel/ViewModels/GameDirectoryViewModel.cs index 1d358e6c6..588b4f5bf 100644 --- a/FModel/ViewModels/GameDirectoryViewModel.cs +++ b/FModel/ViewModels/GameDirectoryViewModel.cs @@ -1,4 +1,3 @@ -using FModel.Framework; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; @@ -9,6 +8,7 @@ using CUE4Parse.UE4.IO; using CUE4Parse.UE4.Objects.Core.Misc; using CUE4Parse.UE4.VirtualFileSystem; +using FModel.Framework; namespace FModel.ViewModels; diff --git a/FModel/ViewModels/GameFileViewModel.cs b/FModel/ViewModels/GameFileViewModel.cs index 57a9456c3..25e986b4c 100644 --- a/FModel/ViewModels/GameFileViewModel.cs +++ b/FModel/ViewModels/GameFileViewModel.cs @@ -5,7 +5,7 @@ using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; - +using CUE4Parse_Conversion.Textures; using CUE4Parse.FileProvider.Objects; using CUE4Parse.GameTypes.Borderlands3.Assets.Exports; using CUE4Parse.GameTypes.Borderlands4.Assets.Exports; @@ -51,17 +51,11 @@ using CUE4Parse.UE4.Objects.UObject.Editor; using CUE4Parse.UE4.Versions; using CUE4Parse.Utils; - -using CUE4Parse_Conversion.Textures; - using FModel.Framework; using FModel.Services; using FModel.Settings; - using Serilog; - using SkiaSharp; - using Svg.Skia; namespace FModel.ViewModels; diff --git a/FModel/ViewModels/GameSelectorViewModel.cs b/FModel/ViewModels/GameSelectorViewModel.cs index 6447e2d78..eb288ffe8 100644 --- a/FModel/ViewModels/GameSelectorViewModel.cs +++ b/FModel/ViewModels/GameSelectorViewModel.cs @@ -1,6 +1,3 @@ -using FModel.Framework; -using Newtonsoft.Json; -using Serilog; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -12,9 +9,12 @@ using CUE4Parse.UE4.Objects.Core.Serialization; using CUE4Parse.UE4.Versions; using CUE4Parse.Utils; +using FModel.Framework; using FModel.Settings; using FModel.ViewModels.ApiEndpoints.Models; using Microsoft.Win32; +using Newtonsoft.Json; +using Serilog; namespace FModel.ViewModels; diff --git a/FModel/ViewModels/SettingsViewModel.cs b/FModel/ViewModels/SettingsViewModel.cs index 0b91ff261..30e693561 100644 --- a/FModel/ViewModels/SettingsViewModel.cs +++ b/FModel/ViewModels/SettingsViewModel.cs @@ -2,12 +2,8 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse.UE4.Objects.Core.Serialization; using CUE4Parse.UE4.Versions; -using CUE4Parse_Conversion.Options; -using CUE4Parse_Conversion.Writers.UEFormat.Enums; -using CUE4Parse.UE4.Assets.Exports.Material; using FModel.Extensions.Themes; using FModel.Framework; using FModel.Services; diff --git a/FModel/ViewModels/TabControlViewModel.cs b/FModel/ViewModels/TabControlViewModel.cs index a2b7ddf73..0422e6d9b 100644 --- a/FModel/ViewModels/TabControlViewModel.cs +++ b/FModel/ViewModels/TabControlViewModel.cs @@ -6,10 +6,10 @@ using System.Windows; using System.Windows.Media.Imaging; using CUE4Parse_Conversion.Options; +using CUE4Parse_Conversion.Textures; using CUE4Parse.FileProvider.Objects; using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse.Utils; -using CUE4Parse_Conversion.Textures; using FModel.Extensions; using FModel.Framework; using FModel.Services; diff --git a/FModel/Views/AesManager.xaml b/FModel/Views/AesManager.xaml index 9948d4b8a..43d424263 100644 --- a/FModel/Views/AesManager.xaml +++ b/FModel/Views/AesManager.xaml @@ -74,8 +74,7 @@ HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="OK" Click="OnClick" /> + Visibility="{Binding Converter={x:Static converters:EndpointToTypeConverter.Instance}, ConverterParameter={x:Static local:EEndpointType.Aes}}" /> diff --git a/FModel/Views/DirectorySelector.xaml.cs b/FModel/Views/DirectorySelector.xaml.cs index 8b0be1431..b12210ed8 100644 --- a/FModel/Views/DirectorySelector.xaml.cs +++ b/FModel/Views/DirectorySelector.xaml.cs @@ -1,6 +1,6 @@ +using System.Windows; using FModel.ViewModels; using Ookii.Dialogs.Wpf; -using System.Windows; namespace FModel.Views; diff --git a/FModel/Views/ExportSessionWindow.xaml b/FModel/Views/ExportSessionWindow.xaml index 61a6f26b6..e0ff579f3 100644 --- a/FModel/Views/ExportSessionWindow.xaml +++ b/FModel/Views/ExportSessionWindow.xaml @@ -13,7 +13,6 @@ xmlns:converters="clr-namespace:FModel.Views.Resources.Converters" xmlns:controls="clr-namespace:FModel.Views.Resources.Controls" xmlns:serilog="clr-namespace:Serilog.Events;assembly=Serilog" - xmlns:c4pMeshes="clr-namespace:CUE4Parse_Conversion.Options;assembly=CUE4Parse-Conversion" DataContext="{x:Static vm:ExportSessionViewModel.Instance}" WindowStartupLocation="CenterScreen" ResizeMode="NoResize" IconVisibility="Collapsed" diff --git a/FModel/Views/ImageMerger.xaml.cs b/FModel/Views/ImageMerger.xaml.cs index 6a3a41897..9e1b2236f 100644 --- a/FModel/Views/ImageMerger.xaml.cs +++ b/FModel/Views/ImageMerger.xaml.cs @@ -1,10 +1,3 @@ -using AdonisUI.Controls; -using FModel.Extensions; -using FModel.Settings; -using FModel.Views.Resources.Controls; -using Microsoft.Win32; -using Serilog; -using SkiaSharp; using System; using System.Collections.Generic; using System.Drawing.Imaging; @@ -16,6 +9,14 @@ using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Media.Imaging; +using AdonisUI.Controls; +using FModel.Extensions; +using FModel.Settings; +using FModel.Views.Resources.Controls; +using Microsoft.Win32; +using Serilog; +using SkiaSharp; +using Image = System.Drawing.Image; namespace FModel.Views; @@ -66,7 +67,7 @@ private async Task DrawPreview() { await using var tmp = new MemoryStream(); await stream.CopyToAsync(tmp); - System.Drawing.Image.FromStream(tmp).Save(ms, ImageFormat.Png); + Image.FromStream(tmp).Save(ms, ImageFormat.Png); } else { diff --git a/FModel/Views/Resources/Controls/Aed/GamePathElementGenerator.cs b/FModel/Views/Resources/Controls/Aed/GamePathElementGenerator.cs index 3b6a7efe8..7a1c80dfa 100644 --- a/FModel/Views/Resources/Controls/Aed/GamePathElementGenerator.cs +++ b/FModel/Views/Resources/Controls/Aed/GamePathElementGenerator.cs @@ -1,5 +1,4 @@ using System.Text.RegularExpressions; -using FModel.Extensions; using ICSharpCode.AvalonEdit.Rendering; namespace FModel.Views.Resources.Controls; diff --git a/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs b/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs index 51d6a5d1c..6cacbaf08 100644 --- a/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs +++ b/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs @@ -1,5 +1,4 @@ using System; -using System.Text.RegularExpressions; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.TextFormatting; @@ -7,7 +6,6 @@ using FModel.Extensions; using FModel.Services; using FModel.ViewModels; -using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Rendering; namespace FModel.Views.Resources.Controls; diff --git a/FModel/Views/Resources/Controls/Aed/JumpVisualLineText.cs b/FModel/Views/Resources/Controls/Aed/JumpVisualLineText.cs index 41034e3ee..7c11876f4 100644 --- a/FModel/Views/Resources/Controls/Aed/JumpVisualLineText.cs +++ b/FModel/Views/Resources/Controls/Aed/JumpVisualLineText.cs @@ -3,8 +3,6 @@ using System.Windows.Media; using System.Windows.Media.TextFormatting; using FModel.Extensions; -using FModel.Services; -using FModel.ViewModels; using ICSharpCode.AvalonEdit.Rendering; namespace FModel.Views.Resources.Controls; diff --git a/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs b/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs index 9aebc30cc..42c3b001e 100644 --- a/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs +++ b/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text.RegularExpressions; using System.Windows; +using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using CUE4Parse.Utils; @@ -11,6 +12,7 @@ using FModel.ViewModels; using ICSharpCode.AvalonEdit; using SkiaSharp; +using TabItem = FModel.ViewModels.TabItem; namespace FModel.Views.Resources.Controls; @@ -20,10 +22,10 @@ namespace FModel.Views.Resources.Controls; public partial class AvalonEditor { public static TextEditor YesWeEditor; - public static System.Windows.Controls.TextBox YesWeSearch; + public static TextBox YesWeSearch; private readonly Regex _hexColorRegex = new("\"Hex\": \"(?'target'[0-9A-Fa-f]{3,8})\"$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - private readonly System.Windows.Controls.ToolTip _toolTip = new(); + private readonly ToolTip _toolTip = new(); private readonly Dictionary> _savedCarets = new(); private NavigationList _caretsOffsets { diff --git a/FModel/Views/Resources/Controls/ContextMenus/FolderContextMenu.xaml.cs b/FModel/Views/Resources/Controls/ContextMenus/FolderContextMenu.xaml.cs index b1e1c8c97..ec348f777 100644 --- a/FModel/Views/Resources/Controls/ContextMenus/FolderContextMenu.xaml.cs +++ b/FModel/Views/Resources/Controls/ContextMenus/FolderContextMenu.xaml.cs @@ -2,7 +2,6 @@ using System.Linq; using System.Windows; using System.Windows.Controls; -using System.Windows.Media; using FModel.Extensions; using FModel.Services; using FModel.Settings; diff --git a/FModel/Views/Resources/Controls/DropOverlay.xaml b/FModel/Views/Resources/Controls/DropOverlay.xaml index 2a5a0dce7..41ef200f8 100644 --- a/FModel/Views/Resources/Controls/DropOverlay.xaml +++ b/FModel/Views/Resources/Controls/DropOverlay.xaml @@ -3,7 +3,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="clr-namespace:FModel.Views.Resources.Controls" xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI" Visibility="Collapsed" Background="Transparent" diff --git a/FModel/Views/Resources/Controls/DropOverlay.xaml.cs b/FModel/Views/Resources/Controls/DropOverlay.xaml.cs index faacc4129..f0214a99a 100644 --- a/FModel/Views/Resources/Controls/DropOverlay.xaml.cs +++ b/FModel/Views/Resources/Controls/DropOverlay.xaml.cs @@ -4,7 +4,6 @@ using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Controls; -using System.Windows.Threading; using FModel.Services; using FModel.Settings; using FModel.ViewModels; diff --git a/FModel/Views/Resources/Controls/Explorer/ListExplorer/FolderRow.xaml b/FModel/Views/Resources/Controls/Explorer/ListExplorer/FolderRow.xaml index deaa89577..92bbb63f0 100644 --- a/FModel/Views/Resources/Controls/Explorer/ListExplorer/FolderRow.xaml +++ b/FModel/Views/Resources/Controls/Explorer/ListExplorer/FolderRow.xaml @@ -4,7 +4,6 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:vm="clr-namespace:FModel.ViewModels" - xmlns:converters="clr-namespace:FModel.Views.Resources.Converters" xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI" mc:Ignorable="d" d:DesignHeight="32" diff --git a/FModel/Views/Resources/Controls/PropertiesPopout.xaml.cs b/FModel/Views/Resources/Controls/PropertiesPopout.xaml.cs index ed4b3ff7c..738695e9b 100644 --- a/FModel/Views/Resources/Controls/PropertiesPopout.xaml.cs +++ b/FModel/Views/Resources/Controls/PropertiesPopout.xaml.cs @@ -1,12 +1,12 @@ using System; using System.Text.RegularExpressions; +using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; -using CUE4Parse.Utils; -using FModel.ViewModels; using ICSharpCode.AvalonEdit; using ICSharpCode.AvalonEdit.Document; using SkiaSharp; +using TabItem = FModel.ViewModels.TabItem; namespace FModel.Views.Resources.Controls; @@ -14,7 +14,7 @@ public partial class PropertiesPopout { private readonly Regex _hexColorRegex = new("\"Hex\": \"(?'target'[0-9A-Fa-f]{3,8})\"$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - private readonly System.Windows.Controls.ToolTip _toolTip = new(); + private readonly ToolTip _toolTip = new(); private JsonFoldingStrategies _manager; public PropertiesPopout(TabItem contextViewModel) diff --git a/FModel/Views/Resources/Converters/EnumToStringConverter.cs b/FModel/Views/Resources/Converters/EnumToStringConverter.cs index d317f4e61..56c6bf6f2 100644 --- a/FModel/Views/Resources/Converters/EnumToStringConverter.cs +++ b/FModel/Views/Resources/Converters/EnumToStringConverter.cs @@ -1,7 +1,7 @@ -using FModel.Extensions; using System; using System.Globalization; using System.Windows.Data; +using FModel.Extensions; namespace FModel.Views.Resources.Converters; @@ -27,4 +27,4 @@ public object ConvertBack(object value, Type targetType, object parameter, Cultu { throw new NotImplementedException(); } -} \ No newline at end of file +} diff --git a/FModel/Views/Resources/Converters/SizeToStringConverter.cs b/FModel/Views/Resources/Converters/SizeToStringConverter.cs index a256e092b..ab90588a3 100644 --- a/FModel/Views/Resources/Converters/SizeToStringConverter.cs +++ b/FModel/Views/Resources/Converters/SizeToStringConverter.cs @@ -1,7 +1,7 @@ -using FModel.Extensions; using System; using System.Globalization; using System.Windows.Data; +using FModel.Extensions; namespace FModel.Views.Resources.Converters; @@ -18,4 +18,4 @@ public object ConvertBack(object value, Type targetType, object parameter, Cultu { throw new NotImplementedException(); } -} \ No newline at end of file +} diff --git a/FModel/Views/SettingsView.xaml b/FModel/Views/SettingsView.xaml index cf904af88..39c0ea995 100644 --- a/FModel/Views/SettingsView.xaml +++ b/FModel/Views/SettingsView.xaml @@ -2,7 +2,6 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:FModel" - xmlns:c4pMeshes="clr-namespace:CUE4Parse_Conversion.Options;assembly=CUE4Parse-Conversion" xmlns:controls="clr-namespace:FModel.Views.Resources.Controls" xmlns:converters="clr-namespace:FModel.Views.Resources.Converters" xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI" @@ -108,7 +107,7 @@ - + @@ -169,7 +168,7 @@ DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" /> - + @@ -558,8 +557,7 @@ Converter={x:Static converters:EnumFlagToBoolConverter.Instance}, ConverterParameter=RawString, Mode=OneWay}" Checked="OnUnluacFlagChanged" Unchecked="OnUnluacFlagChanged" - ToolTip="Copy string bytes directly to output"> - + ToolTip="Copy string bytes directly to output" /> - + ToolTip="Ignore debugging information in input file" /> - + ToolTip="Emulate Luaj's permissive parser" /> Date: Wed, 19 Aug 2026 06:22:51 +1000 Subject: [PATCH 04/17] Audio player uses proper audio device + finally uses setting --- FModel/ViewModels/AudioPlayerViewModel.cs | 18 ++++++++++++++++-- FModel/Views/AudioPlayer.xaml.cs | 9 ++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/FModel/ViewModels/AudioPlayerViewModel.cs b/FModel/ViewModels/AudioPlayerViewModel.cs index ee9f2af9e..a630097ef 100644 --- a/FModel/ViewModels/AudioPlayerViewModel.cs +++ b/FModel/ViewModels/AudioPlayerViewModel.cs @@ -191,7 +191,14 @@ public AudioFile SelectedAudioFile public MMDevice SelectedAudioDevice { get => _selectedAudioDevice; - set => SetProperty(ref _selectedAudioDevice, value); + set + { + if (SetProperty(ref _selectedAudioDevice, value)) + { + UserSettings.Default.AudioDeviceId = value?.DeviceID; + UserSettings.Save(); + } + } } private AudioCommand _audioCommand; @@ -213,7 +220,13 @@ public AudioPlayerViewModel() var audioDevices = new ObservableCollection(EnumerateDevices()); AudioDevicesView = new ListCollectionView(audioDevices) { SortDescriptions = { new SortDescription("FriendlyName", ListSortDirection.Ascending) } }; - SelectedAudioDevice ??= audioDevices.FirstOrDefault(); + SelectedAudioDevice = audioDevices.FirstOrDefault(x => x.DeviceID == UserSettings.Default.AudioDeviceId) ?? GetDefaultAudioDevice() ?? audioDevices.FirstOrDefault(); + } + + private MMDevice GetDefaultAudioDevice() + { + using var enumerator = new MMDeviceEnumerator(); + return enumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); } public void Load() @@ -527,6 +540,7 @@ public void Dispose() }); } + [DebuggerHidden] private void TimerTick(object state) { if (_waveSource == null || _soundOut == null) return; diff --git a/FModel/Views/AudioPlayer.xaml.cs b/FModel/Views/AudioPlayer.xaml.cs index f65660cea..ca9c40dd6 100644 --- a/FModel/Views/AudioPlayer.xaml.cs +++ b/FModel/Views/AudioPlayer.xaml.cs @@ -36,11 +36,10 @@ private void OnClosing(object sender, CancelEventArgs e) private void OnDeviceSwap(object sender, SelectionChangedEventArgs e) { - if (sender is not ComboBox { SelectedItem: MMDevice selectedDevice }) - return; - - UserSettings.Default.AudioDeviceId = selectedDevice.DeviceID; - _applicationView.AudioPlayer.Device(); + if (sender is ComboBox { SelectedItem: MMDevice selectedDevice }) + { + _applicationView.AudioPlayer.Device(); + } } private void OnVolumeChange(object sender, RoutedEventArgs e) From 73c6315334b6840e6bcf7e96fe9240510e819f11 Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Wed, 19 Aug 2026 06:42:05 +1000 Subject: [PATCH 05/17] removes Archive only information from loose assets --- FModel/MainWindow.xaml | 227 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 212 insertions(+), 15 deletions(-) diff --git a/FModel/MainWindow.xaml b/FModel/MainWindow.xaml index b79b19c99..4a9582d11 100644 --- a/FModel/MainWindow.xaml +++ b/FModel/MainWindow.xaml @@ -404,12 +404,98 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -454,17 +540,128 @@ - - - + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 9ed6fe7625b1be3c9d2641e393017776b9485385 Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:00:48 +1000 Subject: [PATCH 06/17] faster overall speeds --- FModel/ViewModels/SearchViewModel.cs | 161 ++++++++++++++++++--------- FModel/Views/SearchView.xaml | 4 +- 2 files changed, 110 insertions(+), 55 deletions(-) diff --git a/FModel/ViewModels/SearchViewModel.cs b/FModel/ViewModels/SearchViewModel.cs index 17b904f2c..feed6372a 100644 --- a/FModel/ViewModels/SearchViewModel.cs +++ b/FModel/ViewModels/SearchViewModel.cs @@ -2,8 +2,9 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; -using System.Windows.Data; +using System.Windows; using CUE4Parse.FileProvider.Objects; using CUE4Parse.UE4.VirtualFileSystem; using FModel.Framework; @@ -47,7 +48,7 @@ public ESortSizeMode CurrentSortSizeMode set => SetProperty(ref _currentSortSizeMode, value); } - private int _resultsCount = 0; + private int _resultsCount; public int ResultsCount { get => _resultsCount; @@ -62,30 +63,24 @@ public GameFile RefFile } public RangeObservableCollection SearchResults { get; } - public ListCollectionView SearchResultsView { get; } + private List _allEntries = new(); + private CancellationTokenSource _updateCts = new(); public SearchViewModel() { - SearchResults = []; - SearchResultsView = new ListCollectionView(SearchResults) - { - Filter = e => ItemFilter(e, FilterText.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries)), - }; - ResultsCount = SearchResultsView.Count; + SearchResults = new RangeObservableCollection(); } - public void RefreshFilter() + public void ChangeCollection(IEnumerable files, GameFile refFile = null) { - SearchResultsView.Refresh(); - ResultsCount = SearchResultsView.Count; + _allEntries = files?.ToList() ?? new List(); + RefFile = refFile; + _ = UpdateResultsAsync(); } - public void ChangeCollection(IEnumerable files, GameFile refFile = null) + public async Task RefreshFilter() { - SearchResults.Clear(); - SearchResults.AddRange(files); - RefFile = refFile; - ResultsCount = SearchResultsView.Count; + await UpdateResultsAsync(); } public async Task CycleSortSizeMode() @@ -96,50 +91,110 @@ public async Task CycleSortSizeMode() ESortSizeMode.Descending => ESortSizeMode.Ascending, _ => ESortSizeMode.None }; + await UpdateResultsAsync(); + } - var sorted = await Task.Run(() => + private async Task UpdateResultsAsync() + { + _updateCts.Cancel(); + var cts = new CancellationTokenSource(); + _updateCts = cts; + var token = cts.Token; + + string filterText = FilterText; + bool regex = HasRegexEnabled; + bool matchCase = HasMatchCaseEnabled; + ESortSizeMode sortMode = CurrentSortSizeMode; + List allEntries = _allEntries; + + try { - var archiveDict = SearchResults - .OfType() - .Select(f => f.Vfs.Name) - .Distinct() - .Select((name, idx) => (name, idx)) - .ToDictionary(x => x.name, x => x.idx); - - var keyed = SearchResults.Select(f => + var filtered = await Task.Run(() => { - int archiveKey = f is VfsEntry ve && archiveDict.TryGetValue(ve.Vfs.Name, out var key) ? key : -1; - return (File: f, f.Size, ArchiveKey: archiveKey); - }); + return FilterAndSort(allEntries, filterText, regex, matchCase, sortMode, token); + }, token).ConfigureAwait(false); - return CurrentSortSizeMode switch + if (cts != _updateCts) + return; + + await Application.Current.Dispatcher.InvokeAsync(() => { - ESortSizeMode.Ascending => keyed - .OrderBy(x => x.Size).ThenBy(x => x.ArchiveKey) - .Select(x => x.File).ToList(), - ESortSizeMode.Descending => keyed - .OrderByDescending(x => x.Size).ThenBy(x => x.ArchiveKey) - .Select(x => x.File).ToList(), - _ => keyed - .OrderBy(x => x.ArchiveKey).ThenBy(x => x.File.Path, StringComparer.OrdinalIgnoreCase) - .Select(x => x.File).ToList() - }; - }); - - SearchResults.Clear(); - SearchResults.AddRange(sorted); + SearchResults.Clear(); + SearchResults.AddRange(filtered); + ResultsCount = SearchResults.Count; + }); + } + catch (OperationCanceledException) + { + // Ignore + } } - private bool ItemFilter(object item, IEnumerable filters) + private static List FilterAndSort( + List entries, + string filterText, + bool regex, + bool matchCase, + ESortSizeMode sortMode, + CancellationToken token) { - if (item is not GameFile entry) - return true; - - if (!HasRegexEnabled) - return filters.All(x => entry.Path.Contains(x, HasMatchCaseEnabled ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)); + if (entries.Count == 0) + return new List(); - var o = RegexOptions.None; - if (!HasMatchCaseEnabled) o |= RegexOptions.IgnoreCase; - return new Regex(FilterText, o).Match(entry.Path).Success; + IEnumerable filtered = entries; + if (!string.IsNullOrWhiteSpace(filterText)) + { + if (regex) + { + var options = RegexOptions.None; + if (!matchCase) options |= RegexOptions.IgnoreCase; + var regexObj = new Regex(filterText, options | RegexOptions.Compiled); + filtered = entries.Where(f => regexObj.IsMatch(f.Path)); + } + else + { + var filters = filterText.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries); + var comparison = matchCase ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + filtered = entries.Where(f => filters.All(x => f.Path.Contains(x, comparison))); + } + } + + var sortedList = filtered.ToList(); + if (token.IsCancellationRequested) + return sortedList; + + var archiveDict = sortedList + .OfType() + .Select(f => f.Vfs.Name) + .Distinct() + .Select((name, idx) => (name, idx)) + .ToDictionary(x => x.name, x => x.idx); + + int GetArchiveIndex(GameFile f) => + f is VfsEntry ve && archiveDict.TryGetValue(ve.Vfs.Name, out var idx) ? idx : -1; + + switch (sortMode) + { + case ESortSizeMode.Ascending: + sortedList = sortedList + .OrderBy(f => f.Size) + .ThenBy(f => GetArchiveIndex(f)) + .ToList(); + break; + case ESortSizeMode.Descending: + sortedList = sortedList + .OrderByDescending(f => f.Size) + .ThenBy(f => GetArchiveIndex(f)) + .ToList(); + break; + default: + sortedList = sortedList + .OrderBy(f => GetArchiveIndex(f)) + .ThenBy(f => f.Path, StringComparer.OrdinalIgnoreCase) + .ToList(); + break; + } + + return sortedList; } } diff --git a/FModel/Views/SearchView.xaml b/FModel/Views/SearchView.xaml index 01d300358..10c4e92b1 100644 --- a/FModel/Views/SearchView.xaml +++ b/FModel/Views/SearchView.xaml @@ -91,7 +91,7 @@ + ScrollViewer.CanContentScroll="True" ItemsSource="{Binding SearchTab.SearchResults, IsAsync=True}"> @@ -453,10 +454,10 @@ diff --git a/FModel/Views/SearchView.xaml.cs b/FModel/Views/SearchView.xaml.cs index e1499a398..e27d4ef31 100644 --- a/FModel/Views/SearchView.xaml.cs +++ b/FModel/Views/SearchView.xaml.cs @@ -5,6 +5,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Input; +using System.Windows.Threading; using CUE4Parse.FileProvider.Objects; using FModel.Services; using FModel.ViewModels; @@ -19,15 +20,21 @@ public enum ESearchViewTab public partial class SearchView { + private static readonly TimeSpan AutoSearchDelay = TimeSpan.FromMilliseconds(200); + private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; private SearchViewModel _searchViewModel => _applicationView.CUE4Parse.SearchVm; private SearchViewModel _refViewModel => _applicationView.CUE4Parse.RefVm; private ESearchViewTab _currentTab = ESearchViewTab.SearchView; + private readonly DispatcherTimer _autoSearchTimer; + private SearchViewModel _pendingAutoSearch; public SearchView() { + _autoSearchTimer = new DispatcherTimer { Interval = AutoSearchDelay }; + _autoSearchTimer.Tick += OnAutoSearchTimerTick; DataContext = new { mainApplication = _applicationView, @@ -95,9 +102,30 @@ private void OnDeleteSearchClick(object sender, RoutedEventArgs e) if (viewModel == null) return; viewModel.FilterText = string.Empty; + CancelAutoSearch(); viewModel.RefreshFilter(); } + private void OnSearchTextChanged(object sender, TextChangedEventArgs e) + { + _pendingAutoSearch = ReferenceEquals(sender, RefSearchTextBox) ? _refViewModel : _searchViewModel; + _autoSearchTimer.Stop(); + _autoSearchTimer.Start(); + } + + private void OnAutoSearchTimerTick(object sender, EventArgs e) + { + var viewModel = _pendingAutoSearch; + CancelAutoSearch(); + viewModel?.RefreshFilter(); + } + + private void CancelAutoSearch() + { + _autoSearchTimer.Stop(); + _pendingAutoSearch = null; + } + private SearchViewModel CurrentViewModel => _currentTab switch { ESearchViewTab.SearchView => _applicationView.CUE4Parse.SearchVm, @@ -182,9 +210,17 @@ private void OnWindowKeyDown(object sender, KeyEventArgs e) { if (e.Key != Key.Enter) return; + + CancelAutoSearch(); CurrentViewModel?.RefreshFilter(); } + private void OnWindowClosed(object sender, EventArgs e) + { + CancelAutoSearch(); + _autoSearchTimer.Tick -= OnAutoSearchTimerTick; + } + private void OnStateChanged(object sender, EventArgs e) { switch (WindowState) From 009ac7b2c7c4ddfeb459269bd805dc84e5464e28 Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:32:13 +1000 Subject: [PATCH 08/17] removed debug code and worded information log better --- FModel/MainWindow.xaml.cs | 6 ------ FModel/ViewModels/Commands/LoadCommand.cs | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index 88d1cb82a..7ef1fd208 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -131,12 +131,6 @@ await Task.WhenAll( }), UserSettings.Default.DecompileLua ? ApplicationViewModel.InitUnluac() : Task.CompletedTask ).ConfigureAwait(false); - -#if DEBUG - // await _threadWorkerView.Begin(cancellationToken => - // _applicationView.CUE4Parse.Extract(cancellationToken, - // _applicationView.CUE4Parse.Provider["Marvel/Content/Marvel/Wwise/Assets/Events/Music/music_new/event/Entry.uasset"])); -#endif } private void OnGridSplitterDoubleClick(object sender, MouseButtonEventArgs e) diff --git a/FModel/ViewModels/Commands/LoadCommand.cs b/FModel/ViewModels/Commands/LoadCommand.cs index dba6eb4b1..77bce9ad1 100644 --- a/FModel/ViewModels/Commands/LoadCommand.cs +++ b/FModel/ViewModels/Commands/LoadCommand.cs @@ -175,7 +175,7 @@ private void FilterNewOrModifiedFilesToDisplay(CancellationToken cancellationTok if (!openFileDialog.ShowDialog().GetValueOrDefault()) return; FLogger.Append(ELog.Information, () => - FLogger.Text($"Backup file older than current game is '{openFileDialog.FileName.SubstringAfterLast("\\")}'", Constants.WHITE, true)); + FLogger.Text($"Loaded old backup file '{openFileDialog.FileName.SubstringAfterLast("\\")}'", Constants.WHITE, true)); var mode = UserSettings.Default.LoadingMode; var entries = ParseBackup(openFileDialog.FileName, mode, cancellationToken); From 0c48750a71bb7eb15fe43d71f8a3b6a22cb1993d Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:39:39 +1000 Subject: [PATCH 09/17] prevent these from being shown on loose and null archives --- FModel/MainWindow.xaml | 91 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 85 insertions(+), 6 deletions(-) diff --git a/FModel/MainWindow.xaml b/FModel/MainWindow.xaml index 4a9582d11..176566575 100644 --- a/FModel/MainWindow.xaml +++ b/FModel/MainWindow.xaml @@ -295,6 +295,16 @@ + + + @@ -307,14 +317,83 @@ - - + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + From cc577f53144871bd47e34ee3d70b74e81bb7f6c0 Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:59:24 +1000 Subject: [PATCH 10/17] All Installed Steam games now automatically get added UE only ofc --- FModel/ViewModels/GameSelectorViewModel.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/FModel/ViewModels/GameSelectorViewModel.cs b/FModel/ViewModels/GameSelectorViewModel.cs index eb288ffe8..35afe7fee 100644 --- a/FModel/ViewModels/GameSelectorViewModel.cs +++ b/FModel/ViewModels/GameSelectorViewModel.cs @@ -218,6 +218,14 @@ private IEnumerable EnumerateDetectedGames() yield return GetRockstarGamesGame("GTA San Andreas - Definitive Edition", "\\Gameface\\Content\\Paks", EGame.GAME_GTATheTrilogyDefinitiveEdition); yield return GetRockstarGamesGame("GTA Vice City - Definitive Edition", "\\Gameface\\Content\\Paks", EGame.GAME_GTATheTrilogyDefinitiveEdition); yield return GetLevelInfiniteGame("tof_launcher", "\\Hotta\\Content\\Paks", EGame.GAME_TowerOfFantasy); + + foreach (var game in SteamDetection.GetSteamGames()) + { + if (!TryDetectUeVersion(game.GameRoot, out var ueVersion, out var detectedDir)) + continue; + + yield return DirectorySettings.Default(game.Name, detectedDir ?? game.GameRoot, ue: ueVersion); + } } private LauncherInstalled _launcherInstalled; @@ -342,6 +350,7 @@ private class LauncherInstalled private class Installation { public string InstallLocation; + public string NamespaceId; public string AppName; public string AppVersion; } @@ -395,6 +404,7 @@ static SteamDetection() public static AppInfo GetSteamGameById(int id) => _steamApps.FirstOrDefault(app => app.Id == id.ToString()); + public static IEnumerable GetSteamApps() => _steamApps; private static List GetSteamApps(IEnumerable steamLibs) { var apps = new List(); @@ -409,6 +419,17 @@ private static List GetSteamApps(IEnumerable steamLibs) return apps; } + public static IEnumerable GetSteamGames() + { + foreach (var app in GetSteamApps()) + { + if (!Directory.Exists(app.GameRoot) || !Directory.EnumerateDirectories(app.GameRoot, "Paks", SearchOption.AllDirectories).Any()) // TODO: remove paks check and maybe do a better way + continue; + + yield return app; + } + } + private static AppInfo GetAppInfo(string appMetaFile) { var fileDataLines = File.ReadAllLines(appMetaFile); From 0763d2722962c832da59c450d2add6fe6daf4002 Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:59:57 +1000 Subject: [PATCH 11/17] No more export name under textures, only show when texture name is different --- FModel/ViewModels/TabControlViewModel.cs | 5 +++++ FModel/Views/Resources/Resources.xaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/FModel/ViewModels/TabControlViewModel.cs b/FModel/ViewModels/TabControlViewModel.cs index 0422e6d9b..25b859e6c 100644 --- a/FModel/ViewModels/TabControlViewModel.cs +++ b/FModel/ViewModels/TabControlViewModel.cs @@ -245,6 +245,7 @@ public TabImage SelectedImage if (_selectedImage == value) return; SetProperty(ref _selectedImage, value); RaisePropertyChanged("HasImage"); + RaisePropertyChanged("ShowExportName"); RaisePropertyChanged("Page"); } } @@ -252,6 +253,10 @@ public TabImage SelectedImage public string Header => $"{Entry.Name}{(string.IsNullOrEmpty(TitleExtra) ? "" : $" ({TitleExtra})")}"; public bool HasImage => SelectedImage != null; + public bool ShowExportName => HasImage && !string.Equals( + Path.GetFileNameWithoutExtension(SelectedImage.ExportName), + Path.GetFileNameWithoutExtension(Entry.Name), + StringComparison.OrdinalIgnoreCase); public bool HasMultipleImages => _images.Count > 1; public string Page => $"{_images.IndexOf(_selectedImage) + 1} / {_images.Count}"; diff --git a/FModel/Views/Resources/Resources.xaml b/FModel/Views/Resources/Resources.xaml index 53cefe2bd..b3d4aba42 100644 --- a/FModel/Views/Resources/Resources.xaml +++ b/FModel/Views/Resources/Resources.xaml @@ -792,7 +792,7 @@ + Visibility="{Binding SelectedItem.ShowExportName, ElementName=TabControlName, Converter={StaticResource BoolToVisibilityConverter}}" /> From 9213566b8ad35f44e0c8881891b57156a2156df9 Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Wed, 19 Aug 2026 08:01:06 +1000 Subject: [PATCH 12/17] update cue4parse --- CUE4Parse | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CUE4Parse b/CUE4Parse index c99a1d6df..bfd4e2cf1 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit c99a1d6dfc269281c2a7bf4dc36f8664dd8791e6 +Subproject commit bfd4e2cf1fb2bc6ef83b5e5b0e1e870e732ffccb From 9296a96ce8babc6778571981eae30558a4e0fc93 Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Wed, 19 Aug 2026 12:23:05 +1000 Subject: [PATCH 13/17] more proper check + Epic games launcher games plus first 30 only to prevent lag --- FModel/ViewModels/GameSelectorViewModel.cs | 41 ++++++++++++++++++---- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/FModel/ViewModels/GameSelectorViewModel.cs b/FModel/ViewModels/GameSelectorViewModel.cs index 35afe7fee..f174d9735 100644 --- a/FModel/ViewModels/GameSelectorViewModel.cs +++ b/FModel/ViewModels/GameSelectorViewModel.cs @@ -45,10 +45,15 @@ public DirectorySettings SelectedDirectory private readonly ObservableCollection _detectedDirectories; public ReadOnlyObservableCollection DetectedDirectories { get; } public ReadOnlyObservableCollection UeGames { get; } - + private readonly LauncherInstalled _launcherInstalled; public GameSelectorViewModel(string gameDirectory) { - _detectedDirectories = new ObservableCollection(EnumerateDetectedGames().Where(x => x != null)); + _launcherInstalled = GetDriveLauncherInstalls("ProgramData\\Epic\\UnrealEngineLauncher\\LauncherInstalled.dat"); + _detectedDirectories = new ObservableCollection(EnumerateDetectedGames() + .Where(x => x != null) + .GroupBy(x => x.GameDirectory, StringComparer.OrdinalIgnoreCase) + .Select(g => g.First())); + foreach (var dir in UserSettings.Default.PerDirectory.Values.Where(x => x.IsManual)) { _detectedDirectories.Add((DirectorySettings) dir.Clone()); @@ -92,6 +97,13 @@ private bool TryDetectUeVersion(string gameDirectory, out EGame ueVersion, [Mayb Log.Warning("Selected directory \"{GameDirectory}\" does not end with \"Paks\". Looking in \"{PaksDir}\" instead.", targetGameDir, paksDir); targetGameDir = paksDir; } + else + { + Log.Warning("No Paks folder found under \"{GameDirectory}\".", gameDirectory); + ueVersion = EGame.GAME_UE4_LATEST; + newGameDirectory = targetGameDir; + return false; + } if (Directory.GetFiles(gameDirectory, "*.exe") is { Length: 1 } exe && TryGetUeVersionFromExe(exe[0], out ueVersion)) { @@ -189,6 +201,11 @@ public void DeleteSelectedGame() SelectedDirectory = DetectedDirectories.Last(); } + private static bool IsUuidNamespace(string ns) + { + return Guid.TryParseExact(ns, "N", out _); + } + private IEnumerable EnumerateUeGames() => Enum.GetValues() .GroupBy(value => (int)value) @@ -219,7 +236,21 @@ private IEnumerable EnumerateDetectedGames() yield return GetRockstarGamesGame("GTA Vice City - Definitive Edition", "\\Gameface\\Content\\Paks", EGame.GAME_GTATheTrilogyDefinitiveEdition); yield return GetLevelInfiniteGame("tof_launcher", "\\Hotta\\Content\\Paks", EGame.GAME_TowerOfFantasy); - foreach (var game in SteamDetection.GetSteamGames()) + foreach (var install in (_launcherInstalled?.InstallationList ?? []).Take(30)) // First 30 games only if a user has hundreds, it will be slow. + { + if (!IsUuidNamespace(install.NamespaceId) || !IsUuidNamespace(install.AppName)) // No official Epic games apps / apps with no name (needs api calls) + continue; + + if (!Directory.Exists(install.InstallLocation)) + continue; + + if (!TryDetectUeVersion(install.InstallLocation, out var ueVersion, out var detectedDir)) + continue; + + yield return DirectorySettings.Default(install.AppName, detectedDir ?? install.InstallLocation, ue: ueVersion); + } + + foreach (var game in SteamDetection.GetSteamGames().Take(30)) { if (!TryDetectUeVersion(game.GameRoot, out var ueVersion, out var detectedDir)) continue; @@ -228,10 +259,8 @@ private IEnumerable EnumerateDetectedGames() } } - private LauncherInstalled _launcherInstalled; private DirectorySettings GetUnrealEngineGame(string gameName, string pakDirectory, EGame ueVersion) { - _launcherInstalled ??= GetDriveLauncherInstalls("ProgramData\\Epic\\UnrealEngineLauncher\\LauncherInstalled.dat"); if (_launcherInstalled?.InstallationList != null) { foreach (var installationList in _launcherInstalled.InstallationList) @@ -423,7 +452,7 @@ public static IEnumerable GetSteamGames() { foreach (var app in GetSteamApps()) { - if (!Directory.Exists(app.GameRoot) || !Directory.EnumerateDirectories(app.GameRoot, "Paks", SearchOption.AllDirectories).Any()) // TODO: remove paks check and maybe do a better way + if (!Directory.Exists(app.GameRoot)) continue; yield return app; From 156d64e845fb5e63e321ddf4a3a56581c89a8c9b Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Thu, 20 Aug 2026 05:59:36 +1000 Subject: [PATCH 14/17] Added PerMonitorV2 and commented a warning PerMonitorV2 should fix all issues with multiple monitors, such as moving fmodel from a big screen to a small one won't resize it. --- FModel/FModel.csproj | 1 + FModel/ViewModels/GameSelectorViewModel.cs | 2 +- FModel/app.manifest | 10 ++++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 FModel/app.manifest diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj index 1bfc1c33e..2d6d27903 100644 --- a/FModel/FModel.csproj +++ b/FModel/FModel.csproj @@ -15,6 +15,7 @@ true true FModel.App + app.manifest diff --git a/FModel/ViewModels/GameSelectorViewModel.cs b/FModel/ViewModels/GameSelectorViewModel.cs index f174d9735..e142bbfcc 100644 --- a/FModel/ViewModels/GameSelectorViewModel.cs +++ b/FModel/ViewModels/GameSelectorViewModel.cs @@ -99,7 +99,7 @@ private bool TryDetectUeVersion(string gameDirectory, out EGame ueVersion, [Mayb } else { - Log.Warning("No Paks folder found under \"{GameDirectory}\".", gameDirectory); + // Log.Warning("No Paks folder found under \"{GameDirectory}\".", gameDirectory); ueVersion = EGame.GAME_UE4_LATEST; newGameDirectory = targetGameDir; return false; diff --git a/FModel/app.manifest b/FModel/app.manifest new file mode 100644 index 000000000..c55171bf3 --- /dev/null +++ b/FModel/app.manifest @@ -0,0 +1,10 @@ + + + + + + true/PM + PerMonitorV2 + + + From 0525179d0aed6bbd01a4b5384db8c5204fec3e33 Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Sun, 23 Aug 2026 07:07:58 +1000 Subject: [PATCH 15/17] Improvements --- .github/workflows/main.yml | 2 +- FModel/FModel.csproj | 224 ++----------------------------------- 2 files changed, 13 insertions(+), 213 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f0389930b..09f4d0d28 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,7 +30,7 @@ jobs: run: dotnet restore FModel - name: .NET Publish - run: dotnet publish FModel -c Release --no-self-contained -r win-x64 -f net8.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false -p:AssemblyVersion=${{ github.event.inputs.appVersion }} -p:FileVersion=${{ github.event.inputs.appVersion }} + run: dotnet publish FModel -c Release --no-self-contained -r win-x64 -f net10.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false -p:AssemblyVersion=${{ github.event.inputs.appVersion }} -p:FileVersion=${{ github.event.inputs.appVersion }} - name: ZIP File uses: papeloto/action-zip@v1.2 diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj index 2d6d27903..3b6e5033c 100644 --- a/FModel/FModel.csproj +++ b/FModel/FModel.csproj @@ -5,26 +5,22 @@ net10.0-windows true FModel.ico + app.manifest 4.4.4.0 - 4.4.4.0 - 4.4.4.0 false true - win-x64 x64 true - true + enable + en + en-US + NU1701 FModel.App - app.manifest - - - - 1701;1702;NU1701 - + true - NU1701 + true @@ -32,127 +28,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + @@ -167,6 +45,7 @@ + @@ -186,88 +65,9 @@ - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 45b8279ed1f025da436e8eba35e018b2a4520708 Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Sun, 23 Aug 2026 08:57:38 +1000 Subject: [PATCH 16/17] removed app manifest --- FModel/FModel.csproj | 1 - FModel/app.manifest | 10 ---------- 2 files changed, 11 deletions(-) delete mode 100644 FModel/app.manifest diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj index 3b6e5033c..71000b161 100644 --- a/FModel/FModel.csproj +++ b/FModel/FModel.csproj @@ -5,7 +5,6 @@ net10.0-windows true FModel.ico - app.manifest 4.4.4.0 false true diff --git a/FModel/app.manifest b/FModel/app.manifest deleted file mode 100644 index c55171bf3..000000000 --- a/FModel/app.manifest +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - true/PM - PerMonitorV2 - - - From 7cf8e39f69359536fa36f15b5372bf3d5c59eaa7 Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Sun, 23 Aug 2026 09:05:45 +1000 Subject: [PATCH 17/17] misc --- FModel/MainWindow.xaml.cs | 2 +- FModel/ViewModels/AudioPlayerViewModel.cs | 49 +++++++++++++----- FModel/ViewModels/CUE4ParseViewModel.cs | 3 +- FModel/ViewModels/TabControlViewModel.cs | 23 +++++++-- FModel/Views/ImageMerger.xaml.cs | 51 +++++++++++++++---- .../Resources/Controls/AvalonEditor.xaml.cs | 10 +++- FModel/Views/SearchView.xaml | 2 +- 7 files changed, 109 insertions(+), 31 deletions(-) diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index 7ef1fd208..693bf6ad4 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -140,7 +140,7 @@ private void OnGridSplitterDoubleClick(object sender, MouseButtonEventArgs e) private void OnWindowKeyDown(object sender, KeyEventArgs e) { - if (e.OriginalSource is TextBox || e.OriginalSource is TextArea && Keyboard.Modifiers.HasFlag(ModifierKeys.Control)) + if (e.OriginalSource is TextBox || (e.OriginalSource is TextArea && Keyboard.Modifiers.HasFlag(ModifierKeys.Control))) return; if (_threadWorkerView.CanBeCanceled && e.Key == Key.Escape) diff --git a/FModel/ViewModels/AudioPlayerViewModel.cs b/FModel/ViewModels/AudioPlayerViewModel.cs index a630097ef..e014351f0 100644 --- a/FModel/ViewModels/AudioPlayerViewModel.cs +++ b/FModel/ViewModels/AudioPlayerViewModel.cs @@ -233,6 +233,8 @@ public void Load() { Application.Current.Dispatcher.Invoke(() => { + _sourceTimer ??= new Timer(TimerTick, null, 0, 10); + if (!ConvertIfNeeded()) return; @@ -521,11 +523,10 @@ public void Dispose() _waveSource = null; } - if (_soundOut != null) - { - _soundOut.Dispose(); - _soundOut = null; - } + ClearSoundOut(); + + _sourceTimer?.Dispose(); + _sourceTimer = null; if (Spectrum != null) Spectrum = null; @@ -568,6 +569,8 @@ private void TimerTick(object state) private void LoadSoundOut() { if (_waveSource == null) return; + + ClearSoundOut(); _soundOut = new WasapiOut(true, AudioClientShareMode.Shared, 100, ThreadPriority.Highest) { Device = SelectedAudioDevice }; _soundOut.Initialize(_waveSource.ToSampleSource().ToWaveSource(16)); _soundOut.Volume = UserSettings.Default.AudioPlayerVolume / 100; @@ -575,6 +578,10 @@ private void LoadSoundOut() private void ClearSoundOut() { + if (_soundOut == null) return; + + _soundOut.Stop(); + _soundOut.Dispose(); _soundOut = null; } @@ -762,17 +769,33 @@ private static bool TryConvertToWav(string inputFilePath, byte[] inputFileData, UseShellExecute = false, CreateNoWindow = true }); - process?.WaitForExit(5000); + using (process) + { + var exited = process != null && process.WaitForExit(5000); + if (!exited) + { + try + { + process?.Kill(); + process?.WaitForExit(2000); + Log.Warning("Audio process timed out and was killed"); + } + catch + { + // Ignore + } + } - File.Delete(tempfile); + File.Delete(tempfile); - var success = process?.ExitCode == 0 && File.Exists(tempWavFilePath); - if (success) - { - File.Move(tempWavFilePath, wavFilePath, true); - } + var success = exited && process.ExitCode == 0 && File.Exists(tempWavFilePath); + if (success) + { + File.Move(tempWavFilePath, wavFilePath, true); + } - return success; + return success; + } } private static string TryGetVgmstreamPath() diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 57f09bf73..63b23a45e 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -703,9 +703,8 @@ private void BulkFolder(CancellationToken cancellationToken, TreeItem folder, Ac public void ExportFolder(CancellationToken cancellationToken, TreeItem folder) { - Parallel.ForEach(folder.AssetsList.Assets, entry => + Parallel.ForEach(folder.AssetsList.Assets, new ParallelOptions { CancellationToken = cancellationToken }, entry => { - cancellationToken.ThrowIfCancellationRequested(); ExportData(entry.Asset); }); diff --git a/FModel/ViewModels/TabControlViewModel.cs b/FModel/ViewModels/TabControlViewModel.cs index 25b859e6c..581a02407 100644 --- a/FModel/ViewModels/TabControlViewModel.cs +++ b/FModel/ViewModels/TabControlViewModel.cs @@ -43,6 +43,23 @@ public TabImage(string name, bool rnn, CTexture img) SetImage(img); } + private static readonly string[] KnownImageExtensions = [".png", ".jpg", ".jpeg", ".jpe", ".jfif", ".bmp", ".tif", ".tiff", ".webp", ".dds", ".hdr", ".exr"]; + + // prevents T_Slop.png.jpg + private void SetExportExtension(string extension) + { + var name = ExportName; + foreach (var ext in KnownImageExtensions) + { + if (name.EndsWith(ext, StringComparison.OrdinalIgnoreCase)) + { + name = name[..^ext.Length]; + break; + } + } + ExportName = $"{name}.{extension}"; + } + private BitmapImage _image; public BitmapImage Image { @@ -82,7 +99,7 @@ private void SetImage(SKBitmap bitmap) } _bmp = bitmap; - ExportName += "." + (NoAlpha ? "jpg" : "png"); + SetExportExtension(NoAlpha ? "jpg" : "png"); using var data = _bmp.Encode(NoAlpha ? SKEncodedImageFormat.Jpeg : SKEncodedImageFormat.Png, 100); using var stream = new MemoryStream(ImageBuffer = data.ToArray(), false); var image = new BitmapImage(); @@ -109,12 +126,12 @@ private void SetImage(CTexture bitmap) if (PixelFormatUtils.IsHDR(bitmap.PixelFormat) || (UserSettings.Default.TextureExportFormat != ETextureFormat.Jpeg && UserSettings.Default.TextureExportFormat != ETextureFormat.Png)) { ImageBuffer = bitmap.Encode(UserSettings.Default.TextureExportFormat, UserSettings.Default.SaveHdrTexturesAsHdr, out var ext); - ExportName += "." + ext; + SetExportExtension(ext); } else { ImageBuffer = imageData; - ExportName += "." + (NoAlpha || UserSettings.Default.TextureExportFormat == ETextureFormat.Jpeg ? "jpg" : "png"); + SetExportExtension(NoAlpha || UserSettings.Default.TextureExportFormat == ETextureFormat.Jpeg ? "jpg" : "png"); } using var stream = new MemoryStream(imageData); diff --git a/FModel/Views/ImageMerger.xaml.cs b/FModel/Views/ImageMerger.xaml.cs index 9e1b2236f..5544fae9f 100644 --- a/FModel/Views/ImageMerger.xaml.cs +++ b/FModel/Views/ImageMerger.xaml.cs @@ -44,6 +44,13 @@ private async void Click_DrawPreview(object sender, MouseButtonEventArgs e) private async Task DrawPreview() { + if (ImagesListBox.Items.Count == 0) + { + ImagePreview.Source = null; + _imageBuffer = null; + return; + } + AddButton.IsEnabled = false; UpButton.IsEnabled = false; DownButton.IsEnabled = false; @@ -60,21 +67,36 @@ private async Task DrawPreview() for (var i = 0; i < images.Length; i++) { var item = (ListBoxItem) ImagesListBox.Items[i]; - var ms = new MemoryStream(); - var stream = new FileStream(item.ContentStringFormat, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - if (item.ContentStringFormat.EndsWith(".tif")) + SKBitmap image; + await using (var ms = new MemoryStream()) { - await using var tmp = new MemoryStream(); - await stream.CopyToAsync(tmp); - Image.FromStream(tmp).Save(ms, ImageFormat.Png); + using (var stream = new FileStream(item.ContentStringFormat, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + if (item.ContentStringFormat.EndsWith(".tif")) + { + await using var tmp = new MemoryStream(); + await stream.CopyToAsync(tmp); + tmp.Position = 0; + using var drawing = Image.FromStream(tmp); + drawing.Save(ms, ImageFormat.Png); + } + else + { + await stream.CopyToAsync(ms); + } + } + + image = SKBitmap.Decode(ms.ToArray()); } - else + + if (image == null) { - await stream.CopyToAsync(ms); + Log.Warning("Image merger skipped an undecodable file: {File}", item.ContentStringFormat); + images[i] = null; + continue; } - var image = SKBitmap.Decode(ms.ToArray()); positions[i] = new SKPoint(curW, curH); images[i] = image; @@ -109,6 +131,9 @@ await Task.Run(() => for (var i = 0; i < images.Length; i++) { + if (images[i] == null) + continue; + using (images[i]) { canvas.DrawBitmap(images[i], positions[i], new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); @@ -186,7 +211,6 @@ private async void ModifyItemInList(object sender, RoutedEventArgs e) } } - ImagesListBox.SelectedItems.Add(indices); if (reloadImage) { await DrawPreview().ConfigureAwait(false); @@ -224,6 +248,13 @@ private async void ModifyItemInList(object sender, RoutedEventArgs e) ImagesListBox.Items.Remove(ImagesListBox.SelectedItems[i]); } + if (ImagesListBox.Items.Count == 0) + { + ImagePreview.Source = null; + _imageBuffer = null; + break; + } + await DrawPreview().ConfigureAwait(false); break; diff --git a/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs b/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs index 42c3b001e..0dbf569dd 100644 --- a/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs +++ b/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs @@ -228,7 +228,15 @@ private void OnCloseClick(object sender, RoutedEventArgs e) private void OnTabClose(object sender, EventArgs eventArgs) { - if (eventArgs is not TabControlViewModel.TabEventArgs e || e.TabToRemove.Document?.FileName is not { } fileName) + if (eventArgs is not TabControlViewModel.TabEventArgs e) + return; + + if (ReferenceEquals(e.TabToRemove, DataContext)) + { + ApplicationService.ApplicationView.CUE4Parse.TabControl.OnTabRemove -= OnTabClose; + } + + if (e.TabToRemove.Document?.FileName is not { } fileName) return; if (_savedCarets.ContainsKey(fileName)) diff --git a/FModel/Views/SearchView.xaml b/FModel/Views/SearchView.xaml index f28ce4094..84daee836 100644 --- a/FModel/Views/SearchView.xaml +++ b/FModel/Views/SearchView.xaml @@ -450,7 +450,7 @@ - +