diff --git a/CUE4Parse b/CUE4Parse index e63d6ee9..0f442379 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit e63d6ee96fba5e5742dcd9bdf7f834e95c3e4f8f +Subproject commit 0f442379903ce789dfef2ed2c784120525036b81 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 e8264f83..a8abbe9b 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,14 @@ 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; +using CUE4Parse.UE4.CriWare.Decoders.ADX; +using CUE4Parse.UE4.CriWare.Decoders.HCA; using CUE4Parse.Utils; using FModel.Extensions; using FModel.Framework; @@ -579,6 +582,9 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable return false; } + case "adx": + case "hca": + return TryConvertCriware(); case "rada": case "binka": { @@ -596,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 c462e975..fc12685d 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; @@ -135,6 +138,8 @@ public class CUE4ParseViewModel : ViewModel public WwiseProvider WwiseProvider => _wwiseProviderLazy.Value; private Lazy _fmodProviderLazy; public FModProvider FmodProvider => _fmodProviderLazy?.Value; + private Lazy _criWareProviderLazy; + public CriWareProvider CriWareProvider => _criWareProviderLazy?.Value; public ConcurrentBag UnknownExtensions = []; public CUE4ParseViewModel() @@ -289,6 +294,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)); + _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}"); }); } @@ -642,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": @@ -770,6 +777,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 "xvag": case "flac": case "at9": @@ -995,6 +1034,25 @@ public class CUE4ParseViewModel : ViewModel } 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 USoundWave when isNone || saveAudio: { 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 + ); + } } 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",