diff --git a/CUE4Parse b/CUE4Parse index af1207f8..32ddcc61 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit af1207f8a099062e48255db4e5cf45ba3c21311d +Subproject commit 32ddcc61539cb8f82a92c8dc2d769ec34375aa37 diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index aeba0307..87f3bfc3 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -10,7 +10,9 @@ 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; @@ -20,6 +22,7 @@ using CUE4Parse.FileProvider.Vfs; using CUE4Parse.GameTypes.Aion2.Objects; using CUE4Parse.GameTypes.AshEchoes.FileProvider; using CUE4Parse.GameTypes.KRD.Assets.Exports; +using CUE4Parse.GameTypes.SMG.UE4.Assets.Exports.Wwise; using CUE4Parse.MappingsProvider; using CUE4Parse.UE4.AssetRegistry; using CUE4Parse.UE4.Assets; @@ -50,11 +53,14 @@ 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; @@ -63,14 +69,21 @@ 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; @@ -296,7 +309,7 @@ public class CUE4ParseViewModel : ViewModel } Provider.Initialize(); - _wwiseProviderLazy = new Lazy(() => new WwiseProvider(Provider, UserSettings.Default.WwiseMaxBnkPrefetch)); + _wwiseProviderLazy = new Lazy(() => new WwiseProvider(Provider, UserSettings.Default.GameDirectory, 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}"); @@ -1061,6 +1074,13 @@ public class CUE4ParseViewModel : ViewModel TabControl.SelectedTab.AddImage(sourceFile.SubstringAfterLast('/'), false, bitmap, false, updateUi); return false; } + // The Dark Pictures Anthology: House of Ashes + case UExternalSource when (isNone || saveAudio) && pointer.Object.Value is UExternalSource externalSource: + { + var audioName = Path.GetFileNameWithoutExtension(externalSource.ExternalSourcePath); + SaveAndPlaySound(audioName, "wem", externalSource.Data?.WemFile ?? [], saveAudio); + return false; + } case UAkAudioEvent when (isNone || saveAudio) && pointer.Object.Value is UAkAudioEvent audioEvent: { var extractedSounds = WwiseProvider.ExtractAudioEventSounds(audioEvent); diff --git a/FModel/ViewModels/GameFileViewModel.cs b/FModel/ViewModels/GameFileViewModel.cs index e0d84c7f..d13390d1 100644 --- a/FModel/ViewModels/GameFileViewModel.cs +++ b/FModel/ViewModels/GameFileViewModel.cs @@ -5,8 +5,10 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; + using CUE4Parse.FileProvider.Objects; using CUE4Parse.GameTypes.FN.Assets.Exports.DataAssets; +using CUE4Parse.GameTypes.SMG.UE4.Assets.Exports.Wwise; using CUE4Parse.UE4.Assets; using CUE4Parse.UE4.Assets.Exports.Animation; using CUE4Parse.UE4.Assets.Exports.BuildData; @@ -37,12 +39,17 @@ using CUE4Parse.UE4.Objects.RigVM; using CUE4Parse.UE4.Objects.UObject; using CUE4Parse.UE4.Objects.UObject.Editor; using CUE4Parse.Utils; + using CUE4Parse_Conversion.Textures; + using FModel.Framework; using FModel.Services; using FModel.Settings; + using Serilog; + using SkiaSharp; + using Svg.Skia; namespace FModel.ViewModels; @@ -200,7 +207,8 @@ public class GameFileViewModel(GameFile asset) : ViewModel UCurveBase => EAssetCategory.CurveBase, UWwiseAssetLibrary or USoundBase or UAkMediaAssetData or UAtomWaveBank or USoundAtomCue - or UAtomCueSheet or USoundAtomCueSheet or UFMODBank or UFMODEvent or UAkAudioType => EAssetCategory.Audio, + or UAtomCueSheet or USoundAtomCueSheet or UFMODBank or UFMODEvent or UAkAudioType + or UExternalSource or UExternalSourceBank => EAssetCategory.Audio, UFileMediaSource => EAssetCategory.Video, UFont or UFontFace => EAssetCategory.Font, diff --git a/FModel/ViewModels/GameSelectorViewModel.cs b/FModel/ViewModels/GameSelectorViewModel.cs index f700a507..9cd43d67 100644 --- a/FModel/ViewModels/GameSelectorViewModel.cs +++ b/FModel/ViewModels/GameSelectorViewModel.cs @@ -87,8 +87,8 @@ public class GameSelectorViewModel : ViewModel .OrderBy(value => ((int)value & 0xFF) == 0); private IEnumerable EnumerateDetectedGames() { - yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks", EGame.GAME_UE5_7); - yield return DirectorySettings.Default("Fortnite [LIVE]", Constants._FN_LIVE_TRIGGER, ue: EGame.GAME_UE5_7); + yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks", EGame.GAME_UE5_8); + yield return DirectorySettings.Default("Fortnite [LIVE]", Constants._FN_LIVE_TRIGGER, ue: EGame.GAME_UE5_8); yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks", EGame.GAME_RogueCompany); yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks", EGame.GAME_UE4_21); yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks", EGame.GAME_Borderlands3); diff --git a/FModel/Views/Resources/Controls/ContextMenus/FileContextMenu.xaml b/FModel/Views/Resources/Controls/ContextMenus/FileContextMenu.xaml index 59785879..d8a7ea31 100644 --- a/FModel/Views/Resources/Controls/ContextMenus/FileContextMenu.xaml +++ b/FModel/Views/Resources/Controls/ContextMenus/FileContextMenu.xaml @@ -157,9 +157,10 @@ - + + diff --git a/FModel/Views/Resources/Converters/AnyItemMeetsConditionConverter.cs b/FModel/Views/Resources/Converters/AnyItemMeetsConditionConverter.cs index eaf2de3f..c98977e9 100644 --- a/FModel/Views/Resources/Converters/AnyItemMeetsConditionConverter.cs +++ b/FModel/Views/Resources/Converters/AnyItemMeetsConditionConverter.cs @@ -14,18 +14,36 @@ public class AnyItemMeetsConditionConverter : IValueConverter { public Collection Conditions { get; } = []; + /// + /// Determines how multiple conditions are evaluated. Default is 'And'. + /// + public EConditionMode ConditionMode { get; set; } = EConditionMode.And; + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is not IEnumerable items || Conditions.Count == 0) return false; - return items.OfType().Any(item => Conditions.All(c => c.Matches(item))); + Func predicate = ConditionMode switch + { + EConditionMode.And => item => Conditions.All(condition => condition.Matches(item)), + EConditionMode.Or => item => Conditions.Any(condition => condition.Matches(item)), + _ => throw new ArgumentOutOfRangeException() + }; + + return items.OfType().Any(predicate); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } + + public enum EConditionMode + { + And, + Or + } } public interface IItemCondition @@ -39,7 +57,16 @@ public class ItemCategoryCondition : IItemCondition public bool Matches(GameFileViewModel item) { - return item != null && item.AssetCategory.IsOfCategory(Category); + if (item == null) return false; + + // if the specified category is a base category, check if the item's category is derived from it + if (Category.IsBaseCategory()) + { + return item.AssetCategory.IsOfCategory(Category); + } + + // if the specified category is a targeted non-base category, check for exact match + return item.AssetCategory == Category; } } diff --git a/FModel/Views/Snooper/Renderer.cs b/FModel/Views/Snooper/Renderer.cs index 0a3a6795..ab9a47b4 100644 --- a/FModel/Views/Snooper/Renderer.cs +++ b/FModel/Views/Snooper/Renderer.cs @@ -598,7 +598,7 @@ public class Renderer : IDisposable if (bSpline && model is SplineModel splineModel) splineModel.AddComponent((USplineMeshComponent)staticMeshComp); } - else if (m.TryConvert(out var mesh)) + else if (m.TryConvert(out var mesh, UserSettings.Default.NaniteMeshExportFormat)) { model = bSpline ? new SplineModel(m, mesh, (USplineMeshComponent)staticMeshComp, transform) : new StaticModel(m, mesh, transform); model.IsTwoSided = actor.GetOrDefault("bMirrored", staticMeshComp.GetOrDefault("bDisallowMeshPaintPerInstance", model.IsTwoSided));