From 93fa0b2bb6be78f507defbec4aed99bd3ee4f4e7 Mon Sep 17 00:00:00 2001 From: Asval Date: Sun, 9 Feb 2025 00:40:11 +0100 Subject: [PATCH 1/5] added tab extra title --- FModel/Extensions/StringExtensions.cs | 28 ++++++++++--------- FModel/ViewModels/CUE4ParseViewModel.cs | 5 +++- FModel/ViewModels/Commands/ImageCommand.cs | 22 +++++++-------- FModel/ViewModels/Commands/TabCommand.cs | 26 ++++++++--------- FModel/ViewModels/TabControlViewModel.cs | 22 +++++++++++++-- .../Controls/Aed/GamePathVisualLineText.cs | 28 ++++++------------- .../Resources/Controls/AvalonEditor.xaml.cs | 2 ++ FModel/Views/Resources/Resources.xaml | 2 +- 8 files changed, 75 insertions(+), 60 deletions(-) diff --git a/FModel/Extensions/StringExtensions.cs b/FModel/Extensions/StringExtensions.cs index 07d6873c..cc8b523c 100644 --- a/FModel/Extensions/StringExtensions.cs +++ b/FModel/Extensions/StringExtensions.cs @@ -6,14 +6,14 @@ using ICSharpCode.AvalonEdit.Document; namespace FModel.Extensions; -public static class StringExtensions +public static partial class StringExtensions { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string GetReadableSize(double size) { if (size == 0) return "0 B"; - string[] sizes = { "B", "KB", "MB", "GB", "TB" }; + string[] sizes = ["B", "KB", "MB", "GB", "TB"]; var order = 0; while (size >= 1024 && order < sizes.Length - 1) { @@ -27,21 +27,22 @@ public static class StringExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetNameLineNumber(this string s, string lineToFind) { + if (KismetRegex().IsMatch(lineToFind)) + return s.GetKismetLineNumber(lineToFind); if (int.TryParse(lineToFind, out var index)) return s.GetLineNumber(index); lineToFind = $" \"Name\": \"{lineToFind}\","; using var reader = new StringReader(s); var lineNum = 0; - string line; - while ((line = reader.ReadLine()) != null) + while (reader.ReadLine() is { } line) { lineNum++; if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase)) return lineNum; } - return 1; + return -1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -64,18 +65,17 @@ public static class StringExtensions } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetKismetLineNumber(this string s, string input) + private static int GetKismetLineNumber(this string s, string input) { - var match = Regex.Match(input, @"^(.+)\[(\d+)\]$"); + var match = KismetRegex().Match(input); var name = match.Groups[1].Value; int index = int.Parse(match.Groups[2].Value); var lineToFind = $" \"Name\": \"{name}\","; var offset = $"\"StatementIndex\": {index}"; using var reader = new StringReader(s); var lineNum = 0; - string line; - while ((line = reader.ReadLine()) != null) + while (reader.ReadLine() is { } line) { lineNum++; if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase)) @@ -91,7 +91,7 @@ public static class StringExtensions } } - return 1; + return -1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -99,8 +99,7 @@ public static class StringExtensions { using var reader = new StringReader(s); var lineNum = 0; - string line; - while ((line = reader.ReadLine()) != null) + while (reader.ReadLine() is { } line) { lineNum++; if (line.Equals(" {")) @@ -110,6 +109,9 @@ public static class StringExtensions return lineNum + 1; } - return 1; + return -1; } + + [GeneratedRegex(@"^(.+)\[(\d+)\]$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] + private static partial Regex KismetRegex(); } diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index fdcc086d..63a66d55 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -907,7 +907,10 @@ public class CUE4ParseViewModel : ViewModel { var package = Provider.LoadPackage(entry); - TabControl.AddTab($"{entry.Name} (Metadata)"); + if (TabControl.CanAddTabs) TabControl.AddTab(entry); + else TabControl.SelectedTab.SoftReset(entry); + + TabControl.SelectedTab.TitleExtra = "Metadata"; TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(""); TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(package, Formatting.Indented), false, false); diff --git a/FModel/ViewModels/Commands/ImageCommand.cs b/FModel/ViewModels/Commands/ImageCommand.cs index 264255ac..0201c989 100644 --- a/FModel/ViewModels/Commands/ImageCommand.cs +++ b/FModel/ViewModels/Commands/ImageCommand.cs @@ -14,34 +14,34 @@ public class ImageCommand : ViewModelCommand { } - public override void Execute(TabItem contextViewModel, object parameter) + public override void Execute(TabItem tabViewModel, object parameter) { - if (parameter == null || !contextViewModel.HasImage) return; + if (parameter == null || !tabViewModel.HasImage) return; switch (parameter) { case "Open": { - Helper.OpenWindow(contextViewModel.SelectedImage.ExportName + " (Image)", () => + Helper.OpenWindow(tabViewModel.SelectedImage.ExportName + " (Image)", () => { var popout = new ImagePopout { - Title = contextViewModel.SelectedImage.ExportName + " (Image)", - Width = contextViewModel.SelectedImage.Image.Width, - Height = contextViewModel.SelectedImage.Image.Height, - WindowState = contextViewModel.SelectedImage.Image.Height > 1000 ? WindowState.Maximized : WindowState.Normal, - ImageCtrl = { Source = contextViewModel.SelectedImage.Image } + Title = tabViewModel.SelectedImage.ExportName + " (Image)", + Width = tabViewModel.SelectedImage.Image.Width, + Height = tabViewModel.SelectedImage.Image.Height, + WindowState = tabViewModel.SelectedImage.Image.Height > 1000 ? WindowState.Maximized : WindowState.Normal, + ImageCtrl = { Source = tabViewModel.SelectedImage.Image } }; - RenderOptions.SetBitmapScalingMode(popout.ImageCtrl, BoolToRenderModeConverter.Instance.Convert(contextViewModel.SelectedImage.RenderNearestNeighbor)); + RenderOptions.SetBitmapScalingMode(popout.ImageCtrl, BoolToRenderModeConverter.Instance.Convert(tabViewModel.SelectedImage.RenderNearestNeighbor)); popout.Show(); }); break; } case "Copy": - ClipboardExtensions.SetImage(contextViewModel.SelectedImage.ImageBuffer, $"{contextViewModel.SelectedImage.ExportName}.png"); + ClipboardExtensions.SetImage(tabViewModel.SelectedImage.ImageBuffer, $"{tabViewModel.SelectedImage.ExportName}.png"); break; case "Save": - contextViewModel.SaveImage(); + tabViewModel.SaveImage(); break; } } diff --git a/FModel/ViewModels/Commands/TabCommand.cs b/FModel/ViewModels/Commands/TabCommand.cs index 5abbc4e3..a7911782 100644 --- a/FModel/ViewModels/Commands/TabCommand.cs +++ b/FModel/ViewModels/Commands/TabCommand.cs @@ -15,7 +15,7 @@ public class TabCommand : ViewModelCommand { } - public override async void Execute(TabItem contextViewModel, object parameter) + public override async void Execute(TabItem tabViewModel, object parameter) { switch (parameter) { @@ -23,53 +23,53 @@ public class TabCommand : ViewModelCommand _applicationView.CUE4Parse.TabControl.RemoveTab(mdlClick); break; case "Close_Tab": - _applicationView.CUE4Parse.TabControl.RemoveTab(contextViewModel); + _applicationView.CUE4Parse.TabControl.RemoveTab(tabViewModel); break; case "Close_All_Tabs": _applicationView.CUE4Parse.TabControl.RemoveAllTabs(); break; case "Close_Other_Tabs": - _applicationView.CUE4Parse.TabControl.RemoveOtherTabs(contextViewModel); + _applicationView.CUE4Parse.TabControl.RemoveOtherTabs(tabViewModel); break; case "Asset_Export_Data": - await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.ExportData(contextViewModel.Entry)); + await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.ExportData(tabViewModel.Entry)); break; case "Asset_Save_Properties": await _threadWorkerView.Begin(cancellationToken => { - _applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Entry, false, EBulkType.Properties); + _applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Properties); }); break; case "Asset_Save_Textures": await _threadWorkerView.Begin(cancellationToken => { - _applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Entry, false, EBulkType.Textures); + _applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Textures); }); break; case "Asset_Save_Models": await _threadWorkerView.Begin(cancellationToken => { - _applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Entry, false, EBulkType.Meshes); + _applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Meshes); }); break; case "Asset_Save_Animations": await _threadWorkerView.Begin(cancellationToken => { - _applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Entry, false, EBulkType.Animations); + _applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Animations); }); break; case "Open_Properties": - if (contextViewModel.Entry.Name == "New Tab" || contextViewModel.Document == null) return; - Helper.OpenWindow(contextViewModel.Entry.Name + " (Properties)", () => + if (tabViewModel.Header == "New Tab" || tabViewModel.Document == null) return; + Helper.OpenWindow(tabViewModel.Header + " (Properties)", () => { - new PropertiesPopout(contextViewModel) + new PropertiesPopout(tabViewModel) { - Title = contextViewModel.Entry.Name + " (Properties)" + Title = tabViewModel.Header + " (Properties)" }.Show(); }); break; case "Copy_Asset_Path": - Clipboard.SetText(contextViewModel.Entry.Path); + Clipboard.SetText(tabViewModel.Entry.Path); break; } } diff --git a/FModel/ViewModels/TabControlViewModel.cs b/FModel/ViewModels/TabControlViewModel.cs index 1eda1e1c..39edaced 100644 --- a/FModel/ViewModels/TabControlViewModel.cs +++ b/FModel/ViewModels/TabControlViewModel.cs @@ -97,7 +97,22 @@ public class TabItem : ViewModel public GameFile Entry { get => _entry; - set => SetProperty(ref _entry, value); + set + { + SetProperty(ref _entry, value); + RaisePropertyChanged(nameof(Header)); + } + } + + private string _titleExtra; + public string TitleExtra + { + get => _titleExtra; + set + { + SetProperty(ref _titleExtra, value); + RaisePropertyChanged(nameof(Header)); + } } private bool _hasSearchOpen; @@ -194,6 +209,8 @@ public class TabItem : ViewModel } } + public string Header => $"{Entry.Name}{(string.IsNullOrEmpty(TitleExtra) ? "" : $" ({TitleExtra})")}"; + public bool HasImage => SelectedImage != null; public bool HasMultipleImages => _images.Count > 1; public string Page => $"{_images.IndexOf(_selectedImage) + 1} / {_images.Count}"; @@ -219,6 +236,7 @@ public class TabItem : ViewModel public void SoftReset(GameFile entry) { Entry = entry; + TitleExtra = string.Empty; ParentExportType = string.Empty; ScrollTrigger = null; Application.Current.Dispatcher.Invoke(() => @@ -389,7 +407,7 @@ public class TabControlViewModel : ViewModel public void AddTab(string title) => AddTab(new FakeGameFile(title)); public void AddTab(GameFile entry, string parentExportType = null) { - if (SelectedTab?.Entry.Name == "New Tab") + if (SelectedTab?.Header == "New Tab") { SelectedTab.Entry = entry; return; diff --git a/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs b/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs index 86fdb4d4..d642a47e 100644 --- a/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs +++ b/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs @@ -76,28 +76,18 @@ public class GamePathVisualLineText : VisualLineText if (a.ParentVisualLine.Document.FileName.Equals(fullPath.SubstringBeforeLast('.'), StringComparison.OrdinalIgnoreCase) && !a.ParentVisualLine.Document.GetText(firstLine.Offset, firstLine.Length).Equals(" \"Summary\": {")) // Show Metadata case { - int lineNumber; - DocumentLine line; - - if (Regex.IsMatch(obj, @"^(.+)\[(\d+)\]$")) + var lineNumber = a.ParentVisualLine.Document.Text.GetNameLineNumber(obj); + if (lineNumber > -1) { - lineNumber = a.ParentVisualLine.Document.Text.GetKismetLineNumber(obj); - line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber); - } - else - { - lineNumber = a.ParentVisualLine.Document.Text.GetNameLineNumber(obj); - line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber); + var line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber); + AvalonEditor.YesWeEditor.Select(line.Offset, line.Length); + AvalonEditor.YesWeEditor.ScrollToLine(lineNumber); + return; } + } - AvalonEditor.YesWeEditor.Select(line.Offset, line.Length); - AvalonEditor.YesWeEditor.ScrollToLine(lineNumber); - } - else - { - await _threadWorkerView.Begin(cancellationToken => - _applicationView.CUE4Parse.ExtractAndScroll(cancellationToken, fullPath, obj, parentExportType)); - } + await _threadWorkerView.Begin(cancellationToken => + _applicationView.CUE4Parse.ExtractAndScroll(cancellationToken, fullPath, obj, parentExportType)); }; return a; } diff --git a/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs b/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs index bc5fdab8..cdf50ae0 100644 --- a/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs +++ b/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs @@ -127,6 +127,8 @@ public partial class AvalonEditor if (!tabItem.ShouldScroll) return; var lineNumber = avalonEditor.Document.Text.GetNameLineNumber(tabItem.ScrollTrigger); + if (lineNumber == -1) lineNumber = 1; + var line = avalonEditor.Document.GetLineByNumber(lineNumber); avalonEditor.Select(line.Offset, line.Length); avalonEditor.ScrollToLine(lineNumber); diff --git a/FModel/Views/Resources/Resources.xaml b/FModel/Views/Resources/Resources.xaml index 0a4af432..d7540235 100644 --- a/FModel/Views/Resources/Resources.xaml +++ b/FModel/Views/Resources/Resources.xaml @@ -651,7 +651,7 @@ - + From 71468dca231a5c327ff9f752db18ca179205926a Mon Sep 17 00:00:00 2001 From: Asval Date: Sun, 9 Feb 2025 00:41:47 +0100 Subject: [PATCH 2/5] poc pagination for large packages --- CUE4Parse | 2 +- FModel/Extensions/CUE4ParseExtensions.cs | 46 ++++++++++++++++++++++++ FModel/MainWindow.xaml.cs | 6 ++-- FModel/ViewModels/CUE4ParseViewModel.cs | 22 +++++++----- 4 files changed, 63 insertions(+), 13 deletions(-) create mode 100644 FModel/Extensions/CUE4ParseExtensions.cs diff --git a/CUE4Parse b/CUE4Parse index 11a92870..699dbd44 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 11a92870024a088888aae79c74d8ae0c6c8af3e5 +Subproject commit 699dbd44389e3543e7c09875e0a1b518b0dd05da diff --git a/FModel/Extensions/CUE4ParseExtensions.cs b/FModel/Extensions/CUE4ParseExtensions.cs new file mode 100644 index 00000000..7254564e --- /dev/null +++ b/FModel/Extensions/CUE4ParseExtensions.cs @@ -0,0 +1,46 @@ +using System; +using CUE4Parse.FileProvider; +using CUE4Parse.FileProvider.Objects; +using CUE4Parse.UE4.Assets; + +namespace FModel.Extensions; + +public static class CUE4ParseExtensions +{ + public class LoadPackageResult + { + private const int PaginationThreshold = 5000; + private const int MaxExportPerPage = 1; + + public IPackage Package; + public bool IsPaginated => Package.ExportMapLength >= PaginationThreshold; + + public int InclusiveStart; + public int ExclusiveEnd => IsPaginated + ? Math.Min(InclusiveStart + MaxExportPerPage, Package.ExportMapLength) + : Package.ExportMapLength; + + private int CurrentPage => (InclusiveStart + 1) / MaxExportPerPage; + private int LastPage => Math.Max(1, (Package.ExportMapLength + MaxExportPerPage - 1) / MaxExportPerPage); + private int PageSize => ExclusiveEnd - InclusiveStart; + + public string TabTitleExtra => IsPaginated ? $"Page {CurrentPage}/{LastPage}" : null; + + public object GetDisplayData(bool save = false) => !save ? Package.GetExports(InclusiveStart, PageSize) : Package.GetExports(); + } + + public static LoadPackageResult GetLoadPackageResult(this IFileProvider provider, GameFile file, string objectName = null) + { + var result = new LoadPackageResult { Package = provider.LoadPackage(file) }; + if (result.IsPaginated) + { + result.InclusiveStart = result.Package.GetExportIndex(file.NameWithoutExtension); + if (objectName != null) + { + result.InclusiveStart = int.TryParse(objectName, out var index) ? index : result.Package.GetExportIndex(objectName); + } + } + + return result; + } +} diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index b4200026..23cdc2d2 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -83,9 +83,9 @@ public partial class MainWindow ).ConfigureAwait(false); #if DEBUG - // await _threadWorkerView.Begin(cancellationToken => - // _applicationView.CUE4Parse.Extract(cancellationToken, - // "Marvel/Content/Marvel/Characters/1016/1016501/Meshes/SK_1016_1016501.uasset")); + await _threadWorkerView.Begin(cancellationToken => + _applicationView.CUE4Parse.Extract(cancellationToken, + _applicationView.CUE4Parse.Provider["FortniteGame/Plugins/GameFeatures/BRMapCh6/Content/Maps/Hermes_Terrain.umap"])); // await _threadWorkerView.Begin(cancellationToken => // _applicationView.CUE4Parse.Extract(cancellationToken, // "RED/Content/Chara/ABA/Costume01/Animation/Charaselect/body/stand_body01.uasset")); diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 63a66d55..eed30718 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -571,16 +571,18 @@ public class CUE4ParseViewModel : ViewModel case "uasset": case "umap": { - var pkg = Provider.LoadPackage(entry); + var result = Provider.GetLoadPackageResult(entry); + TabControl.SelectedTab.TitleExtra = result.TabTitleExtra; + if (saveProperties || updateUi) { - TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(pkg.GetExports(), Formatting.Indented), saveProperties, updateUi); + TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(result.GetDisplayData(saveProperties), Formatting.Indented), saveProperties, updateUi); if (saveProperties) break; // do not search for viewable exports if we are dealing with jsons } - for (var i = 0; i < pkg.ExportMapLength; i++) + for (var i = result.InclusiveStart; i < result.ExclusiveEnd; i++) { - if (CheckExport(cancellationToken, pkg, i, bulk)) + if (CheckExport(cancellationToken, result.Package, i, bulk)) break; } @@ -743,13 +745,15 @@ public class CUE4ParseViewModel : ViewModel TabControl.AddTab(entry, parentExportType); TabControl.SelectedTab.ScrollTrigger = objectName; - var pkg = Provider.LoadPackage(entry); - TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(""); // json - TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(pkg.GetExports(), Formatting.Indented), false, false); + var result = Provider.GetLoadPackageResult(entry, objectName); - for (var i = 0; i < pkg.ExportMapLength; i++) + TabControl.SelectedTab.TitleExtra = result.TabTitleExtra; + TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(""); // json + TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(result.GetDisplayData(), Formatting.Indented), false, false); + + for (var i = result.InclusiveStart; i < result.ExclusiveEnd; i++) { - if (CheckExport(cancellationToken, pkg, i)) + if (CheckExport(cancellationToken, result.Package, i)) break; } } From 6dfbd11ad415c80137e53c17d9556f6445687bf3 Mon Sep 17 00:00:00 2001 From: Asval Date: Tue, 11 Feb 2025 19:47:39 +0100 Subject: [PATCH 3/5] focus export check on UWorld for maps --- CUE4Parse | 2 +- FModel/Creator/CreatorPackage.cs | 3 +- FModel/Extensions/CUE4ParseExtensions.cs | 44 ++++++++++++++----- FModel/MainWindow.xaml.cs | 6 +-- FModel/ViewModels/CUE4ParseViewModel.cs | 10 ++--- .../Controls/Aed/GamePathVisualLineText.cs | 2 +- .../Views/Resources/Controls/ImagePopout.xaml | 6 ++- FModel/Views/Snooper/Shading/Material.cs | 2 +- 8 files changed, 51 insertions(+), 24 deletions(-) diff --git a/CUE4Parse b/CUE4Parse index 699dbd44..822dc9bf 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 699dbd44389e3543e7c09875e0a1b518b0dd05da +Subproject commit 822dc9bf39d793a4005c2eb9ba53721c10b791da diff --git a/FModel/Creator/CreatorPackage.cs b/FModel/Creator/CreatorPackage.cs index ee169896..7c2b154b 100644 --- a/FModel/Creator/CreatorPackage.cs +++ b/FModel/Creator/CreatorPackage.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using CUE4Parse.UE4.Assets.Exports; using FModel.Creator.Bases; @@ -30,7 +31,7 @@ public class CreatorPackage : IDisposable } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryConstructCreator(out UCreator creator) + public bool TryConstructCreator([MaybeNullWhen(false)] out UCreator creator) { switch (_exportType) { diff --git a/FModel/Extensions/CUE4ParseExtensions.cs b/FModel/Extensions/CUE4ParseExtensions.cs index 7254564e..463a668e 100644 --- a/FModel/Extensions/CUE4ParseExtensions.cs +++ b/FModel/Extensions/CUE4ParseExtensions.cs @@ -2,6 +2,8 @@ using CUE4Parse.FileProvider; using CUE4Parse.FileProvider.Objects; using CUE4Parse.UE4.Assets; +using CUE4Parse.UE4.Objects.UObject; +using FModel.Settings; namespace FModel.Extensions; @@ -9,35 +11,57 @@ public static class CUE4ParseExtensions { public class LoadPackageResult { + // more than 1 export per page currently break the inner package navigation feature + // if you have 1k exports per page, at page 2, you click on export index 932 + // it will find the export index 932 in the current page, which would realistically be 1932 + // fix would be to use InclusiveStart and ExclusiveEnd to determine the page the export index is in + // giving the document access to this would fix the issue and we could re-use Package instead of reloading it but it's quite a bit of work atm + private const int PaginationThreshold = 5000; private const int MaxExportPerPage = 1; public IPackage Package; + public int RequestedIndex; + public bool IsPaginated => Package.ExportMapLength >= PaginationThreshold; - public int InclusiveStart; + /// + /// index of the first export on the current page + /// this index is the starting point for additional data preview + /// + /// it can be >0 even if is false if we want to focus data preview on a specific export + /// in this case, we will display all exports but only the focused one will be checked for data preview + /// + public int InclusiveStart => Math.Max(0, RequestedIndex - RequestedIndex % MaxExportPerPage); + /// + /// last exclusive export index of the current page + /// public int ExclusiveEnd => IsPaginated ? Math.Min(InclusiveStart + MaxExportPerPage, Package.ExportMapLength) : Package.ExportMapLength; + public int PageSize => ExclusiveEnd - InclusiveStart; - private int CurrentPage => (InclusiveStart + 1) / MaxExportPerPage; - private int LastPage => Math.Max(1, (Package.ExportMapLength + MaxExportPerPage - 1) / MaxExportPerPage); - private int PageSize => ExclusiveEnd - InclusiveStart; + public string TabTitleExtra => IsPaginated ? $"Export{(PageSize > 1 ? "s" : "")} {InclusiveStart}{(PageSize > 1 ? $"-{ExclusiveEnd - 1}" : "")} of {Package.ExportMapLength - 1}" : null; - public string TabTitleExtra => IsPaginated ? $"Page {CurrentPage}/{LastPage}" : null; - - public object GetDisplayData(bool save = false) => !save ? Package.GetExports(InclusiveStart, PageSize) : Package.GetExports(); + /// + /// display all exports unless paginated + /// + /// if we save the data we will display all exports even if is true + /// + public object GetDisplayData(bool save = false) => !save && IsPaginated + ? Package.GetExports(InclusiveStart, PageSize) + : Package.GetExports(); } public static LoadPackageResult GetLoadPackageResult(this IFileProvider provider, GameFile file, string objectName = null) { var result = new LoadPackageResult { Package = provider.LoadPackage(file) }; - if (result.IsPaginated) + if (result.IsPaginated || (result.Package.HasFlags(EPackageFlags.PKG_ContainsMap) && UserSettings.Default.PreviewWorlds)) // focus on UWorld if it's a map we want to preview { - result.InclusiveStart = result.Package.GetExportIndex(file.NameWithoutExtension); + result.RequestedIndex = result.Package.GetExportIndex(file.NameWithoutExtension); if (objectName != null) { - result.InclusiveStart = int.TryParse(objectName, out var index) ? index : result.Package.GetExportIndex(objectName); + result.RequestedIndex = int.TryParse(objectName, out var index) ? index : result.Package.GetExportIndex(objectName); } } diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index 23cdc2d2..4597519a 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -83,9 +83,9 @@ public partial class MainWindow ).ConfigureAwait(false); #if DEBUG - await _threadWorkerView.Begin(cancellationToken => - _applicationView.CUE4Parse.Extract(cancellationToken, - _applicationView.CUE4Parse.Provider["FortniteGame/Plugins/GameFeatures/BRMapCh6/Content/Maps/Hermes_Terrain.umap"])); + // await _threadWorkerView.Begin(cancellationToken => + // _applicationView.CUE4Parse.Extract(cancellationToken, + // _applicationView.CUE4Parse.Provider["FortniteGame/Plugins/GameFeatures/MeshCosmetics/Content/Jumpsuit/F_MED_Jumpsuit_Scrap/CO/CO_F_MED_Jumpsuit_Scrap.uasset"])); // await _threadWorkerView.Begin(cancellationToken => // _applicationView.CUE4Parse.Extract(cancellationToken, // "RED/Content/Chara/ABA/Costume01/Animation/Charaselect/body/stand_body01.uasset")); diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index eed30718..4a820a62 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -138,12 +138,12 @@ public class CUE4ParseViewModel : ViewModel { case Constants._FN_LIVE_TRIGGER: { - Provider = new StreamedFileProvider("FortniteLive", true, versionContainer); + Provider = new StreamedFileProvider("FortniteLive", versionContainer); break; } case Constants._VAL_LIVE_TRIGGER: { - Provider = new StreamedFileProvider("ValorantLive", true, versionContainer); + Provider = new StreamedFileProvider("ValorantLive", versionContainer); break; } default: @@ -155,12 +155,12 @@ public class CUE4ParseViewModel : ViewModel [ new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\Paks"), new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\DisabledPaks") - ], SearchOption.AllDirectories, true, versionContainer), + ], SearchOption.AllDirectories, versionContainer), "eFootball" => new DefaultFileProvider(new DirectoryInfo(gameDirectory), [ new(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\KONAMI\\eFootball\\ST\\Download") - ], SearchOption.AllDirectories, true, versionContainer), - _ => new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, true, versionContainer) + ], SearchOption.AllDirectories, versionContainer), + _ => new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer) }; break; diff --git a/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs b/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs index d642a47e..ea19954c 100644 --- a/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs +++ b/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs @@ -70,7 +70,7 @@ public class GamePathVisualLineText : VisualLineText { var obj = gamePath.SubstringAfterLast('.'); var package = gamePath.SubstringBeforeLast('.'); - var fullPath = _applicationView.CUE4Parse.Provider.FixPath(package, StringComparison.Ordinal); + var fullPath = _applicationView.CUE4Parse.Provider.FixPath(package); var firstLine = a.ParentVisualLine.Document.GetLineByNumber(2); if (a.ParentVisualLine.Document.FileName.Equals(fullPath.SubstringBeforeLast('.'), StringComparison.OrdinalIgnoreCase) && diff --git a/FModel/Views/Resources/Controls/ImagePopout.xaml b/FModel/Views/Resources/Controls/ImagePopout.xaml index 31bf8301..b28330aa 100644 --- a/FModel/Views/Resources/Controls/ImagePopout.xaml +++ b/FModel/Views/Resources/Controls/ImagePopout.xaml @@ -15,7 +15,9 @@ - - + + + + diff --git a/FModel/Views/Snooper/Shading/Material.cs b/FModel/Views/Snooper/Shading/Material.cs index 05d7e354..a3e58d49 100644 --- a/FModel/Views/Snooper/Shading/Material.cs +++ b/FModel/Views/Snooper/Shading/Material.cs @@ -80,7 +80,7 @@ public class Material : IDisposable Normals = [new Texture(new FLinearColor(0.5f, 0.5f, 1f, 1f))]; SpecularMasks = [new Texture(new FLinearColor(1f, 0.5f, 0.5f, 1f))]; Emissive = new Texture[1]; - DiffuseColor = [Vector4.One]; + DiffuseColor = FillColors(1, Diffuse, CMaterialParams2.DiffuseColors, Vector4.One); EmissiveColor = [Vector4.One]; } else From 37025228a2f48e696c01d32e4231eb5a80fd6905 Mon Sep 17 00:00:00 2001 From: Asval Date: Thu, 13 Feb 2025 18:50:51 +0100 Subject: [PATCH 4/5] bump c4p --- CUE4Parse | 2 +- FModel/MainWindow.xaml | 2 +- FModel/MainWindow.xaml.cs | 2 +- FModel/Views/Resources/Resources.xaml | 2 +- FModel/Views/SearchView.xaml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CUE4Parse b/CUE4Parse index 822dc9bf..7001bda7 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 822dc9bf39d793a4005c2eb9ba53721c10b791da +Subproject commit 7001bda784112a00e15968e3d49d5a237907afd0 diff --git a/FModel/MainWindow.xaml b/FModel/MainWindow.xaml index d85febbe..198d8b59 100644 --- a/FModel/MainWindow.xaml +++ b/FModel/MainWindow.xaml @@ -506,7 +506,7 @@ diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index 4597519a..badfeffd 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -85,7 +85,7 @@ public partial class MainWindow #if DEBUG // await _threadWorkerView.Begin(cancellationToken => // _applicationView.CUE4Parse.Extract(cancellationToken, - // _applicationView.CUE4Parse.Provider["FortniteGame/Plugins/GameFeatures/MeshCosmetics/Content/Jumpsuit/F_MED_Jumpsuit_Scrap/CO/CO_F_MED_Jumpsuit_Scrap.uasset"])); + // _applicationView.CUE4Parse.Provider["FortniteGame/Plugins/GameFeatures/Juno/JunoGame/Content/Environments/Landscape/GrassType/M_Juno_LS_Grass.uasset"])); // await _threadWorkerView.Begin(cancellationToken => // _applicationView.CUE4Parse.Extract(cancellationToken, // "RED/Content/Chara/ABA/Costume01/Animation/Charaselect/body/stand_body01.uasset")); diff --git a/FModel/Views/Resources/Resources.xaml b/FModel/Views/Resources/Resources.xaml index d7540235..1d1500ed 100644 --- a/FModel/Views/Resources/Resources.xaml +++ b/FModel/Views/Resources/Resources.xaml @@ -858,7 +858,7 @@ - + diff --git a/FModel/Views/SearchView.xaml b/FModel/Views/SearchView.xaml index 3a454383..bf5a59e8 100644 --- a/FModel/Views/SearchView.xaml +++ b/FModel/Views/SearchView.xaml @@ -159,7 +159,7 @@ From e1b9529d6fab89c672636cdb86e3a780fa2ae99f Mon Sep 17 00:00:00 2001 From: Asval Date: Fri, 14 Feb 2025 17:31:29 +0100 Subject: [PATCH 5/5] pagination qa --- CUE4Parse | 2 +- FModel/MainWindow.xaml.cs | 2 +- FModel/ViewModels/CUE4ParseViewModel.cs | 11 ++++++----- FModel/Views/Snooper/Options.cs | 1 + 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CUE4Parse b/CUE4Parse index 7001bda7..2aed4da1 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 7001bda784112a00e15968e3d49d5a237907afd0 +Subproject commit 2aed4da1ee440cd421222526bdc501031f685be6 diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index badfeffd..6e469d96 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -85,7 +85,7 @@ public partial class MainWindow #if DEBUG // await _threadWorkerView.Begin(cancellationToken => // _applicationView.CUE4Parse.Extract(cancellationToken, - // _applicationView.CUE4Parse.Provider["FortniteGame/Plugins/GameFeatures/Juno/JunoGame/Content/Environments/Landscape/GrassType/M_Juno_LS_Grass.uasset"])); + // _applicationView.CUE4Parse.Provider["Marvel/Content/Marvel/Characters/1050/1050300/Meshes/SK_1050_1050300_Lobby.uasset"])); // await _threadWorkerView.Begin(cancellationToken => // _applicationView.CUE4Parse.Extract(cancellationToken, // "RED/Content/Chara/ABA/Costume01/Animation/Charaselect/body/stand_body01.uasset")); diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 4a820a62..2d668df1 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -133,17 +133,18 @@ public class CUE4ParseViewModel : ViewModel customVersions: new FCustomVersionContainer(currentDir.Versioning.CustomVersions), optionOverrides: currentDir.Versioning.Options, mapStructTypesOverrides: currentDir.Versioning.MapStructTypes); + var pathComparer = StringComparer.OrdinalIgnoreCase; switch (gameDirectory) { case Constants._FN_LIVE_TRIGGER: { - Provider = new StreamedFileProvider("FortniteLive", versionContainer); + Provider = new StreamedFileProvider("FortniteLive", versionContainer, pathComparer); break; } case Constants._VAL_LIVE_TRIGGER: { - Provider = new StreamedFileProvider("ValorantLive", versionContainer); + Provider = new StreamedFileProvider("ValorantLive", versionContainer, pathComparer); break; } default: @@ -155,12 +156,12 @@ public class CUE4ParseViewModel : ViewModel [ new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\Paks"), new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\DisabledPaks") - ], SearchOption.AllDirectories, versionContainer), + ], SearchOption.AllDirectories, versionContainer, pathComparer), "eFootball" => new DefaultFileProvider(new DirectoryInfo(gameDirectory), [ new(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\KONAMI\\eFootball\\ST\\Download") - ], SearchOption.AllDirectories, versionContainer), - _ => new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer) + ], SearchOption.AllDirectories, versionContainer, pathComparer), + _ => new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer) }; break; diff --git a/FModel/Views/Snooper/Options.cs b/FModel/Views/Snooper/Options.cs index b081de94..d4201392 100644 --- a/FModel/Views/Snooper/Options.cs +++ b/FModel/Views/Snooper/Options.cs @@ -190,6 +190,7 @@ public class Options { var guid = o.LightingGuid; if (Textures.TryGetValue(guid, out texture)) return texture != null; + if (o.Format == EPixelFormat.PF_BC6H) return false; // BC6H is not supported by Decode thus randomly crashes the app SKBitmap bitmap = o switch {