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 @@
-
+