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] 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
+ );
+ }
}