mirror of
https://github.com/4sval/FModel.git
synced 2026-06-20 15:00:12 -05:00
Square Enix audio support, Marvel Rivals asset registry fix, DBD update
Some checks are pending
FModel QA Builder / build (push) Waiting to run
Some checks are pending
FModel QA Builder / build (push) Waiting to run
Co-Authored-By: LongerWarrior <37636768+LongerWarrior@users.noreply.github.com> Co-Authored-By: GhostScissors <79089473+GhostScissors@users.noreply.github.com>
This commit is contained in:
parent
e723149db8
commit
02cd52ac0f
|
|
@ -1 +1 @@
|
|||
Subproject commit 166d67076273f6e717adcc15cd57b759abf8e987
|
||||
Subproject commit 75f3878b7a348cbda3927048b142a0a6923e79c0
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
|
|
@ -664,23 +665,11 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
|||
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))
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
var vgmStreamPath = TryGetVgmstreamPath();
|
||||
if (string.IsNullOrEmpty(vgmStreamPath))
|
||||
return false;
|
||||
|
||||
var success = TryConvertToWAV(inputFilePath, inputFileData, vgmFilePath, true, out wavFilePath);
|
||||
var success = TryConvertToWav(inputFilePath, inputFileData, vgmStreamPath, true, out wavFilePath);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
|
|
@ -713,10 +702,10 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
|||
return false;
|
||||
}
|
||||
|
||||
return TryConvertToWAV(SelectedAudioFile.FilePath, SelectedAudioFile.Data, decoderPath, false, out rawFilePath);
|
||||
return TryConvertToWav(SelectedAudioFile.FilePath, SelectedAudioFile.Data, decoderPath, false, out rawFilePath);
|
||||
}
|
||||
|
||||
private static bool TryConvertToWAV(string inputFilePath, byte[] inputFileData, string converterPath, bool usevgmstream, out string wavFilePath)
|
||||
private static bool TryConvertToWav(string inputFilePath, byte[] inputFileData, string converterPath, bool usevgmstream, out string wavFilePath)
|
||||
{
|
||||
wavFilePath = Path.ChangeExtension(inputFilePath, ".wav");
|
||||
var directory = Path.GetDirectoryName(inputFilePath);
|
||||
|
|
@ -746,4 +735,74 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
|||
|
||||
return success;
|
||||
}
|
||||
|
||||
private static string TryGetVgmstreamPath()
|
||||
{
|
||||
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))
|
||||
{
|
||||
Log.Error("Failed to convert audio, vgmstream is missing");
|
||||
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 string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
return vgmFilePath;
|
||||
}
|
||||
|
||||
// Since Square Enix soundbanks are pretty niche, let's just use vgmstream to extract them
|
||||
public static List<string> ExtractSquareEnixAudio(string sabPath, byte[] sqexData)
|
||||
{
|
||||
var vgmStreamPath = TryGetVgmstreamPath();
|
||||
if (string.IsNullOrEmpty(vgmStreamPath))
|
||||
return [];
|
||||
if (sqexData.Length == 0)
|
||||
return [];
|
||||
|
||||
var extractionDir = Path.GetDirectoryName(sabPath);
|
||||
Directory.CreateDirectory(extractionDir);
|
||||
|
||||
// There's no clean way to know what was extracted with vgmstream (it's a soundbank, might contain multiple sounds) so we're monitoring extraction directory
|
||||
var capturedFiles = new ConcurrentBag<string>();
|
||||
using var watcher = new FileSystemWatcher(extractionDir)
|
||||
{
|
||||
Filter = "*.wav",
|
||||
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime
|
||||
};
|
||||
|
||||
void handler(object s, FileSystemEventArgs e) => capturedFiles.Add(e.FullPath);
|
||||
|
||||
watcher.Created += handler;
|
||||
watcher.Changed += handler;
|
||||
watcher.EnableRaisingEvents = true;
|
||||
|
||||
var tempSab = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".sab");
|
||||
File.WriteAllBytes(tempSab, sqexData);
|
||||
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = vgmStreamPath,
|
||||
Arguments = $"-S 0 -o \"{extractionDir}\\?n_?s.wav\" \"{tempSab}\"",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using (var process = Process.Start(startInfo))
|
||||
{
|
||||
process?.WaitForExit(15000);
|
||||
}
|
||||
|
||||
File.Delete(tempSab);
|
||||
watcher.EnableRaisingEvents = false;
|
||||
|
||||
return [.. capturedFiles.Distinct()];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,14 +5,11 @@ using System.Diagnostics;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
using AdonisUI.Controls;
|
||||
|
||||
using CUE4Parse;
|
||||
using CUE4Parse.Compression;
|
||||
using CUE4Parse.Encryption.Aes;
|
||||
|
|
@ -22,11 +19,12 @@ using CUE4Parse.FileProvider.Vfs;
|
|||
using CUE4Parse.GameTypes.Aion2.Objects;
|
||||
using CUE4Parse.GameTypes.AoC.Objects;
|
||||
using CUE4Parse.GameTypes.AshEchoes.FileProvider;
|
||||
using CUE4Parse.GameTypes.SMG.UE4.Assets.Exports.Wwise;
|
||||
using CUE4Parse.GameTypes.KRD.Assets.Exports;
|
||||
using CUE4Parse.GameTypes.Borderlands3.Assets.Exports;
|
||||
using CUE4Parse.GameTypes.Borderlands4.Assets.Exports;
|
||||
using CUE4Parse.GameTypes.Borderlands4.Wwise;
|
||||
using CUE4Parse.GameTypes.Borderlands3.Assets.Exports;
|
||||
using CUE4Parse.GameTypes.KRD.Assets.Exports;
|
||||
using CUE4Parse.GameTypes.SMG.UE4.Assets.Exports.Wwise;
|
||||
using CUE4Parse.GameTypes.SquareEnix.UE4.Assets.Exports;
|
||||
using CUE4Parse.MappingsProvider;
|
||||
using CUE4Parse.UE4.AssetRegistry;
|
||||
using CUE4Parse.UE4.Assets;
|
||||
|
|
@ -57,14 +55,11 @@ using CUE4Parse.UE4.Shaders;
|
|||
using CUE4Parse.UE4.Versions;
|
||||
using CUE4Parse.UE4.Wwise;
|
||||
using CUE4Parse.Utils;
|
||||
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse_Conversion.Sounds;
|
||||
|
||||
using EpicManifestParser;
|
||||
using EpicManifestParser.UE;
|
||||
using EpicManifestParser.ZlibngDotNetDecompressor;
|
||||
|
||||
using FModel.Creator;
|
||||
using FModel.Extensions;
|
||||
using FModel.Framework;
|
||||
|
|
@ -73,21 +68,14 @@ using FModel.Settings;
|
|||
using FModel.Views;
|
||||
using FModel.Views.Resources.Controls;
|
||||
using FModel.Views.Snooper;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
using OpenTK.Windowing.Common;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
|
||||
using Serilog;
|
||||
|
||||
using SkiaSharp;
|
||||
|
||||
using Svg.Skia;
|
||||
|
||||
using UE4Config.Parsing;
|
||||
|
||||
using Application = System.Windows.Application;
|
||||
using FGuid = CUE4Parse.UE4.Objects.Core.Misc.FGuid;
|
||||
|
||||
|
|
@ -1171,20 +1159,20 @@ public class CUE4ParseViewModel : ViewModel
|
|||
case UFMODEvent when (isNone || saveAudio) && pointer.Object.Value is UFMODEvent fmodEvent:
|
||||
{
|
||||
var extractedSounds = FmodProvider.ExtractEventSounds(fmodEvent);
|
||||
var directory = Path.GetDirectoryName(fmodEvent.Owner?.Name) ?? "/FMOD/Desktop/";
|
||||
var directory = Path.GetDirectoryName(Provider.FixPath(fmodEvent.Owner?.Name ?? "/FMOD/Desktop/"));
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(cancellationToken, Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, Path.Combine(directory, sound.Name).Replace("\\", "/"), sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case UFMODBank when (isNone || saveAudio) && pointer.Object.Value is UFMODBank fmodBank:
|
||||
{
|
||||
var extractedSounds = FmodProvider.ExtractBankSounds(fmodBank);
|
||||
var directory = Path.GetDirectoryName(fmodBank.Owner?.Name) ?? "/FMOD/Desktop/";
|
||||
var directory = Path.GetDirectoryName(Provider.FixPath(fmodBank.Owner?.Name ?? "/FMOD/Desktop/"));
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(cancellationToken, Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, Path.Combine(directory, sound.Name).Replace("\\", "/"), sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1207,6 +1195,22 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
return false;
|
||||
}
|
||||
case USQEXSEADSoundBank or USQEXSEADSound when (isNone || saveAudio) && pointer.Object.Value is UObject squareEnixObject:
|
||||
{
|
||||
var data = squareEnixObject switch
|
||||
{
|
||||
USQEXSEADSoundBank sqexSoundBank => sqexSoundBank.SQEXSoundBankData?.Data ?? [],
|
||||
USQEXSEADSound sqexSound => sqexSound.SQEXSoundData?.Data ?? [],
|
||||
_ => [],
|
||||
};
|
||||
var sabPath = Path.Combine(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/').SubstringBeforeLast('/'), squareEnixObject.Name);
|
||||
var extractedSounds = AudioPlayerViewModel.ExtractSquareEnixAudio(sabPath, data);
|
||||
foreach (var soundPath in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(cancellationToken, soundPath, "wav", File.ReadAllBytes(soundPath), saveAudio, updateUi);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case UAkMediaAssetData when isNone || saveAudio:
|
||||
case USoundWave when isNone || saveAudio:
|
||||
{
|
||||
|
|
@ -1443,8 +1447,10 @@ public class CUE4ParseViewModel : ViewModel
|
|||
private void SaveAndPlaySound(CancellationToken cancellationToken, string fullPath, string ext, byte[] data, bool saveAudio, bool updateUi)
|
||||
{
|
||||
if (fullPath.StartsWith('/')) fullPath = fullPath[1..];
|
||||
var savedAudioPath = Path.Combine(UserSettings.Default.AudioDirectory,
|
||||
UserSettings.Default.KeepDirectoryStructure ? fullPath : fullPath.SubstringAfterLast('/')).Replace('\\', '/') + $".{ext.ToLowerInvariant()}";
|
||||
var extLower = ext.ToLowerInvariant();
|
||||
var baseFilePath = UserSettings.Default.KeepDirectoryStructure ? fullPath : fullPath.SubstringAfterLast('/');
|
||||
var combinedPath = Path.Combine(UserSettings.Default.AudioDirectory, baseFilePath);
|
||||
var savedAudioPath = Path.ChangeExtension(combinedPath, extLower).Replace('\\', '/');
|
||||
|
||||
if (saveAudio)
|
||||
{
|
||||
|
|
@ -1453,7 +1459,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
Directory.CreateDirectory(directory);
|
||||
|
||||
bool conversionSuccess = true;
|
||||
if (UserSettings.Default.ConvertAudioOnBulkExport)
|
||||
if (UserSettings.Default.ConvertAudioOnBulkExport && extLower is not "wav")
|
||||
{
|
||||
if (AudioPlayerViewModel.TryConvert(savedAudioPath, data, out string wavFilePath))
|
||||
savedAudioPath = wavFilePath;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using CUE4Parse.GameTypes.Borderlands4.Assets.Exports;
|
|||
using CUE4Parse.GameTypes.FN.Assets.Exports.DataAssets;
|
||||
using CUE4Parse.GameTypes.SMG.UE4.Assets.Exports.Wwise;
|
||||
using CUE4Parse.GameTypes.SMG.UE4.Assets.Objects;
|
||||
using CUE4Parse.GameTypes.SquareEnix.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Animation;
|
||||
|
|
@ -245,9 +246,9 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
|||
|
||||
UFMODBankLookup => (EAssetCategory.Data, EBulkType.None),
|
||||
|
||||
UFMODBus or UFMODSnapshot or UFMODSnapshotReverb or UFMODVCA => (EAssetCategory.Audio, EBulkType.None),
|
||||
UFMODBus or UFMODSnapshot or UFMODSnapshotReverb or UFMODVCA or USQEXSEADSoundAttenuation => (EAssetCategory.Audio, EBulkType.None),
|
||||
|
||||
UFMODBank or UAkAudioBank or UAtomWaveBank or UAkInitBank => (EAssetCategory.SoundBank, EBulkType.Audio),
|
||||
UFMODBank or UAkAudioBank or UAtomWaveBank or UAkInitBank or USQEXSEADSoundBank => (EAssetCategory.SoundBank, EBulkType.Audio),
|
||||
|
||||
UWwiseAssetLibrary or USoundBase or UAkMediaAssetData or UAtomCueSheet
|
||||
or USoundAtomCueSheet or UAkAudioType or UExternalSource or UExternalSourceBank
|
||||
|
|
@ -259,8 +260,8 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
|||
UNiagaraSystem or UNiagaraScriptBase or UParticleSystem => (EAssetCategory.Particle, EBulkType.None),
|
||||
|
||||
// Game specific assets below
|
||||
UBorderlandsDialogObject => (EAssetCategory.Borderlands, EBulkType.None), // Borderlands 3;
|
||||
UGbxGraphAsset or UDialogScriptData or UDialogPerformanceData => (EAssetCategory.Borderlands, EBulkType.Audio), // Borderlands 4; Borderlands 3;
|
||||
UBorderlandsDialogObject when GameVersion is EGame.GAME_Borderlands3 => (EAssetCategory.Borderlands, EBulkType.None), // Borderlands 3;
|
||||
UGbxGraphAsset or UDialogScriptData or UDialogPerformanceData when GameVersion is EGame.GAME_Borderlands4 or EGame.GAME_Borderlands3 => (EAssetCategory.Borderlands, EBulkType.Audio), // Borderlands 4; Borderlands 3;
|
||||
UFaceFXAnimSet when GameVersion is EGame.GAME_Borderlands4 => (EAssetCategory.Borderlands, EBulkType.Audio), // Borderlands 4;
|
||||
|
||||
_ => (EAssetCategory.All, EBulkType.None),
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user