From 879b5a2ce8d013f9c29471e026f6ae3c36f6b328 Mon Sep 17 00:00:00 2001 From: Masusder <59669685+Masusder@users.noreply.github.com> Date: Tue, 4 Nov 2025 21:26:19 +0100 Subject: [PATCH 1/5] CriWare --- CUE4Parse | 2 +- FModel/ViewModels/AudioPlayerViewModel.cs | 1 + FModel/ViewModels/CUE4ParseViewModel.cs | 67 +++++++++++++++++++++-- 3 files changed, 63 insertions(+), 7 deletions(-) diff --git a/CUE4Parse b/CUE4Parse index e63d6ee9..afccc891 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit e63d6ee96fba5e5742dcd9bdf7f834e95c3e4f8f +Subproject commit afccc8919be266a63d68125a87a5171615cfc1d2 diff --git a/FModel/ViewModels/AudioPlayerViewModel.cs b/FModel/ViewModels/AudioPlayerViewModel.cs index e8264f83..ec65f5cb 100644 --- a/FModel/ViewModels/AudioPlayerViewModel.cs +++ b/FModel/ViewModels/AudioPlayerViewModel.cs @@ -569,6 +569,7 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable case "wem": case "at9": case "raw": + case "hca": { if (TryConvert(out var wavFilePath)) { diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index c462e975..f7987da6 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -24,6 +24,7 @@ using CUE4Parse.UE4.AssetRegistry; using CUE4Parse.UE4.Assets; using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports.Animation; +using CUE4Parse.UE4.Assets.Exports.CriWare; using CUE4Parse.UE4.Assets.Exports.Fmod; using CUE4Parse.UE4.Assets.Exports.Material; using CUE4Parse.UE4.Assets.Exports.SkeletalMesh; @@ -33,6 +34,8 @@ using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse.UE4.Assets.Exports.Verse; using CUE4Parse.UE4.Assets.Exports.Wwise; using CUE4Parse.UE4.BinaryConfig; +using CUE4Parse.UE4.CriWare; +using CUE4Parse.UE4.CriWare.Readers; using CUE4Parse.UE4.FMod; using CUE4Parse.UE4.IO; using CUE4Parse.UE4.Localization; @@ -134,7 +137,9 @@ public class CUE4ParseViewModel : ViewModel private Lazy _wwiseProviderLazy; public WwiseProvider WwiseProvider => _wwiseProviderLazy.Value; private Lazy _fmodProviderLazy; - public FModProvider FmodProvider => _fmodProviderLazy?.Value; + public FModProvider FmodProvider => _fmodProviderLazy?.Value; + private Lazy _criWareProviderLazy; + public CriWareProvider CriWareProvider => _criWareProviderLazy?.Value; public ConcurrentBag UnknownExtensions = []; public CUE4ParseViewModel() @@ -288,7 +293,8 @@ public class CUE4ParseViewModel : ViewModel Provider.Initialize(); _wwiseProviderLazy = new Lazy(() => new WwiseProvider(Provider, UserSettings.Default.WwiseMaxBnkPrefetch)); - _fmodProviderLazy = new Lazy(() => new FModProvider(Provider, UserSettings.Default.GameDirectory)); + _fmodProviderLazy = new Lazy(() => new FModProvider(Provider, UserSettings.Default.GameDirectory)); + _criWareProviderLazy = new Lazy(() => new CriWareProvider(Provider, UserSettings.Default.GameDirectory)); Log.Information($"{Provider.Versions.Game} ({Provider.Versions.Platform}) | Archives: x{Provider.UnloadedVfs.Count} | AES: x{Provider.RequiredKeys.Count} | Loose Files: x{Provider.Files.Count}"); }); } @@ -769,7 +775,36 @@ public class CUE4ParseViewModel : ViewModel } break; - } + } + case "awb": + { + var archive = entry.CreateReader(); + var awbReader = new AwbReader(archive); + + var extractedSounds = CriWareProvider.ExtractCriWareSounds(awbReader, archive.Name); + foreach (var sound in extractedSounds) + { + SaveAndPlaySound(Path.Combine("/Criware/", sound.Name), sound.Extension, sound.Data, saveAudio); + } + + break; + } + + case "acb": + { + var archive = entry.CreateReader(); + var acbReader = new AcbReader(archive); + + TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(acbReader, Formatting.Indented), saveProperties, updateUi); + + var extractedSounds = CriWareProvider.ExtractCriWareSounds(acbReader, archive.Name); + foreach (var sound in extractedSounds) + { + SaveAndPlaySound(Path.Combine("/Criware/", sound.Name), sound.Extension, sound.Data, saveAudio); + } + + break; + } case "xvag": case "flac": case "at9": @@ -994,8 +1029,28 @@ public class CUE4ParseViewModel : ViewModel SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); } return false; - } - case UAkMediaAssetData when isNone || saveAudio: + } + case USoundAtomCueSheet when isNone && pointer.Object.Value is USoundAtomCueSheet atomCueSheet: + { + var extractedSounds = CriWareProvider.ExtractCriWareSounds(atomCueSheet); + var directory = Path.GetDirectoryName(atomCueSheet.Owner?.Name) ?? "/Criware/"; + foreach (var sound in extractedSounds) + { + SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); + } + return false; + } + case UAtomCueSheet when isNone && pointer.Object.Value is UAtomCueSheet atomCueSheet: + { + var extractedSounds = CriWareProvider.ExtractCriWareSounds(atomCueSheet); + var directory = Path.GetDirectoryName(atomCueSheet.Owner?.Name) ?? "/Criware/"; + foreach (var sound in extractedSounds) + { + SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); + } + return false; + } + case UAkMediaAssetData when isNone || saveAudio: case USoundWave when isNone || saveAudio: { var shouldDecompress = UserSettings.Default.CompressedAudioMode == ECompressedAudio.PlayDecompressed; @@ -1216,4 +1271,4 @@ public class CUE4ParseViewModel : ViewModel { return (a & b) == b; } -} +} From 2888c9261c1810a29fc680ae8e27f5362159d764 Mon Sep 17 00:00:00 2001 From: Masusder <59669685+Masusder@users.noreply.github.com> Date: Fri, 7 Nov 2025 16:42:41 +0100 Subject: [PATCH 2/5] Criware decrypt key setting / HCA decoder --- CUE4Parse | 2 +- FModel/Settings/DirectorySettings.cs | 12 +++++- FModel/ViewModels/AudioPlayerViewModel.cs | 41 +++++++++++++++--- FModel/ViewModels/CUE4ParseViewModel.cs | 43 ++++++++++--------- FModel/ViewModels/SettingsViewModel.cs | 14 ++++++- FModel/Views/SettingsView.xaml | 21 ++++++++++ FModel/Views/SettingsView.xaml.cs | 51 +++++++++++++++++++++++ 7 files changed, 152 insertions(+), 32 deletions(-) diff --git a/CUE4Parse b/CUE4Parse index afccc891..40813f38 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit afccc8919be266a63d68125a87a5171615cfc1d2 +Subproject commit 40813f382f3f7ab92cbc3597b1f2ab8b29db836a diff --git a/FModel/Settings/DirectorySettings.cs b/FModel/Settings/DirectorySettings.cs index 6b977cbb..8cf126c7 100644 --- a/FModel/Settings/DirectorySettings.cs +++ b/FModel/Settings/DirectorySettings.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse.UE4.Versions; @@ -24,7 +24,8 @@ public class DirectorySettings : ViewModel, ICloneable Endpoints = old?.Endpoints ?? EndpointSettings.Default(gameName), Directories = old?.Directories ?? CustomDirectory.Default(gameName), AesKeys = old?.AesKeys ?? new AesResponse { MainKey = aes, DynamicKeys = null }, - LastAesReload = old?.LastAesReload ?? DateTime.Today.AddDays(-1) + LastAesReload = old?.LastAesReload ?? DateTime.Today.AddDays(-1), + CriwareDecryptionKey = old?.CriwareDecryptionKey ?? 0 }; } @@ -98,6 +99,13 @@ public class DirectorySettings : ViewModel, ICloneable set => SetProperty(ref _lastAesReload, value); } + private ulong _criwareDecryptionKey; + public ulong CriwareDecryptionKey + { + get => _criwareDecryptionKey; + set => SetProperty(ref _criwareDecryptionKey, value); + } + private bool Equals(DirectorySettings other) { return GameDirectory == other.GameDirectory && UeVersion == other.UeVersion; diff --git a/FModel/ViewModels/AudioPlayerViewModel.cs b/FModel/ViewModels/AudioPlayerViewModel.cs index ec65f5cb..2187bcbd 100644 --- a/FModel/ViewModels/AudioPlayerViewModel.cs +++ b/FModel/ViewModels/AudioPlayerViewModel.cs @@ -1,7 +1,3 @@ -using CSCore; -using CSCore.DSP; -using CSCore.SoundOut; -using CSCore.Streams; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -12,7 +8,12 @@ using System.Linq; using System.Threading; using System.Windows; using System.Windows.Data; +using CSCore; using CSCore.CoreAudioAPI; +using CSCore.DSP; +using CSCore.SoundOut; +using CSCore.Streams; +using CUE4Parse.UE4.CriWare.Decoders.HCA; using CUE4Parse.Utils; using FModel.Extensions; using FModel.Framework; @@ -569,7 +570,6 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable case "wem": case "at9": case "raw": - case "hca": { if (TryConvert(out var wavFilePath)) { @@ -580,6 +580,37 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable return false; } + case "hca": + { + try + { + byte[] wavData = HcaWaveStream.ConvertHcaToWav(SelectedAudioFile.Data, UserSettings.Default.CurrentDir.CriwareDecryptionKey); + + string outputDir = Path.Combine(UserSettings.Default.OutputDirectory, ".data"); + Directory.CreateDirectory(outputDir); + + string wavFilePath = Path.Combine(outputDir, + Path.ChangeExtension(SelectedAudioFile.FileName, ".wav")); + + File.WriteAllBytes(wavFilePath, wavData); + + var newAudio = new AudioFile(SelectedAudioFile.Id, new FileInfo(wavFilePath)); + Replace(newAudio); + + return true; + } + catch (CriwareDecryptionException ex) + { + FLogger.Append(ELog.Error, () => FLogger.Text($"Failed to convert HCA: '{ex.Message}'", Constants.WHITE, true)); + Log.Error($"Failed to convert HCA: {ex.Message}"); + return false; + } + catch (Exception ex) + { + Log.Error($"Failed to convert HCA: {ex.Message}"); + return false; + } + } case "rada": case "binka": { diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index f7987da6..9637a107 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -777,34 +777,33 @@ public class CUE4ParseViewModel : ViewModel break; } case "awb": + { + var archive = entry.CreateReader(); + var awbReader = new AwbReader(archive); + + var extractedSounds = CriWareProvider.ExtractCriWareSounds(awbReader, archive.Name); + foreach (var sound in extractedSounds) { - var archive = entry.CreateReader(); - var awbReader = new AwbReader(archive); - - var extractedSounds = CriWareProvider.ExtractCriWareSounds(awbReader, archive.Name); - foreach (var sound in extractedSounds) - { - SaveAndPlaySound(Path.Combine("/Criware/", sound.Name), sound.Extension, sound.Data, saveAudio); - } - - break; + SaveAndPlaySound(Path.Combine("/Criware/", sound.Name), sound.Extension, sound.Data, saveAudio); } + break; + } case "acb": + { + var archive = entry.CreateReader(); + var acbReader = new AcbReader(archive); + + TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(acbReader, Formatting.Indented), saveProperties, updateUi); + + var extractedSounds = CriWareProvider.ExtractCriWareSounds(acbReader, archive.Name); + foreach (var sound in extractedSounds) { - var archive = entry.CreateReader(); - var acbReader = new AcbReader(archive); + SaveAndPlaySound(Path.Combine("/Criware/", sound.Name), sound.Extension, sound.Data, saveAudio); + } - TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(acbReader, Formatting.Indented), saveProperties, updateUi); - - var extractedSounds = CriWareProvider.ExtractCriWareSounds(acbReader, archive.Name); - foreach (var sound in extractedSounds) - { - SaveAndPlaySound(Path.Combine("/Criware/", sound.Name), sound.Extension, sound.Data, saveAudio); - } - - break; - } + break; + } case "xvag": case "flac": case "at9": diff --git a/FModel/ViewModels/SettingsViewModel.cs b/FModel/ViewModels/SettingsViewModel.cs index b733fc61..becbf3a2 100644 --- a/FModel/ViewModels/SettingsViewModel.cs +++ b/FModel/ViewModels/SettingsViewModel.cs @@ -2,14 +2,14 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using CUE4Parse.UE4.Assets.Exports.Material; +using CUE4Parse.UE4.Assets.Exports.Nanite; using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse.UE4.Objects.Core.Serialization; using CUE4Parse.UE4.Versions; using CUE4Parse_Conversion.Meshes; using CUE4Parse_Conversion.Textures; using CUE4Parse_Conversion.UEFormat.Enums; -using CUE4Parse.UE4.Assets.Exports.Material; -using CUE4Parse.UE4.Assets.Exports.Nanite; using FModel.Framework; using FModel.Services; using FModel.Settings; @@ -165,6 +165,13 @@ public class SettingsViewModel : ViewModel set => SetProperty(ref _selectedTextureExportFormat, value); } + private ulong _criwareDecryptionKey; + public ulong CriwareDecryptionKey + { + get => _criwareDecryptionKey; + set => SetProperty(ref _criwareDecryptionKey, value); + } + public bool SocketSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.ActorX; public bool CompressionSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.UEFormat; @@ -227,6 +234,7 @@ public class SettingsViewModel : ViewModel _customVersionsSnapshot = UserSettings.Default.CurrentDir.Versioning.CustomVersions; _optionsSnapshot = UserSettings.Default.CurrentDir.Versioning.Options; _mapStructTypesSnapshot = UserSettings.Default.CurrentDir.Versioning.MapStructTypes; + _criwareDecryptionKey = UserSettings.Default.CurrentDir.CriwareDecryptionKey; AesEndpoint = UserSettings.Default.CurrentDir.Endpoints[0]; MappingEndpoint = UserSettings.Default.CurrentDir.Endpoints[1]; @@ -262,6 +270,7 @@ public class SettingsViewModel : ViewModel SelectedNaniteMeshExportFormat = _naniteMeshExportFormatSnapshot; SelectedMaterialExportFormat = _materialExportFormatSnapshot; SelectedTextureExportFormat = _textureExportFormatSnapshot; + CriwareDecryptionKey = _criwareDecryptionKey; SelectedAesReload = UserSettings.Default.AesReload; SelectedDiscordRpc = UserSettings.Default.DiscordRpc; @@ -308,6 +317,7 @@ public class SettingsViewModel : ViewModel UserSettings.Default.CurrentDir.Versioning.CustomVersions = SelectedCustomVersions; UserSettings.Default.CurrentDir.Versioning.Options = SelectedOptions; UserSettings.Default.CurrentDir.Versioning.MapStructTypes = SelectedMapStructTypes; + UserSettings.Default.CurrentDir.CriwareDecryptionKey = CriwareDecryptionKey; UserSettings.Default.AssetLanguage = SelectedAssetLanguage; UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio; diff --git a/FModel/Views/SettingsView.xaml b/FModel/Views/SettingsView.xaml index 744af781..74562957 100644 --- a/FModel/Views/SettingsView.xaml +++ b/FModel/Views/SettingsView.xaml @@ -46,6 +46,7 @@ + @@ -238,6 +239,26 @@ + + + + + diff --git a/FModel/Views/SettingsView.xaml.cs b/FModel/Views/SettingsView.xaml.cs index 9321f41a..4c3ffde7 100644 --- a/FModel/Views/SettingsView.xaml.cs +++ b/FModel/Views/SettingsView.xaml.cs @@ -1,4 +1,7 @@ +using System; +using System.Globalization; using System.IO; +using System.Linq; using System.Windows; using System.Windows.Controls; using FModel.Services; @@ -190,4 +193,52 @@ public partial class SettingsView _applicationView.SettingsView.MappingEndpoint, "Endpoint Configuration (Mapping)", EEndpointType.Mapping); editor.ShowDialog(); } + + private void CriwareKeyBox_Loaded(object sender, RoutedEventArgs e) + { + if (sender is not TextBox textBox) + return; + + textBox.Text = _applicationView.SettingsView.CriwareDecryptionKey.ToString(); + } + + private void CriwareKeyBox_TextChanged(object sender, TextChangedEventArgs e) + { + if (sender is not TextBox textBox) + return; + + string input = textBox.Text?.Trim() ?? string.Empty; + + if (string.IsNullOrEmpty(input)) + return; + + if (TryParseKey(input, out ulong parsed)) + _applicationView.SettingsView.CriwareDecryptionKey = parsed; + } + + private static bool TryParseKey(string text, out ulong value) + { + value = 0; + if (string.IsNullOrWhiteSpace(text)) + return false; + + bool isHex = false; + if (text.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + { + isHex = true; + text = text[2..]; + } + else if (text.Any(char.IsLetter)) + { + isHex = true; + } + + int numberBase = text.All(Uri.IsHexDigit) ? 16 : 10; + return ulong.TryParse( + text, + isHex ? NumberStyles.HexNumber : NumberStyles.Integer, + CultureInfo.InvariantCulture, + out value + ); + } } From c525243c5003b20ed69b52bca1e152ced3226643 Mon Sep 17 00:00:00 2001 From: Masusder <59669685+Masusder@users.noreply.github.com> Date: Fri, 7 Nov 2025 22:47:21 +0100 Subject: [PATCH 3/5] AWB json converter, fix paths --- CUE4Parse | 2 +- FModel/ViewModels/AudioPlayerViewModel.cs | 12 ++++---- FModel/ViewModels/CUE4ParseViewModel.cs | 34 +++++++++++------------ 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/CUE4Parse b/CUE4Parse index 40813f38..b4ac4238 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 40813f382f3f7ab92cbc3597b1f2ab8b29db836a +Subproject commit b4ac4238fea01ef2ca2cb3849d135552e320ea9a diff --git a/FModel/ViewModels/AudioPlayerViewModel.cs b/FModel/ViewModels/AudioPlayerViewModel.cs index 2187bcbd..ba4ce7ef 100644 --- a/FModel/ViewModels/AudioPlayerViewModel.cs +++ b/FModel/ViewModels/AudioPlayerViewModel.cs @@ -586,11 +586,10 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable { byte[] wavData = HcaWaveStream.ConvertHcaToWav(SelectedAudioFile.Data, UserSettings.Default.CurrentDir.CriwareDecryptionKey); - string outputDir = Path.Combine(UserSettings.Default.OutputDirectory, ".data"); - Directory.CreateDirectory(outputDir); + string wavFilePath = Path.Combine(UserSettings.Default.AudioDirectory, SelectedAudioFile.FilePath.TrimStart('/')); + wavFilePath = Path.ChangeExtension(wavFilePath, ".wav"); - string wavFilePath = Path.Combine(outputDir, - Path.ChangeExtension(SelectedAudioFile.FileName, ".wav")); + Directory.CreateDirectory(Path.GetDirectoryName(wavFilePath)!); File.WriteAllBytes(wavFilePath, wavData); @@ -601,12 +600,13 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable } catch (CriwareDecryptionException ex) { - FLogger.Append(ELog.Error, () => FLogger.Text($"Failed to convert HCA: '{ex.Message}'", Constants.WHITE, true)); - Log.Error($"Failed to convert HCA: {ex.Message}"); + FLogger.Append(ELog.Error, () => FLogger.Text($"Encrypted HCA: {ex.Message}", Constants.WHITE, true)); + Log.Error($"Encrypted HCA: {ex.Message}"); return false; } catch (Exception ex) { + FLogger.Append(ELog.Error, () => FLogger.Text($"Failed to convert HCA: {ex.Message}", Constants.WHITE, true)); Log.Error($"Failed to convert HCA: {ex.Message}"); return false; } diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 9637a107..05bc3eb4 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -781,6 +781,8 @@ public class CUE4ParseViewModel : ViewModel var archive = entry.CreateReader(); var awbReader = new AwbReader(archive); + TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(awbReader, Formatting.Indented), saveProperties, updateUi); + var extractedSounds = CriWareProvider.ExtractCriWareSounds(awbReader, archive.Name); foreach (var sound in extractedSounds) { @@ -1029,26 +1031,24 @@ public class CUE4ParseViewModel : ViewModel } return false; } - case USoundAtomCueSheet when isNone && pointer.Object.Value is USoundAtomCueSheet atomCueSheet: + case USoundAtomCueSheet or UAtomCueSheet or USoundAtomCue or UAtomWaveBank when (isNone || saveAudio) && pointer.Object.Value is UObject atomObject: + { + var extractedSounds = atomObject switch { - var extractedSounds = CriWareProvider.ExtractCriWareSounds(atomCueSheet); - var directory = Path.GetDirectoryName(atomCueSheet.Owner?.Name) ?? "/Criware/"; - foreach (var sound in extractedSounds) - { - SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); - } - return false; - } - case UAtomCueSheet when isNone && pointer.Object.Value is UAtomCueSheet atomCueSheet: + USoundAtomCueSheet cueSheet => CriWareProvider.ExtractCriWareSounds(cueSheet), + UAtomCueSheet cueSheet => CriWareProvider.ExtractCriWareSounds(cueSheet), + USoundAtomCue cue => CriWareProvider.ExtractCriWareSounds(cue), + UAtomWaveBank awb => CriWareProvider.ExtractCriWareSounds(awb), + _ => [] + }; + + var directory = Path.GetDirectoryName(atomObject.Owner?.Name) ?? "/Criware/"; + foreach (var sound in extractedSounds) { - var extractedSounds = CriWareProvider.ExtractCriWareSounds(atomCueSheet); - var directory = Path.GetDirectoryName(atomCueSheet.Owner?.Name) ?? "/Criware/"; - foreach (var sound in extractedSounds) - { - SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); - } - return false; + SaveAndPlaySound(Path.Combine(directory, sound.Name).Replace("\\", "/"), sound.Extension, sound.Data, saveAudio); } + return false; + } case UAkMediaAssetData when isNone || saveAudio: case USoundWave when isNone || saveAudio: { From 3b82f14290964153aa48565484b896f94e74d663 Mon Sep 17 00:00:00 2001 From: Masusder <59669685+Masusder@users.noreply.github.com> Date: Tue, 11 Nov 2025 13:24:34 +0100 Subject: [PATCH 4/5] Added ADX decoder --- CUE4Parse | 2 +- FModel/ViewModels/AudioPlayerViewModel.cs | 76 ++++++++++++++--------- FModel/ViewModels/CUE4ParseViewModel.cs | 7 ++- 3 files changed, 52 insertions(+), 33 deletions(-) diff --git a/CUE4Parse b/CUE4Parse index b4ac4238..8713445a 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit b4ac4238fea01ef2ca2cb3849d135552e320ea9a +Subproject commit 8713445a76718e26f58d1a85d9dab114979d1bfc diff --git a/FModel/ViewModels/AudioPlayerViewModel.cs b/FModel/ViewModels/AudioPlayerViewModel.cs index ba4ce7ef..a8abbe9b 100644 --- a/FModel/ViewModels/AudioPlayerViewModel.cs +++ b/FModel/ViewModels/AudioPlayerViewModel.cs @@ -13,6 +13,8 @@ using CSCore.CoreAudioAPI; using CSCore.DSP; using CSCore.SoundOut; using CSCore.Streams; +using CUE4Parse.UE4.CriWare.Decoders; +using CUE4Parse.UE4.CriWare.Decoders.ADX; using CUE4Parse.UE4.CriWare.Decoders.HCA; using CUE4Parse.Utils; using FModel.Extensions; @@ -580,37 +582,9 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable return false; } + case "adx": case "hca": - { - try - { - byte[] wavData = HcaWaveStream.ConvertHcaToWav(SelectedAudioFile.Data, UserSettings.Default.CurrentDir.CriwareDecryptionKey); - - string wavFilePath = Path.Combine(UserSettings.Default.AudioDirectory, SelectedAudioFile.FilePath.TrimStart('/')); - wavFilePath = Path.ChangeExtension(wavFilePath, ".wav"); - - Directory.CreateDirectory(Path.GetDirectoryName(wavFilePath)!); - - File.WriteAllBytes(wavFilePath, wavData); - - var newAudio = new AudioFile(SelectedAudioFile.Id, new FileInfo(wavFilePath)); - Replace(newAudio); - - return true; - } - catch (CriwareDecryptionException ex) - { - FLogger.Append(ELog.Error, () => FLogger.Text($"Encrypted HCA: {ex.Message}", Constants.WHITE, true)); - Log.Error($"Encrypted HCA: {ex.Message}"); - return false; - } - catch (Exception ex) - { - FLogger.Append(ELog.Error, () => FLogger.Text($"Failed to convert HCA: {ex.Message}", Constants.WHITE, true)); - Log.Error($"Failed to convert HCA: {ex.Message}"); - return false; - } - } + return TryConvertCriware(); case "rada": case "binka": { @@ -628,6 +602,48 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable return true; } + private bool TryConvertCriware() + { + try + { + byte[] wavData = SelectedAudioFile.Extension switch + { + "hca" => HcaWaveStream.ConvertHcaToWav( + SelectedAudioFile.Data, + UserSettings.Default.CurrentDir.CriwareDecryptionKey), + "adx" => AdxDecoder.ConvertAdxToWav( + SelectedAudioFile.Data, + UserSettings.Default.CurrentDir.CriwareDecryptionKey), + _ => throw new NotSupportedException() + }; + + string wavFilePath = Path.Combine( + UserSettings.Default.AudioDirectory, + SelectedAudioFile.FilePath.TrimStart('/')); + wavFilePath = Path.ChangeExtension(wavFilePath, ".wav"); + + Directory.CreateDirectory(Path.GetDirectoryName(wavFilePath)!); + File.WriteAllBytes(wavFilePath, wavData); + + var newAudio = new AudioFile(SelectedAudioFile.Id, new FileInfo(wavFilePath)); + Replace(newAudio); + + return true; + } + catch (CriwareDecryptionException ex) + { + FLogger.Append(ELog.Error, () => FLogger.Text($"Encrypted {SelectedAudioFile.Extension.ToUpper()}: {ex.Message}", Constants.WHITE, true)); + Log.Error($"Encrypted {SelectedAudioFile.Extension.ToUpper()}: {ex.Message}"); + return false; + } + catch (Exception ex) + { + FLogger.Append(ELog.Error, () => FLogger.Text($"Failed to convert {SelectedAudioFile.Extension.ToUpper()}: {ex.Message}", Constants.WHITE, true)); + Log.Error($"Failed to convert {SelectedAudioFile.Extension.ToUpper()}: {ex.Message}"); + return false; + } + } + private bool TryConvert(out string wavFilePath) => TryConvert(SelectedAudioFile.FilePath, SelectedAudioFile.Data, out wavFilePath); private bool TryConvert(string inputFilePath, byte[] inputFileData, out string wavFilePath) { diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 05bc3eb4..3ef8fd61 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -783,10 +783,11 @@ public class CUE4ParseViewModel : ViewModel TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(awbReader, Formatting.Indented), saveProperties, updateUi); + var directory = Path.GetDirectoryName(archive.Name) ?? "/Criware/"; var extractedSounds = CriWareProvider.ExtractCriWareSounds(awbReader, archive.Name); foreach (var sound in extractedSounds) { - SaveAndPlaySound(Path.Combine("/Criware/", sound.Name), sound.Extension, sound.Data, saveAudio); + SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); } break; @@ -798,10 +799,11 @@ public class CUE4ParseViewModel : ViewModel TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(acbReader, Formatting.Indented), saveProperties, updateUi); + var directory = Path.GetDirectoryName(archive.Name) ?? "/Criware/"; var extractedSounds = CriWareProvider.ExtractCriWareSounds(acbReader, archive.Name); foreach (var sound in extractedSounds) { - SaveAndPlaySound(Path.Combine("/Criware/", sound.Name), sound.Extension, sound.Data, saveAudio); + SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); } break; @@ -1043,6 +1045,7 @@ public class CUE4ParseViewModel : ViewModel }; var directory = Path.GetDirectoryName(atomObject.Owner?.Name) ?? "/Criware/"; + directory = Path.GetDirectoryName(atomObject.Owner.Provider.FixPath(directory)); foreach (var sound in extractedSounds) { SaveAndPlaySound(Path.Combine(directory, sound.Name).Replace("\\", "/"), sound.Extension, sound.Data, saveAudio); From 5b13bb0b46f1b00047dcec4ed98c215a4edeffb7 Mon Sep 17 00:00:00 2001 From: LongerWarrior Date: Wed, 12 Nov 2025 12:57:14 +0200 Subject: [PATCH 5/5] Skip emissive --- CUE4Parse | 2 +- FModel/ViewModels/CUE4ParseViewModel.cs | 113 ++++++++++++----------- FModel/Views/Snooper/Options.cs | 12 +++ FModel/Views/Snooper/Shading/Material.cs | 15 ++- 4 files changed, 80 insertions(+), 62 deletions(-) diff --git a/CUE4Parse b/CUE4Parse index 8713445a..0f442379 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 8713445a76718e26f58d1a85d9dab114979d1bfc +Subproject commit 0f442379903ce789dfef2ed2c784120525036b81 diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 3ef8fd61..fc12685d 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -137,7 +137,7 @@ public class CUE4ParseViewModel : ViewModel private Lazy _wwiseProviderLazy; public WwiseProvider WwiseProvider => _wwiseProviderLazy.Value; private Lazy _fmodProviderLazy; - public FModProvider FmodProvider => _fmodProviderLazy?.Value; + public FModProvider FmodProvider => _fmodProviderLazy?.Value; private Lazy _criWareProviderLazy; public CriWareProvider CriWareProvider => _criWareProviderLazy?.Value; public ConcurrentBag UnknownExtensions = []; @@ -293,7 +293,7 @@ public class CUE4ParseViewModel : ViewModel Provider.Initialize(); _wwiseProviderLazy = new Lazy(() => new WwiseProvider(Provider, UserSettings.Default.WwiseMaxBnkPrefetch)); - _fmodProviderLazy = new Lazy(() => new FModProvider(Provider, UserSettings.Default.GameDirectory)); + _fmodProviderLazy = new Lazy(() => new FModProvider(Provider, UserSettings.Default.GameDirectory)); _criWareProviderLazy = new Lazy(() => new CriWareProvider(Provider, UserSettings.Default.GameDirectory)); Log.Information($"{Provider.Versions.Game} ({Provider.Versions.Platform}) | Archives: x{Provider.UnloadedVfs.Count} | AES: x{Provider.RequiredKeys.Count} | Loose Files: x{Provider.Files.Count}"); }); @@ -648,6 +648,7 @@ public class CUE4ParseViewModel : ViewModel case "dnearchive": // Banishers: Ghosts of New Eden case "gitignore": case "LICENSE": + case "playstats": // Dispatch case "template": case "stUMeta": // LIS: Double Exposure case "vmodule": @@ -775,38 +776,38 @@ public class CUE4ParseViewModel : ViewModel } break; - } - case "awb": - { - var archive = entry.CreateReader(); - var awbReader = new AwbReader(archive); - - TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(awbReader, Formatting.Indented), saveProperties, updateUi); - - var directory = Path.GetDirectoryName(archive.Name) ?? "/Criware/"; - var extractedSounds = CriWareProvider.ExtractCriWareSounds(awbReader, archive.Name); - foreach (var sound in extractedSounds) - { - SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); - } - - break; - } - case "acb": - { - var archive = entry.CreateReader(); - var acbReader = new AcbReader(archive); - - TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(acbReader, Formatting.Indented), saveProperties, updateUi); - - var directory = Path.GetDirectoryName(archive.Name) ?? "/Criware/"; - var extractedSounds = CriWareProvider.ExtractCriWareSounds(acbReader, archive.Name); - foreach (var sound in extractedSounds) - { - SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); - } - - break; + } + case "awb": + { + var archive = entry.CreateReader(); + var awbReader = new AwbReader(archive); + + TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(awbReader, Formatting.Indented), saveProperties, updateUi); + + var directory = Path.GetDirectoryName(archive.Name) ?? "/Criware/"; + var extractedSounds = CriWareProvider.ExtractCriWareSounds(awbReader, archive.Name); + foreach (var sound in extractedSounds) + { + SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); + } + + break; + } + case "acb": + { + var archive = entry.CreateReader(); + var acbReader = new AcbReader(archive); + + TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(acbReader, Formatting.Indented), saveProperties, updateUi); + + var directory = Path.GetDirectoryName(archive.Name) ?? "/Criware/"; + var extractedSounds = CriWareProvider.ExtractCriWareSounds(acbReader, archive.Name); + foreach (var sound in extractedSounds) + { + SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); + } + + break; } case "xvag": case "flac": @@ -1032,27 +1033,27 @@ public class CUE4ParseViewModel : ViewModel SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio); } return false; - } - case USoundAtomCueSheet or UAtomCueSheet or USoundAtomCue or UAtomWaveBank when (isNone || saveAudio) && pointer.Object.Value is UObject atomObject: - { - var extractedSounds = atomObject switch - { - USoundAtomCueSheet cueSheet => CriWareProvider.ExtractCriWareSounds(cueSheet), - UAtomCueSheet cueSheet => CriWareProvider.ExtractCriWareSounds(cueSheet), - USoundAtomCue cue => CriWareProvider.ExtractCriWareSounds(cue), - UAtomWaveBank awb => CriWareProvider.ExtractCriWareSounds(awb), - _ => [] - }; - - var directory = Path.GetDirectoryName(atomObject.Owner?.Name) ?? "/Criware/"; - directory = Path.GetDirectoryName(atomObject.Owner.Provider.FixPath(directory)); - foreach (var sound in extractedSounds) - { - SaveAndPlaySound(Path.Combine(directory, sound.Name).Replace("\\", "/"), sound.Extension, sound.Data, saveAudio); - } - return false; - } - case UAkMediaAssetData when isNone || saveAudio: + } + case USoundAtomCueSheet or UAtomCueSheet or USoundAtomCue or UAtomWaveBank when (isNone || saveAudio) && pointer.Object.Value is UObject atomObject: + { + var extractedSounds = atomObject switch + { + USoundAtomCueSheet cueSheet => CriWareProvider.ExtractCriWareSounds(cueSheet), + UAtomCueSheet cueSheet => CriWareProvider.ExtractCriWareSounds(cueSheet), + USoundAtomCue cue => CriWareProvider.ExtractCriWareSounds(cue), + UAtomWaveBank awb => CriWareProvider.ExtractCriWareSounds(awb), + _ => [] + }; + + var directory = Path.GetDirectoryName(atomObject.Owner?.Name) ?? "/Criware/"; + directory = Path.GetDirectoryName(atomObject.Owner.Provider.FixPath(directory)); + foreach (var sound in extractedSounds) + { + SaveAndPlaySound(Path.Combine(directory, sound.Name).Replace("\\", "/"), sound.Extension, sound.Data, saveAudio); + } + return false; + } + case UAkMediaAssetData when isNone || saveAudio: case USoundWave when isNone || saveAudio: { var shouldDecompress = UserSettings.Default.CompressedAudioMode == ECompressedAudio.PlayDecompressed; @@ -1273,4 +1274,4 @@ public class CUE4ParseViewModel : ViewModel { return (a & b) == b; } -} +} diff --git a/FModel/Views/Snooper/Options.cs b/FModel/Views/Snooper/Options.cs index 7b4f434b..aa821f8f 100644 --- a/FModel/Views/Snooper/Options.cs +++ b/FModel/Views/Snooper/Options.cs @@ -240,6 +240,18 @@ public class Options Services.ApplicationService.ApplicationView.CUE4Parse.ModelIsWaitingAnimation = value; } + /// + /// Skip emmisive for specific games, cause of excessive use in their materials + /// + public bool SkipEmmisive() + { + return _game switch + { + "LIESOFP" => true, + _ => false, + }; + } + public void ResetModelsLightsAnimations() { foreach (var model in Models.Values) diff --git a/FModel/Views/Snooper/Shading/Material.cs b/FModel/Views/Snooper/Shading/Material.cs index a6114ab6..c36fcbfe 100644 --- a/FModel/Views/Snooper/Shading/Material.cs +++ b/FModel/Views/Snooper/Shading/Material.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Numerics; @@ -121,10 +121,15 @@ public class Material : IDisposable RoughnessMax = roughness + d; } - if (Parameters.TryGetScalar(out var emissiveMultScalar, "emissive mult", "Emissive_Mult", "EmissiveIntensity", "EmissionIntensity")) - EmissiveMult = emissiveMultScalar; - else if (Parameters.TryGetLinearColor(out var emissiveMultColor, "Emissive Multiplier", "EmissiveMultiplier")) - EmissiveMult = emissiveMultColor.R; + if (!options.SkipEmmisive()) + { + if (Parameters.TryGetScalar(out var emissiveMultScalar, "emissive mult", "Emissive_Mult", "EmissiveIntensity", "EmissionIntensity")) + EmissiveMult = emissiveMultScalar; + else if (Parameters.TryGetLinearColor(out var emissiveMultColor, "Emissive Multiplier", "EmissiveMultiplier")) + EmissiveMult = emissiveMultColor.R; + } + else + EmissiveMult = 0f; if (Parameters.TryGetLinearColor(out var EmissiveUVs, "EmissiveUVs_RG_UpperLeftCorner_BA_LowerRightCorner",