From c9542f1a91cabfe1b897bce1199203a85e1ef646 Mon Sep 17 00:00:00 2001 From: Masusder <59669685+Masusder@users.noreply.github.com> Date: Fri, 6 Mar 2026 11:56:16 +0100 Subject: [PATCH 01/13] Aion 2, Crystal of Atlan, Fate Trigger, WuWa and The First Descendant updates --- CUE4Parse | 2 +- FModel/Enums.cs | 1 + FModel/ViewModels/CUE4ParseViewModel.cs | 2 +- FModel/ViewModels/GameFileViewModel.cs | 10 +++++++--- FModel/Views/Resources/Colors.xaml | 1 + .../Resources/Converters/FileToGeometryConverter.cs | 1 + FModel/Views/Resources/Icons.xaml | 1 + 7 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CUE4Parse b/CUE4Parse index 4fb74359..7ed9bd5a 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 4fb7435973fc57bfb78577c971d776f7577440cf +Subproject commit 7ed9bd5adf3daada4bd7d884f3a42d163a64247a diff --git a/FModel/Enums.cs b/FModel/Enums.cs index 5e06621d..bf85c984 100644 --- a/FModel/Enums.cs +++ b/FModel/Enums.cs @@ -158,4 +158,5 @@ public enum EAssetCategory : uint Particle = AssetCategoryExtensions.CategoryBase + (9 << 16), GameSpecific = AssetCategoryExtensions.CategoryBase + (10 << 16), Borderlands = GameSpecific + 1, + Aion2 = GameSpecific + 2, } diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index d76aae47..df7cbbfa 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -964,7 +964,7 @@ public class CUE4ParseViewModel : ViewModel } else if (entry.NameWithoutExtension.Equals("L10NString")) { - var l10nData = new FAion2L10NFile(entry); + var l10nData = new FAion2L10NFile(entry, Provider); TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(l10nData, Formatting.Indented), saveProperties, updateUi); } else diff --git a/FModel/ViewModels/GameFileViewModel.cs b/FModel/ViewModels/GameFileViewModel.cs index adefb5f5..1e837b92 100644 --- a/FModel/ViewModels/GameFileViewModel.cs +++ b/FModel/ViewModels/GameFileViewModel.cs @@ -67,6 +67,7 @@ public class GameFileViewModel(GameFile asset) : ViewModel private const int MaxPreviewSize = 128; private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + private EGame? GameVersion => _applicationView.CUE4Parse?.Provider.Versions.Game; public EResolveCompute Resolved { get; private set; } = EResolveCompute.None; public GameFile Asset { get; } = asset; @@ -260,7 +261,7 @@ public class GameFileViewModel(GameFile asset) : ViewModel // Game specific assets below UBorderlandsDialogObject => (EAssetCategory.Borderlands, EBulkType.None), // Borderlands 3; UGbxGraphAsset or UDialogScriptData or UDialogPerformanceData => (EAssetCategory.Borderlands, EBulkType.Audio), // Borderlands 4; Borderlands 3; - UFaceFXAnimSet when _applicationView.CUE4Parse?.Provider.Versions.Game is EGame.GAME_Borderlands4 => (EAssetCategory.Borderlands, EBulkType.Audio), // Borderlands 4; + UFaceFXAnimSet when GameVersion is EGame.GAME_Borderlands4 => (EAssetCategory.Borderlands, EBulkType.Audio), // Borderlands 4; _ => (EAssetCategory.All, EBulkType.None), }; @@ -430,10 +431,13 @@ public class GameFileViewModel(GameFile asset) : ViewModel }); } // Game specific extensions below - case "ace": // Borderlands 3 - case "ncs": // Borderlands 4 + case "ace" when GameVersion is EGame.GAME_Borderlands3: + case "ncs" when GameVersion is EGame.GAME_Borderlands4: AssetCategory = EAssetCategory.Borderlands; break; + case "dat" when GameVersion is EGame.GAME_Aion2: + AssetCategory = EAssetCategory.Aion2; + break; default: AssetCategory = EAssetCategory.All; // just so it sets resolved break; diff --git a/FModel/Views/Resources/Colors.xaml b/FModel/Views/Resources/Colors.xaml index a6689c0e..02134cf8 100644 --- a/FModel/Views/Resources/Colors.xaml +++ b/FModel/Views/Resources/Colors.xaml @@ -51,4 +51,5 @@ + diff --git a/FModel/Views/Resources/Converters/FileToGeometryConverter.cs b/FModel/Views/Resources/Converters/FileToGeometryConverter.cs index cdedce92..7a6c54df 100644 --- a/FModel/Views/Resources/Converters/FileToGeometryConverter.cs +++ b/FModel/Views/Resources/Converters/FileToGeometryConverter.cs @@ -90,6 +90,7 @@ public class FileToGeometryConverter : IMultiValueConverter EAssetCategory.ByteCode => ("CodeIcon", "CodeBrush"), EAssetCategory.Borderlands => ("BorderlandsIcon", "BorderlandsBrush"), + EAssetCategory.Aion2 => ("AionIcon", "AionBrush"), _ => ("AssetIcon", "NeutralBrush") }; diff --git a/FModel/Views/Resources/Icons.xaml b/FModel/Views/Resources/Icons.xaml index 6fde67ec..45e0b5e1 100644 --- a/FModel/Views/Resources/Icons.xaml +++ b/FModel/Views/Resources/Icons.xaml @@ -101,4 +101,5 @@ M13,9V3.5L18.5,9M6,2C4.89,2 4,2.89 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6 ZM12,11A3,3 0 1,0 12,17A3,3 0 0,0 12,11 ZM12,12.5L14,16H13L12,14.5L11,16H10L12,12.5Z + M609 180.1c-19.4 2.3-38.2 7.8-56.5 16.8-53.8 26.5-89.2 74.9-98.7 135.1-1.1 7-1.3 133.1-1.3 739 0 693.9.1 731 1.8 741.4 12.8 79.4 70.7 137.8 149.2 150.6 11.8 2 20.6 2 563.5 2s551.7 0 563.5-2c57.2-9.3 104-43 130.8-94.1 9.1-17.2 14.6-33.9 18.4-55.4 1.6-9.4 1.8-40.6 2-554l.3-544-268.2-268.2L1345.5 179l-364.5.1c-200.5.1-367.9.5-372 1m893 378.4L1747.5 804H1256V558.5c0-135 .1-245.5.3-245.5.1 0 110.7 110.5 245.7 245.5m-792.1 532c24.5 1.3 47 2.9 49.8 3.4 7.1 1.3 12.8 5.5 22.5 16.4 6.4 7.3 9.2 11.5 13.6 20.7 9.2 19.4 21.4 47.2 44.4 101.5 27.5 65.1 39.7 92.3 55.5 124 6.9 13.7 16.1 32.9 20.5 42.5 8.2 17.9 15.4 31.9 18.3 35.5 1.5 1.8 1.5-7.2.9-109-.6-108.7-.7-111.1-2.7-116.5-3.1-8.3-10-14.5-24.7-22-10.1-5.1-11.9-6.4-10.9-7.6 1.6-2 5.8-1.8 21.3 1.1 19.3 3.6 39.1 4.5 97.7 4.5 42.1 0 51.9.3 53.4 1.4 1.8 1.3 1.8 1.4-.1 1.9-1 .3-3.9.8-6.4 1.2s-8.5 1.7-13.5 3c-4.9 1.3-12.1 2.6-16 3-7.9.8-14.3 3.2-18 6.9l-2.5 2.5v123.7c0 121.8 0 123.8 2 125.9 1.4 1.5 2.1 4.1 2.6 9.2.8 8.1 2.2 10.1 12.6 17.2 9.3 6.5 9.3 6.5-26.7 7.3-17.6.4-45.3.7-61.5.5-29.2-.2-29.5-.2-29.8-2.3-.2-1.7.7-2.5 5.5-4.5 3.2-1.3 7.5-4.1 9.8-6.4 4.5-4.5 8.3-11 7.2-12.2-1.3-1.2-72.8-1.6-90.2-.5-13.5.9-16.2.8-17.5-.4-1.9-1.9-.9-2.7 8-6.4 9.8-4.1 11-5.2 11-11 0-3.9-3.7-13.2-22.3-56.1l-22.3-51.4-4.5-.6c-13.1-2-52.1-3.1-69.3-2-14.4.9-38.5 3.6-39.3 4.4-.1.1-5.6 13.5-12.3 29.7s-16 38.7-20.7 50c-7.4 17.9-8.5 21.1-8.1 25.1.6 5.6 3.9 10.3 10.2 14.3 4.8 3 5.8 5.1 2.9 5.8-1 .2-10.6 0-21.3-.5-21.7-.9-83.9-.2-108.4 1.3-22.1 1.4-22.5.3-1.7-5 17.9-4.6 33.3-9.6 42.4-13.7l7.7-3.6 6.9-13.1c3.8-7.2 14.2-27.5 23.1-45.2 17.9-35.6 19-38.9 15.5-47.9-1-2.7-1.4-5.6-1.1-7.1.4-1.5 8.1-10.1 17.5-19.8 9.2-9.4 17.6-18.6 18.5-20.4s6.4-15.7 12.2-30.8c5.7-15.1 17.9-46.7 27.1-70.2l16.6-42.7-9.3-18.2c-5.1-10-10.7-19.8-12.5-21.6-6-6.1-16.6-9.9-32-11.4-7.8-.8-8-.9-8.3-3.6s-.3-2.7 5.9-2.7c3.5 0 26.3 1.1 50.8 2.5m999.3 20.4c3 5.2 4.8 10.9 9.7 31 8.8 35.5 11.8 55.6 14.2 93.1 1.5 24.1.6 87-1.5 108-1.8 17.4-4.4 37.5-5.3 40.5-.3 1.3-.2 1.7.5 1 1.2-1.2 11.9-33.8 16.2-49.5 1.8-6.3 3.7-12.4 4.2-13.4 3.4-6.4 2.2 14.4-2 34.9-1.3 6.3-1.5 9.6-.9 10.7 2.1 3.3-9.4 55.8-18.3 83.3-5.5 17.2-15.6 42.7-18.1 45.9-1.1 1.5-2.6 2.3-3.4 1.9-.8-.3-5.7-6.9-10.9-14.7-17.4-26.2-33.3-45.2-60.7-72.1-16.4-16.1-29.1-27.5-45.6-41-4-3.2-7.5-6.5-7.8-7.3-1.2-3.2 5.5.3 19.9 10.4 8.1 5.8 15 10.3 15.2 10.2.4-.5-17.3-14.6-37.1-29.4-43.5-32.7-81.4-58.4-86.1-58.4-1.3 0-1.4 9.9-.8 86.7.4 61.2 1 87.5 1.8 89.3 1.4 3.1 7.5 7.2 14 9.5 6.9 2.4 7.6 3.2 4.6 5.5-2.4 1.9-5 2-74.2 2-45.3 0-71.8-.4-71.8-1 0-.9 3.2-1.9 12-3.9 9.2-2.1 22.3-6.2 26.9-8.6 7.6-3.8 8.9-8.7 10.1-38 1.3-29.8 2.5-203 1.5-217.8-.7-11.3-.9-12-3.8-15.3-1.6-1.9-4.3-4.2-6-5-5.1-2.7-24.7-6.3-42.9-7.9-9.7-.8-18.4-1.8-19.2-2.1-1.4-.5-2.2-2-1.3-2.6.3-.2 73.8-1.9 90.7-2.1 12.6-.2 13.9-.4 19.5-3 4.8-2.3 8.6-3.1 19.5-4.2 18.5-1.9 34.2-2.3 34.7-.9.3 1-2.3 4.6-10.9 14.7l-3.3 3.9-.3 20.2-.3 20.2 5.7 8c17.5 24.5 49.6 55.6 93.9 91 20 16.1 58.7 45.6 59.1 45.1.7-.7 6.5-38.1 8.5-55.7 3-25 3.3-66.8.6-84-2.5-16.2-9.6-43-11.4-43-.2 0-8.1 7.2-17.5 16.1-9.3 8.8-17.2 15.8-17.5 15.5s2.7-4.5 6.8-9.3c13-15.6 35.1-41.7 37.2-44.1 1.1-1.2 1.8-2.2 1.4-2.2-.3 0-16.9 17-36.8 37.7-37.3 38.8-41.5 43-42.6 41.9-.8-.8-4.1 3.1 54-63.3 28.4-32.6 51.7-59.6 51.7-59.9s-3.2 2.3-7 5.7c-14.3 12.9-15.9 13.8-7.5 4.4 16.1-18.1 32.8-35.6 33.9-35.2.6.2 2.8 3.2 4.8 6.6m-578.4 94.3c-.3.7-5 4.4-10.5 8.3-13.5 9.4-17.7 12.9-31.6 26.4-8.1 8-13.5 14.2-17.6 20.5-6.8 10.7-11.5 18.6-10.9 18.6.3 0 2-2.1 3.8-4.8 5.1-7.1 19.5-21.6 28.5-28.7 24.4-19.1 53.7-31.3 89-37.2 13.7-2.2 53.7-2.5 68.5-.5 45.5 6.4 82.4 22.6 109.1 48 26 24.7 40.4 54.7 45 93.7 1.7 14.2.6 42.3-2.1 54.9-8.5 39.6-25.4 68.8-53.5 92.6-15.5 13-29.1 20.9-45.7 26.5-18.4 6.1-35.4 7.7-56 5.3l-10.3-1.3 10.5-.6c11.4-.8 27.2-3.2 31.1-4.9 2.3-.9 2.2-1-.6-.5-15.1 2.4-38.9 3.2-48.9 1.5-11.5-1.9-10.1-4 6.3-9.9 6.2-2.3 15.4-6.2 20.4-8.8 56.5-29.2 84.7-97.9 69.1-168.3-11.5-51.5-43.4-85-89.3-93.6-11.4-2.1-32.9-2.2-44.2 0-40.6 7.7-70 34.9-84.5 78.1-5.8 17.5-7.7 31.8-7.1 54 .6 21.3 2.2 32.3 7.3 49.2 11.8 39 34.4 64.6 78.4 88.7 4.7 2.6 9.6 5.8 11 7.1 2.3 2.4 2.4 2.6.7 4.2-1.6 1.6-3.8 1.8-17 1.7-45.3-.2-82.1-18.2-112.6-55-4.2-5.1-9.6-12.4-12.1-16.2-2.5-3.9-4.9-7.1-5.4-7.1-.5-.1-.6 3.7-.3 8.5l.5 8.5-2.4-2.2c-3-2.8-10.5-17.2-14.3-27.7-7.2-19.5-12.1-46.9-12.1-67.8 0-30.8 5.7-56.3 18.5-81.9 16.2-32.6 43.1-58.4 79-75.7 10.3-4.9 13-5.7 12.3-3.6 M731 1231.2c-6.2 15.5-15.9 40.1-21.6 54.5-5.7 14.5-10.4 26.7-10.4 27.2 0 1.5 7.9 2 39 2.7 26.5.5 51.1-.3 52.6-1.8.6-.6-46.1-108.6-47.5-110.1-.5-.5-5.9 11.9-12.1 27.5 From 639f21e574b4310776e8853947c5aa7e06368182 Mon Sep 17 00:00:00 2001 From: Masusder <59669685+Masusder@users.noreply.github.com> Date: Fri, 13 Mar 2026 21:14:56 +0100 Subject: [PATCH 02/13] added links to some exceptions + auto detect ue version (#657) Co-authored-by: Asval Co-authored-by: Krowe-moh <27891447+Krowe-moh@users.noreply.github.com> --- CUE4Parse | 2 +- FModel/Constants.cs | 6 ++ FModel/Settings/UserSettings.cs | 7 -- FModel/ViewModels/ApplicationViewModel.cs | 2 +- FModel/ViewModels/AudioPlayerViewModel.cs | 60 ++++++++++-- FModel/ViewModels/CUE4ParseViewModel.cs | 22 ++--- FModel/ViewModels/GameFileViewModel.cs | 1 + FModel/ViewModels/GameSelectorViewModel.cs | 95 ++++++++++++++++++- FModel/ViewModels/ThreadWorkerViewModel.cs | 22 ++++- .../ContextMenus/FileContextMenu.xaml | 3 +- .../ContextMenus/FolderContextMenu.xaml | 4 +- .../Controls/Rtb/CustomRichTextBox.cs | 41 ++++++-- FModel/Views/Resources/Resources.xaml | 3 +- FModel/Views/SearchView.xaml | 7 +- FModel/Views/SettingsView.xaml | 30 +++--- FModel/Views/SettingsView.xaml.cs | 10 ++ FModel/Views/Snooper/Models/UModel.cs | 2 +- 17 files changed, 243 insertions(+), 74 deletions(-) diff --git a/CUE4Parse b/CUE4Parse index 7ed9bd5a..6d7157a2 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 7ed9bd5adf3daada4bd7d884f3a42d163a64247a +Subproject commit 6d7157a29b08d583aef9887a56d70f27a2ff36d5 diff --git a/FModel/Constants.cs b/FModel/Constants.cs index af4c4a51..45a79836 100644 --- a/FModel/Constants.cs +++ b/FModel/Constants.cs @@ -40,6 +40,12 @@ public static class Constants public const string _NO_PRESET_TRIGGER = "Hand Made"; + // Common issues + public const string MAPPING_ISSUE_LINK = "https://github.com/4sval/FModel/discussions/418"; + public const string AUDIO_ISSUE_LINK = "https://github.com/4sval/FModel/discussions/658"; + public const string RADA_ISSUE_LINK = "https://github.com/4sval/FModel/discussions/422"; + public const string VERSION_ISSUE_LINK = "https://github.com/4sval/FModel/discussions/425"; + public static int PALETTE_LENGTH => COLOR_PALETTE.Length; public static readonly Vector3[] COLOR_PALETTE = { diff --git a/FModel/Settings/UserSettings.cs b/FModel/Settings/UserSettings.cs index ebf9e534..f6a77b19 100644 --- a/FModel/Settings/UserSettings.cs +++ b/FModel/Settings/UserSettings.cs @@ -252,13 +252,6 @@ namespace FModel.Settings set => SetProperty(ref _imageMergerMargin, value); } - private bool _canExportRawData; - public bool CanExportRawData - { - get => _canExportRawData; - set => SetProperty(ref _canExportRawData, value); - } - private bool _readScriptData; public bool ReadScriptData { diff --git a/FModel/ViewModels/ApplicationViewModel.cs b/FModel/ViewModels/ApplicationViewModel.cs index 7822aa70..b49b2dd9 100644 --- a/FModel/ViewModels/ApplicationViewModel.cs +++ b/FModel/ViewModels/ApplicationViewModel.cs @@ -246,7 +246,7 @@ public class ApplicationViewModel : ViewModel } else { - FLogger.Append(ELog.Error, () => FLogger.Text("Could not download VgmStream", Constants.WHITE, true)); + FLogger.Append(ELog.Error, () => FLogger.Text("Could not download vgmstream", Constants.WHITE, true)); } } } diff --git a/FModel/ViewModels/AudioPlayerViewModel.cs b/FModel/ViewModels/AudioPlayerViewModel.cs index d12b2e7b..eff4cbb7 100644 --- a/FModel/ViewModels/AudioPlayerViewModel.cs +++ b/FModel/ViewModels/AudioPlayerViewModel.cs @@ -298,13 +298,23 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable Save(a, true); } - FLogger.Append(ELog.Information, () => - { - FLogger.Text("Successfully saved audio from ", Constants.WHITE); - FLogger.Link(_audioFiles.First().FileName, _audioFiles.First().FilePath, true); - }); if (_audioFiles.Count > 1) - FLogger.Append(ELog.Information, () => FLogger.Text($"Successfully saved {_audioFiles.Count} audio files", Constants.WHITE, true)); + { + var dir = new DirectoryInfo(Path.GetDirectoryName(_audioFiles.First().FilePath)); + FLogger.Append(ELog.Information, () => + { + FLogger.Text($"Successfully saved {_audioFiles.Count} audio files to ", Constants.WHITE); + FLogger.Link(dir.Name, dir.FullName, true); + }); + } + else + { + FLogger.Append(ELog.Information, () => + { + FLogger.Text("Successfully saved ", Constants.WHITE); + FLogger.Link(_audioFiles.First().FileName, _audioFiles.First().FilePath, true); + }); + } }); } @@ -654,15 +664,24 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable } } - private bool TryConvert(out string wavFilePath) => TryConvert(SelectedAudioFile.FilePath, SelectedAudioFile.Data, out wavFilePath); - public static bool TryConvert(string inputFilePath, byte[] inputFileData, out string wavFilePath) + private bool TryConvert(out string wavFilePath) => TryConvert(SelectedAudioFile.FilePath, SelectedAudioFile.Data, out wavFilePath, true); + public static bool TryConvert(string inputFilePath, byte[] inputFileData, out string wavFilePath, bool updateUi = false) { wavFilePath = string.Empty; var vgmFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "test.exe"); if (!File.Exists(vgmFilePath)) { vgmFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-cli.exe"); - if (!File.Exists(vgmFilePath)) return false; + if (!File.Exists(vgmFilePath)) + { + Log.Error("Failed to convert {InputFilePath}, vgmstream is missing", inputFilePath); + FLogger.Append(ELog.Error, () => + { + FLogger.Text("Failed to convert audio because vgmstream is missing. See: ", Constants.WHITE); + FLogger.Link("→ link ←", Constants.AUDIO_ISSUE_LINK, true); + }); + return false; + } } Directory.CreateDirectory(inputFilePath.SubstringBeforeLast("/")); @@ -679,7 +698,22 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable vgmProcess?.WaitForExit(5000); File.Delete(inputFilePath); - return vgmProcess?.ExitCode == 0 && File.Exists(wavFilePath); + + var success = vgmProcess?.ExitCode == 0 && File.Exists(wavFilePath); + if (!success) + { + Log.Error("Failed to convert {InputFilePath} to .wav format", inputFilePath); + if (updateUi) + { + FLogger.Append(ELog.Error, () => + { + FLogger.Text("Failed to convert audio to .wav format. See: ", Constants.WHITE); + FLogger.Link("→ link ←", Constants.AUDIO_ISSUE_LINK, true); + }); + } + } + + return success; } private bool TryDecode(string extension, out string rawFilePath) @@ -688,6 +722,12 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable var decoderPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{extension}dec.exe"); if (!File.Exists(decoderPath)) { + Log.Error("Failed to convert {FilePath}, rada decoder is missing", SelectedAudioFile.FilePath); + FLogger.Append(ELog.Error, () => + { + FLogger.Text("Failed to convert audio because rada decoder is missing. See: ", Constants.WHITE); + FLogger.Link("→ link ←", Constants.RADA_ISSUE_LINK, true); + }); return false; } diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index df7cbbfa..6b13ecc4 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -1179,7 +1179,7 @@ public class CUE4ParseViewModel : ViewModel case USoundWave when isNone || saveAudio: { // If UAkMediaAsset exists in the same package it should be used to handle the audio instead (because it contains actual audio name) - if (pointer.Object.Value is UAkMediaAssetData dataObj && dataObj.Outer is UAkMediaAsset) + if (pointer.Object.Value is UAkMediaAssetData dataObj && dataObj.Outer.Object.Value is UAkMediaAsset) return false; var shouldDecompress = UserSettings.Default.CompressedAudioMode == ECompressedAudio.PlayDecompressed; @@ -1196,7 +1196,7 @@ public class CUE4ParseViewModel : ViewModel } case UAkMediaAsset when (isNone || saveAudio) && pointer.Object.Value is UAkMediaAsset akMediaAsset: { - var audioName = akMediaAsset.MediaName; + var audioName = akMediaAsset.MediaName ?? akMediaAsset.Name; if (akMediaAsset.CurrentMediaAssetData?.TryLoad(out var akMediaAssetData) is true) { var shouldDecompress = UserSettings.Default.CompressedAudioMode is ECompressedAudio.PlayDecompressed; @@ -1416,25 +1416,15 @@ public class CUE4ParseViewModel : ViewModel writer.Flush(); } + bool conversionSuccess = true; if (UserSettings.Default.ConvertAudioOnBulkExport) { - AudioPlayerViewModel.TryConvert(savedAudioPath, data, out string wavFilePath); - if (!string.IsNullOrEmpty(wavFilePath)) - { - savedAudioPath = wavFilePath; - } - else if (updateUi) - { - FLogger.Append(ELog.Error, () => - { - FLogger.Text("Failed to convert audio to WAV format, aborting extraction.", Constants.WHITE, true); - }); - return; - } + conversionSuccess = AudioPlayerViewModel.TryConvert(savedAudioPath, data, out string wavFilePath); + if (conversionSuccess) savedAudioPath = wavFilePath; } Log.Information("Successfully saved {FilePath}", savedAudioPath); - if (updateUi) + if (updateUi && conversionSuccess) { FLogger.Append(ELog.Information, () => { diff --git a/FModel/ViewModels/GameFileViewModel.cs b/FModel/ViewModels/GameFileViewModel.cs index 1e837b92..689e315d 100644 --- a/FModel/ViewModels/GameFileViewModel.cs +++ b/FModel/ViewModels/GameFileViewModel.cs @@ -356,6 +356,7 @@ public class GameFileViewModel(GameFile asset) : ViewModel case "csv": AssetCategory = EAssetCategory.Data; break; + case "stinfo": case "ushaderbytecode": AssetCategory = EAssetCategory.ByteCode; break; diff --git a/FModel/ViewModels/GameSelectorViewModel.cs b/FModel/ViewModels/GameSelectorViewModel.cs index 9cd43d67..369945b2 100644 --- a/FModel/ViewModels/GameSelectorViewModel.cs +++ b/FModel/ViewModels/GameSelectorViewModel.cs @@ -4,6 +4,8 @@ using Serilog; using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text.RegularExpressions; @@ -67,12 +69,103 @@ public class GameSelectorViewModel : ViewModel public void AddUndetectedDir(string gameDirectory) => AddUndetectedDir(gameDirectory.SubstringAfterLast('\\'), gameDirectory); public void AddUndetectedDir(string gameName, string gameDirectory) { - var setting = DirectorySettings.Default(gameName, gameDirectory, true); + if (TryDetectUeVersion(gameDirectory, out var ueVersion, out var newGameDirectory)) + { + // gameDirectory = newGameDirectory; // directory was changed to point to the correct paks folder + } + + var setting = DirectorySettings.Default(gameName, gameDirectory, true, ueVersion); UserSettings.Default.PerDirectory[gameDirectory] = setting; _detectedDirectories.Add(setting); SelectedDirectory = DetectedDirectories.Last(); } + private bool TryDetectUeVersion(string gameDirectory, out EGame ueVersion, [MaybeNullWhen(false)] out string newGameDirectory) + { + var targetGameDir = gameDirectory; + if (!targetGameDir.EndsWith("Paks", StringComparison.OrdinalIgnoreCase)) + { + var dirs = Directory.GetDirectories(targetGameDir, "Paks", SearchOption.AllDirectories); + var paksDir = dirs.Length == 1 ? dirs[0] : dirs.FirstOrDefault(x => !x.EndsWith("Engine\\Programs\\CrashReportClient\\Content\\Paks")); + if (!string.IsNullOrEmpty(paksDir)) + { + Log.Warning("Selected directory \"{GameDirectory}\" does not end with \"Paks\". Looking in \"{PaksDir}\" instead.", targetGameDir, paksDir); + targetGameDir = paksDir; + } + + if (Directory.GetFiles(gameDirectory, "*.exe") is { Length: 1 } exe && TryGetUeVersionFromExe(exe[0], out ueVersion)) + { + // we checked the exe in the original directory, the BootstrapPackagedGame one + // but we still want c4p to use the paks folder as the game directory (if any), not the original one + newGameDirectory = targetGameDir; + Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, exe[0]); + return true; + } + } + + // past this point, we assume targetGameDir is the correct Paks folder + newGameDirectory = targetGameDir; + var projectDir = Path.Combine(targetGameDir, "..", ".."); + + var projectBinariesDir = Path.Combine(projectDir, "Binaries", "Win64"); + if (Directory.Exists(projectBinariesDir)) + { + if (Directory.GetFiles(projectBinariesDir, "*-Win64-Shipping.exe") is { Length: > 0 } shipping) + { + foreach (var exe in shipping) + { + if (TryGetUeVersionFromExe(exe, out ueVersion)) + { + Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, exe); + return true; + } + } + } + else if (Directory.GetFiles(projectBinariesDir, "*.exe") is { Length: < 3 } exes) + { + foreach (var exe in exes) + { + if (TryGetUeVersionFromExe(exe, out ueVersion)) + { + Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, exe); + return true; + } + } + } + } + + var crashReportClientExe = Path.Combine(projectDir, "..", "Engine", "Binaries", "Win64", "CrashReportClient.exe"); + if (File.Exists(crashReportClientExe) && TryGetUeVersionFromExe(crashReportClientExe, out ueVersion)) + { + Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, crashReportClientExe); + return true; + } + + ueVersion = EGame.GAME_UE4_LATEST; + Log.Warning("Failed to detect UE version for \"{GameDirectory}\".", gameDirectory); + return false; + } + + private bool TryGetUeVersionFromExe(string exePath, out EGame ueVersion) + { + ueVersion = EGame.GAME_UE4_LATEST; + try + { + var info = FileVersionInfo.GetVersionInfo(exePath); + ueVersion = info.FileMajorPart switch + { + 4 => (EGame) Math.Min((uint)(GameUtils.GameUe4Base + (info.FileMinorPart << 16)), (uint) EGame.GAME_UE4_LATEST), + 5 => (EGame) Math.Min((uint)(GameUtils.GameUe5Base + (info.FileMinorPart << 16)), (uint) EGame.GAME_UE5_LATEST), + _ => throw new InvalidOperationException($"Unsupported UE major version {info.FileMajorPart} detected from {exePath}") + }; + return true; + } + catch + { + return false; + } + } + public void DeleteSelectedGame() { UserSettings.Default.PerDirectory.Remove(SelectedDirectory.GameDirectory); // should not be a problem diff --git a/FModel/ViewModels/ThreadWorkerViewModel.cs b/FModel/ViewModels/ThreadWorkerViewModel.cs index c1fc7b44..59c49f19 100644 --- a/FModel/ViewModels/ThreadWorkerViewModel.cs +++ b/FModel/ViewModels/ThreadWorkerViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using CUE4Parse.UE4.Exceptions; using FModel.Framework; using FModel.Services; using FModel.Views.Resources.Controls; @@ -100,7 +101,26 @@ public class ThreadWorkerViewModel : ViewModel CurrentCancellationTokenSource = null; // kill token Log.Error("{Exception}", e); - FLogger.Append(e); + switch (e) + { + case MappingException: + FLogger.Append(ELog.Error, () => + { + FLogger.Text("Package has unversioned properties but mapping file (.usmap) is missing, can't serialize. See: ", Constants.WHITE); + FLogger.Link("→ link ←", Constants.MAPPING_ISSUE_LINK, true); + }); + break; + case VersionException v: // Error might be unrelated to version, but it's usually the case + FLogger.Append(ELog.Error, () => + { + FLogger.Text(v.Message[..^1] + ", can't serialize. Make sure the correct UE version is configured. See: ", Constants.WHITE); + FLogger.Link("→ link ←", Constants.VERSION_ISSUE_LINK, true); + }); + break; + default: + FLogger.Append(e); + break; + } return; } } diff --git a/FModel/Views/Resources/Controls/ContextMenus/FileContextMenu.xaml b/FModel/Views/Resources/Controls/ContextMenus/FileContextMenu.xaml index b72206f3..109b71fb 100644 --- a/FModel/Views/Resources/Controls/ContextMenus/FileContextMenu.xaml +++ b/FModel/Views/Resources/Controls/ContextMenus/FileContextMenu.xaml @@ -110,8 +110,7 @@ - + + Command="{Binding RightClickMenuCommand}"> diff --git a/FModel/Views/Resources/Controls/Rtb/CustomRichTextBox.cs b/FModel/Views/Resources/Controls/Rtb/CustomRichTextBox.cs index 8b995c01..56478eb9 100644 --- a/FModel/Views/Resources/Controls/Rtb/CustomRichTextBox.cs +++ b/FModel/Views/Resources/Controls/Rtb/CustomRichTextBox.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Windows; using System.Windows.Controls; @@ -126,13 +126,40 @@ public class FLogger : ITextFormatter { NavigateUri = new Uri(url), OverridesDefaultStyle = true, - Style = new Style(typeof(Hyperlink)) { Setters = + Style = new Style(typeof(Hyperlink)) { - new Setter(FrameworkContentElement.CursorProperty, Cursors.Hand), - new Setter(TextBlock.TextDecorationsProperty, TextDecorations.Underline), - new Setter(TextElement.ForegroundProperty, Brushes.Cornsilk) - }} - }.Click += (sender, _) => Process.Start("explorer.exe", $"/select, \"{((Hyperlink)sender).NavigateUri.AbsoluteUri}\""); + Setters = + { + new Setter(FrameworkContentElement.CursorProperty, Cursors.Hand), + new Setter(TextElement.ForegroundProperty, Brushes.Goldenrod), + new Setter(TextElement.FontWeightProperty, FontWeights.Bold) + }, + Triggers = + { + new Trigger + { + Property = UIElement.IsMouseOverProperty, + Value = true, + Setters = + { + new Setter(TextElement.ForegroundProperty, Brushes.Gold), + new Setter(TextBlock.TextDecorationsProperty, TextDecorations.Underline) + } + } + } + } + }.Click += (sender, _) => + { + var uri = ((Hyperlink) sender).NavigateUri; + if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) + { + Process.Start(new ProcessStartInfo(uri.AbsoluteUri) { UseShellExecute = true }); + } + else + { + Process.Start("explorer.exe", $"/select, \"{uri.AbsoluteUri}\""); + } + }; } finally { diff --git a/FModel/Views/Resources/Resources.xaml b/FModel/Views/Resources/Resources.xaml index d9f04f99..c99c9de0 100644 --- a/FModel/Views/Resources/Resources.xaml +++ b/FModel/Views/Resources/Resources.xaml @@ -926,8 +926,7 @@ - + diff --git a/FModel/Views/SearchView.xaml b/FModel/Views/SearchView.xaml index 3b945aea..1b37d87a 100644 --- a/FModel/Views/SearchView.xaml +++ b/FModel/Views/SearchView.xaml @@ -1,7 +1,6 @@ - + @@ -556,8 +554,7 @@ - + diff --git a/FModel/Views/SettingsView.xaml b/FModel/Views/SettingsView.xaml index 179a60c6..4f60506d 100644 --- a/FModel/Views/SettingsView.xaml +++ b/FModel/Views/SettingsView.xaml @@ -44,7 +44,6 @@ - @@ -223,51 +222,46 @@