diff --git a/CUE4Parse b/CUE4Parse index a098f0b6..7afcbb32 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit a098f0b6f87372e95d42701216159961eb691948 +Subproject commit 7afcbb323c9fd9445d5452856b18b1d732d2dccd diff --git a/FModel/App.xaml b/FModel/App.xaml index 603f409d..55a4af7d 100644 --- a/FModel/App.xaml +++ b/FModel/App.xaml @@ -20,7 +20,7 @@ - + #206BD4 diff --git a/FModel/App.xaml.cs b/FModel/App.xaml.cs index 36a1b8c1..fb282ea6 100644 --- a/FModel/App.xaml.cs +++ b/FModel/App.xaml.cs @@ -11,7 +11,9 @@ using CUE4Parse; using FModel.Framework; using FModel.Services; using FModel.Settings; +using FModel.Views.Snooper; using Newtonsoft.Json; +using Serilog.Events; using Serilog.Sinks.SystemConsole.Themes; using MessageBox = AdonisUI.Controls.MessageBox; using MessageBoxImage = AdonisUI.Controls.MessageBoxImage; @@ -110,19 +112,29 @@ public partial class App Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Logs")); CacheManager.EnsureDirectories(); - const string template = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {Enriched}: {Message:lj}{NewLine}{Exception}"; +#if DEBUG + var filePath = Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Debug-Log-{DateTime.Now:yyyy-MM-dd}.log"); +#else + var filePath = Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.log"); +#endif + const string template1 = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {Enriched}: {Message:lj}{NewLine}{Exception}"; + const string template2 = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{ClassName}] {ObjectPath}: {Message:lj}{NewLine}{Exception}"; Log.Logger = new LoggerConfiguration() #if DEBUG .Enrich.With() .MinimumLevel.Verbose() - .WriteTo.Console(outputTemplate: template, theme: AnsiConsoleTheme.Literate) - .WriteTo.File(outputTemplate: template, - path: Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Debug-Log-{DateTime.Now:yyyy-MM-dd}.log")) #else .Enrich.With() - .WriteTo.File(outputTemplate: template, - path: Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.log")) #endif + .WriteTo.Logger(lc => lc + .Filter.ByExcluding(IsConversionLibrary) + .WriteTo.Console(outputTemplate: template1, theme: AnsiConsoleTheme.Literate) + .WriteTo.File(outputTemplate: template1, path: filePath)) + .WriteTo.Logger(lc => lc + .Filter.ByIncludingOnly(IsConversionLibrary) + .WriteTo.Console(outputTemplate: template2, theme: AnsiConsoleTheme.Literate) + .WriteTo.File(outputTemplate: template2, path: filePath)) + .MinimumLevel.Override("CUE4Parse_Conversion", LogEventLevel.Verbose).WriteTo.Sink(ImGuiSink.Instance) .CreateLogger(); CacheManager.MigrateLegacyFiles(); @@ -130,6 +142,10 @@ public partial class App Log.Information("{OS}", GetOperatingSystemProductName()); Log.Information("{RuntimeVer}", RuntimeInformation.FrameworkDescription); Log.Information("Culture {SysLang}", CultureInfo.CurrentCulture); + + static bool IsConversionLibrary(LogEvent e) => + e.Properties.TryGetValue("SourceContext", out var sc) && + sc.ToString().Contains("CUE4Parse_Conversion"); } private void AppExit(object sender, ExitEventArgs e) diff --git a/FModel/Creator/Bases/FN/BaseIcon.cs b/FModel/Creator/Bases/FN/BaseIcon.cs index edf73fce..cd57824c 100644 --- a/FModel/Creator/Bases/FN/BaseIcon.cs +++ b/FModel/Creator/Bases/FN/BaseIcon.cs @@ -56,8 +56,13 @@ public class BaseIcon : UCreator Preview = Utils.GetBitmap(otherPreview); else if (Object.TryGetValue(out UMaterialInstanceConstant materialInstancePreview, "EventCalloutImage")) Preview = Utils.GetBitmap(materialInstancePreview); - else if (Object.TryGetValue(out FStructFallback brush, "IconBrush") && brush.TryGetValue(out UTexture2D res, "ResourceObject")) + else if (Object.TryGetValue(out FStructFallback brush, "IconBrush", "BuildingSymbolNormal") && brush.TryGetValue(out UTexture2D res, "ResourceObject")) Preview = Utils.GetBitmap(res); + else if (Object.TryGetValue(out FStructFallback mission, "MissionIcons", "PopupWidgetData")) + { + if (mission.TryGetValue(out FStructFallback brushsize, "Brush_XL", "Brush_L", "Brush_M", "Brush_S", "Brush_XS", "Brush_XXS", "AvailableIcon", "UnavailableIcon") && brushsize.TryGetValue(out UTexture2D res2, "ResourceObject")) + Preview = Utils.GetBitmap(res2); + } } // text diff --git a/FModel/Creator/CreatorPackage.cs b/FModel/Creator/CreatorPackage.cs index 4369fdd4..82f81427 100644 --- a/FModel/Creator/CreatorPackage.cs +++ b/FModel/Creator/CreatorPackage.cs @@ -51,6 +51,11 @@ public class CreatorPackage : IDisposable case "CosmeticShoesItemDefinition": case "CosmeticCompanionItemDefinition": case "CosmeticCompanionReactFXItemDefinition": + case "MagpieEntitlementRewardDefinition": + case "FortDeferredItemGrantDefinition": + case "BattleLabDeviceItemDefinition": + case "PiggybackDanceItemDefinition": + case "MyTownBuildingDefinitionData": case "AthenaPickaxeItemDefinition": case "AthenaGadgetItemDefinition": case "AthenaGliderItemDefinition": @@ -64,6 +69,7 @@ public class CreatorPackage : IDisposable case "FortTokenType": case "FortAbilityKit": case "FortWorkerType": + case "FortMissionInfo": case "RewardGraphToken": case "JunoKnowledgeBundle": case "FortBannerTokenType": diff --git a/FModel/Enums.cs b/FModel/Enums.cs index dfbeeece..ff8bc027 100644 --- a/FModel/Enums.cs +++ b/FModel/Enums.cs @@ -109,6 +109,7 @@ public enum EBulkType Audio = 1 << 5, Code = 1 << 6, Raw = 1 << 7, + Worlds = 1 << 8, } public enum EAssetCategory : uint @@ -171,3 +172,9 @@ public enum EUnluacMode Decompile, Disassemble, } + +public enum EExplorerViewMode +{ + Grid, + List +} diff --git a/FModel/Extensions/LogEventExtensions.cs b/FModel/Extensions/LogEventExtensions.cs new file mode 100644 index 00000000..620d6798 --- /dev/null +++ b/FModel/Extensions/LogEventExtensions.cs @@ -0,0 +1,11 @@ +using Serilog.Events; + +namespace FModel.Extensions; + +public static class LogEventExtensions +{ + public static string GetContext(this LogEvent log, string propertyName) + { + return log.Properties.TryGetValue(propertyName, out var value) ? value.ToString().Trim('"') : string.Empty; + } +} diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj index af992309..1bfc1c33 100644 --- a/FModel/FModel.csproj +++ b/FModel/FModel.csproj @@ -121,6 +121,9 @@ + + + @@ -149,6 +152,9 @@ + + + diff --git a/FModel/Framework/ImGuiController.cs b/FModel/Framework/ImGuiController.cs index b62fde21..0768de97 100644 --- a/FModel/Framework/ImGuiController.cs +++ b/FModel/Framework/ImGuiController.cs @@ -66,18 +66,69 @@ public class ImGuiController : IDisposable var iniFileNamePtr = Marshal.StringToCoTaskMemUTF8(Path.Combine(UserSettings.Default.OutputDirectory, ".data", "imgui.ini")); io.NativePtr->IniFilename = (byte*)iniFileNamePtr; } - - // If not found, Fallback to default ImGui Font - var normalPath = @"C:\Windows\Fonts\segoeui.ttf"; - var boldPath = @"C:\Windows\Fonts\segoeuib.ttf"; - var semiBoldPath = @"C:\Windows\Fonts\seguisb.ttf"; - if (File.Exists(normalPath)) - FontNormal = io.Fonts.AddFontFromFileTTF(normalPath, 16 * DpiScale); - if (File.Exists(boldPath)) - FontBold = io.Fonts.AddFontFromFileTTF(boldPath, 16 * DpiScale); - if (File.Exists(semiBoldPath)) - FontSemiBold = io.Fonts.AddFontFromFileTTF(semiBoldPath, 16 * DpiScale); + var assembly = System.Reflection.Assembly.GetExecutingAssembly(); + var assemblyName = assembly.GetName().Name; + byte[] LoadFont(string name) + { + using var stream = assembly.GetManifestResourceStream($"{assemblyName}.Resources.{name}") + ?? throw new FileNotFoundException($"Embedded font '{name}' not found."); + using var ms = new MemoryStream(); + stream.CopyTo(ms); + return ms.ToArray(); + } + + var faSolid = LoadFont("fa-solid-900.otf"); + var faRegular = LoadFont("fa-regular-400.otf"); + var faBrands = LoadFont("fa-brands-400.otf"); + + unsafe + { + // FA5 icons live in E000–F8FF (brands/regular/solid) + var iconRanges = stackalloc ushort[] { 0xe000, 0xf8ff, 0 }; + + var cfg = ImGuiNative.ImFontConfig_ImFontConfig(); + cfg->MergeMode = 1; + cfg->PixelSnapH = 1; + cfg->GlyphMinAdvanceX = 16f; + // FontDataOwnedByAtlas = 0 because we manage the GCHandle lifetime ourselves + cfg->FontDataOwnedByAtlas = 0; + + void MergeFontAwesome(byte[] solid, byte[] regular, byte[] brands) + { + fixed (byte* pSolid = solid) + fixed (byte* pRegular = regular) + fixed (byte* pBrands = brands) + { + io.Fonts.AddFontFromMemoryTTF((IntPtr)pSolid, solid.Length, 14, (IntPtr)cfg, (IntPtr)iconRanges); + io.Fonts.AddFontFromMemoryTTF((IntPtr)pRegular, regular.Length, 14, (IntPtr)cfg, (IntPtr)iconRanges); + io.Fonts.AddFontFromMemoryTTF((IntPtr)pBrands, brands.Length, 14, (IntPtr)cfg, (IntPtr)iconRanges); + } + } + + // If not found, Fallback to default ImGui Font + var normalPath = @"C:\Windows\Fonts\segoeui.ttf"; + var boldPath = @"C:\Windows\Fonts\segoeuib.ttf"; + var semiBoldPath = @"C:\Windows\Fonts\seguisb.ttf"; + + if (File.Exists(normalPath)) + { + FontNormal = io.Fonts.AddFontFromFileTTF(normalPath, 16 * DpiScale); + MergeFontAwesome(faSolid, faRegular, faBrands); + } + if (File.Exists(boldPath)) + { + FontBold = io.Fonts.AddFontFromFileTTF(boldPath, 16 * DpiScale); + MergeFontAwesome(faSolid, faRegular, faBrands); + } + if (File.Exists(semiBoldPath)) + { + FontSemiBold = io.Fonts.AddFontFromFileTTF(semiBoldPath, 16 * DpiScale); + MergeFontAwesome(faSolid, faRegular, faBrands); + } + + ImGuiNative.ImFontConfig_destroy(cfg); + } io.Fonts.AddFontDefault(); io.Fonts.Build(); // Build font atlas diff --git a/FModel/Framework/ViewModel.cs b/FModel/Framework/ViewModel.cs index dbaf1c03..6c4eecab 100644 --- a/FModel/Framework/ViewModel.cs +++ b/FModel/Framework/ViewModel.cs @@ -19,12 +19,12 @@ public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo, IDataErro if (string.IsNullOrEmpty(propertyName)) return Error; - return _validationErrors.ContainsKey(propertyName) ? string.Join(Environment.NewLine, _validationErrors[propertyName]) : string.Empty; + return _validationErrors.TryGetValue(propertyName, out IList validationError) ? string.Join(Environment.NewLine, validationError) : string.Empty; } } [JsonIgnore] public string Error => string.Join(Environment.NewLine, GetAllErrors()); - [JsonIgnore] public bool HasErrors => _validationErrors.Any(); + [JsonIgnore] public virtual bool HasErrors => _validationErrors.Count != 0; public IEnumerable GetErrors(string propertyName) { @@ -49,8 +49,7 @@ public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo, IDataErro public void ClearValidationErrors(string propertyName) { - if (_validationErrors.ContainsKey(propertyName)) - _validationErrors.Remove(propertyName); + _validationErrors.Remove(propertyName); } public event PropertyChangedEventHandler PropertyChanged; @@ -72,4 +71,4 @@ public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo, IDataErro RaisePropertyChanged(propertyName); return true; } -} \ No newline at end of file +} diff --git a/FModel/Helper.cs b/FModel/Helper.cs index 05f1530b..62d7c6e0 100644 --- a/FModel/Helper.cs +++ b/FModel/Helper.cs @@ -39,7 +39,7 @@ public static class Helper else { var w = GetOpenedWindow(windowName); - if (windowName == "Search For Packages") w.WindowState = WindowState.Normal; + if (w.WindowState == WindowState.Minimized) w.WindowState = WindowState.Normal; w.Focus(); } } diff --git a/FModel/MainWindow.xaml b/FModel/MainWindow.xaml index 46d2ddb5..6c63f0d7 100644 --- a/FModel/MainWindow.xaml +++ b/FModel/MainWindow.xaml @@ -2,6 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:FModel" + xmlns:viewModels="clr-namespace:FModel.ViewModels" xmlns:controls="clr-namespace:FModel.Views.Resources.Controls" xmlns:inputs="clr-namespace:FModel.Views.Resources.Controls.Inputs" xmlns:converters="clr-namespace:FModel.Views.Resources.Converters" @@ -24,6 +25,28 @@ + + + + + + + + + + + + + + + + + + + @@ -588,7 +691,9 @@ Command="{Binding MenuCommand}" CommandParameter="ToolBox_Open_Output_Directory" Focusable="False"> - + + + @@ -698,23 +803,102 @@ - + - - - - - - + + - - + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index fbed15d4..25e82893 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -54,8 +54,10 @@ public partial class MainWindow InitializeComponent(); AssetsExplorer.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged; + AssetsListExplorer.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged; AssetsListName.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged; AssetsExplorer.SelectionChanged += (_, e) => SyncSelection(AssetsListName, e); + AssetsListExplorer.SelectionChanged += (_, e) => SyncSelection(AssetsListName, e); AssetsListName.SelectionChanged += (_, e) => SyncSelection(AssetsExplorer, e); FLogger.Logger = LogRtbName; @@ -376,4 +378,15 @@ public partial class MainWindow childFolder.IsExpanded = true; childFolder.IsSelected = true; } + + private CustomPopupPlacement[] OnQueueToastCustomPopupPlacement(Size popupSize, Size targetSize, Point offset) + { + return + [ + new CustomPopupPlacement( + new Point((targetSize.Width - popupSize.Width) / 2, -popupSize.Height - 10), + PopupPrimaryAxis.Horizontal + ) + ]; + } } diff --git a/FModel/Resources/fa-brands-400.otf b/FModel/Resources/fa-brands-400.otf new file mode 100644 index 00000000..9e013323 Binary files /dev/null and b/FModel/Resources/fa-brands-400.otf differ diff --git a/FModel/Resources/fa-regular-400.otf b/FModel/Resources/fa-regular-400.otf new file mode 100644 index 00000000..7cf3bee7 Binary files /dev/null and b/FModel/Resources/fa-regular-400.otf differ diff --git a/FModel/Resources/fa-solid-900.otf b/FModel/Resources/fa-solid-900.otf new file mode 100644 index 00000000..b00559cc Binary files /dev/null and b/FModel/Resources/fa-solid-900.otf differ diff --git a/FModel/Settings/UserSettings.cs b/FModel/Settings/UserSettings.cs index 0f48e7e8..f937b6d2 100644 --- a/FModel/Settings/UserSettings.cs +++ b/FModel/Settings/UserSettings.cs @@ -4,14 +4,10 @@ using System.IO; using System.Windows; using System.Windows.Input; using CUE4Parse.UE4.Assets.Exports.Material; -using CUE4Parse.UE4.Assets.Exports.Nanite; -using CUE4Parse.UE4.Lua.unluac; using CUE4Parse.UE4.Versions; -using CUE4Parse_Conversion; -using CUE4Parse_Conversion.Animations; -using CUE4Parse_Conversion.Meshes; -using CUE4Parse_Conversion.Textures; -using CUE4Parse_Conversion.UEFormat.Enums; +using CUE4Parse_Conversion.Options; +using CUE4Parse_Conversion.Writers.UEFormat.Enums; +using CUE4Parse.UE4.Lua.unluac; using FModel.Extensions.Themes; using FModel.Framework; using FModel.ViewModels; @@ -19,569 +15,609 @@ using FModel.ViewModels.ApiEndpoints.Models; using FModel.Views.Snooper; using Newtonsoft.Json; -namespace FModel.Settings +namespace FModel.Settings; + +public sealed class UserSettings : ViewModel { - public sealed class UserSettings : ViewModel - { - public static UserSettings Default { get; set; } + public static UserSettings Default { get; set; } #if DEBUG - public static readonly string FilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FModel", "AppSettings_Debug.json"); + public static readonly string FilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FModel", "AppSettings_Debug.json"); #else - public static readonly string FilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FModel", "AppSettings.json"); + public static readonly string FilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FModel", "AppSettings.json"); #endif - static UserSettings() - { - Default = new UserSettings(); - } - - private static bool _bSave = true; - public static void Save() - { - if (!_bSave || Default == null) return; - Default.PerDirectory[Default.CurrentDir.GameDirectory] = Default.CurrentDir; - File.WriteAllText(FilePath, JsonConvert.SerializeObject(Default, Formatting.Indented)); - } - - public static void Delete() - { - if (File.Exists(FilePath)) - { - _bSave = false; - File.Delete(FilePath); - } - } - - public static bool IsEndpointValid(EEndpointType type, out EndpointSettings endpoint) - { - endpoint = Default.CurrentDir.Endpoints[(int) type]; - return endpoint.Overwrite || endpoint.IsValid; - } - - [JsonIgnore] - public ExporterOptions ExportOptions => new() - { - LodFormat = Default.LodExportFormat, - MeshFormat = Default.MeshExportFormat, - NaniteMeshFormat = Default.NaniteMeshExportFormat, - AnimFormat = Default.MeshExportFormat switch - { - EMeshFormat.UEFormat => EAnimFormat.UEFormat, - _ => EAnimFormat.ActorX - }, - MaterialFormat = Default.MaterialExportFormat, - TextureFormat = Default.TextureExportFormat, - SocketFormat = Default.SocketExportFormat, - CompressionFormat = Default.CompressionFormat, - Platform = Default.CurrentDir.TexturePlatform, - ExportMorphTargets = Default.SaveMorphTargets, - ExportMaterials = Default.SaveEmbeddedMaterials, - ExportHdrTexturesAsHdr = Default.SaveHdrTexturesAsHdr - }; - - private bool _showChangelog = true; - public bool ShowChangelog - { - get => _showChangelog; - set => SetProperty(ref _showChangelog, value); - } - - private string _outputDirectory; - public string OutputDirectory - { - get => _outputDirectory; - set => SetProperty(ref _outputDirectory, value); - } - - private string _rawDataDirectory; - public string RawDataDirectory - { - get => _rawDataDirectory; - set => SetProperty(ref _rawDataDirectory, value); - } - - private string _propertiesDirectory; - public string PropertiesDirectory - { - get => _propertiesDirectory; - set => SetProperty(ref _propertiesDirectory, value); - } - - private string _textureDirectory; - public string TextureDirectory - { - get => _textureDirectory; - set => SetProperty(ref _textureDirectory, value); - } - - private string _audioDirectory; - public string AudioDirectory - { - get => _audioDirectory; - set => SetProperty(ref _audioDirectory, value); - } - - private string _codeDirectory; - public string CodeDirectory - { - get => _codeDirectory; - set => SetProperty(ref _codeDirectory, value); - } - - private string _modelDirectory; - public string ModelDirectory - { - get => _modelDirectory; - set => SetProperty(ref _modelDirectory, value); - } - - private string _gameDirectory = string.Empty; - public string GameDirectory - { - get => _gameDirectory; - set => SetProperty(ref _gameDirectory, value); - } - - private int _lastOpenedSettingTab; - public int LastOpenedSettingTab - { - get => _lastOpenedSettingTab; - set => SetProperty(ref _lastOpenedSettingTab, value); - } - - private bool _isLoggerExpanded = true; - public bool IsLoggerExpanded - { - get => _isLoggerExpanded; - set => SetProperty(ref _isLoggerExpanded, value); - } - - private GridLength _avalonImageSize = new (200); - public GridLength AvalonImageSize - { - get => _avalonImageSize; - set => SetProperty(ref _avalonImageSize, value); - } - - private string _audioDeviceId; - public string AudioDeviceId - { - get => _audioDeviceId; - set => SetProperty(ref _audioDeviceId, value); - } - - private float _audioPlayerVolume = 50.0F; - public float AudioPlayerVolume - { - get => _audioPlayerVolume; - set => SetProperty(ref _audioPlayerVolume, value); - } - - private ELoadingMode _loadingMode = ELoadingMode.All; - public ELoadingMode LoadingMode - { - get => _loadingMode; - set => SetProperty(ref _loadingMode, value); - } - - private DateTime _lastUpdateCheck = DateTime.MinValue; - public DateTime LastUpdateCheck - { - get => _lastUpdateCheck; - set => SetProperty(ref _lastUpdateCheck, value); - } - - private DateTime _nextUpdateCheck = DateTime.Now; - public DateTime NextUpdateCheck - { - get => _nextUpdateCheck; - set => SetProperty(ref _nextUpdateCheck, value); - } - - private bool _keepDirectoryStructure = true; - public bool KeepDirectoryStructure - { - get => _keepDirectoryStructure; - set => SetProperty(ref _keepDirectoryStructure, value); - } - - private bool _showDecompileOption = false; - public bool ShowDecompileOption - { - get => _showDecompileOption; - set => SetProperty(ref _showDecompileOption, value); - } - - private ECompressedAudio _compressedAudioMode = ECompressedAudio.PlayDecompressed; - public ECompressedAudio CompressedAudioMode - { - get => _compressedAudioMode; - set => SetProperty(ref _compressedAudioMode, value); - } - - private EAesReload _aesReload = EAesReload.OncePerDay; - public EAesReload AesReload - { - get => _aesReload; - set => SetProperty(ref _aesReload, value); - } - - private EDiscordRpc _discordRpc = EDiscordRpc.Always; - public EDiscordRpc DiscordRpc - { - get => _discordRpc; - set => SetProperty(ref _discordRpc, value); - } - - private ELanguage _assetLanguage = ELanguage.English; - public ELanguage AssetLanguage - { - get => _assetLanguage; - set => SetProperty(ref _assetLanguage, value); - } - - private EIconStyle _cosmeticStyle = EIconStyle.Default; - public EIconStyle CosmeticStyle - { - get => _cosmeticStyle; - set => SetProperty(ref _cosmeticStyle, value); - } - - private bool _cosmeticDisplayAsset; - public bool CosmeticDisplayAsset - { - get => _cosmeticDisplayAsset; - set => SetProperty(ref _cosmeticDisplayAsset, value); - } - - private int _imageMergerMargin = 5; - public int ImageMergerMargin - { - get => _imageMergerMargin; - set => SetProperty(ref _imageMergerMargin, value); - } - - private bool _readScriptData; - public bool ReadScriptData - { - get => _readScriptData; - set => SetProperty(ref _readScriptData, value); - } - - private bool _readShaderMaps; - public bool ReadShaderMaps - { - get => _readShaderMaps; - set => SetProperty(ref _readShaderMaps, value); - } - - private bool _convertAudioOnBulkExport; - public bool ConvertAudioOnBulkExport - { - get => _convertAudioOnBulkExport; - set => SetProperty(ref _convertAudioOnBulkExport, value); - } - - private bool _decompileLua; - public bool DecompileLua - { - get => _decompileLua; - set => SetProperty(ref _decompileLua, value); - } - - [JsonIgnore] - public EUnluacMode UnluacMode - { - get => UnluacFlags.HasFlag(EUnluacFlags.Disassemble) ? EUnluacMode.Disassemble : EUnluacMode.Decompile; - set - { - var withoutMode = UnluacFlags & ~(EUnluacFlags.Decompile | EUnluacFlags.Disassemble); - var modeFlag = value == EUnluacMode.Disassemble ? EUnluacFlags.Disassemble : EUnluacFlags.Decompile; - UnluacFlags = withoutMode | modeFlag; - } - } - - private EUnluacFlags _unluacFlags = EUnluacFlags.Decompile; - public EUnluacFlags UnluacFlags - { - get => _unluacFlags; - set - { - if (!SetProperty(ref _unluacFlags, value)) return; - RaisePropertyChanged(nameof(UnluacMode)); - } - } - - private EJsonHighlightTheme _jsonHighlightTheme; - public EJsonHighlightTheme JsonHighlightTheme - { - get => _jsonHighlightTheme; - set => SetProperty(ref _jsonHighlightTheme, value); - } - - private IDictionary _perDirectory = new Dictionary(); - public IDictionary PerDirectory - { - get => _perDirectory; - set => SetProperty(ref _perDirectory, value); - } - - [JsonIgnore] - public DirectorySettings CurrentDir { get; set; } - - /// - /// TO DELETEEEEEEEEEEEEE - /// - private IDictionary _manualGames = new Dictionary(); - public IDictionary ManualGames - { - get => _manualGames; - set => SetProperty(ref _manualGames, value); - } - - private AuthResponse _lastAuthResponse = new() {AccessToken = "", ExpiresAt = DateTime.Now}; - public AuthResponse LastAuthResponse - { - get => _lastAuthResponse; - set => SetProperty(ref _lastAuthResponse, value); - } - - private Hotkey _dirLeftTab = new(Key.A); - public Hotkey DirLeftTab - { - get => _dirLeftTab; - set => SetProperty(ref _dirLeftTab, value); - } - - private Hotkey _dirRightTab = new(Key.D); - public Hotkey DirRightTab - { - get => _dirRightTab; - set => SetProperty(ref _dirRightTab, value); - } - - private Hotkey _switchAssetExplorer = new(Key.Z); - public Hotkey SwitchAssetExplorer - { - get => _switchAssetExplorer; - set => SetProperty(ref _switchAssetExplorer, value); - } - - private Hotkey _assetLeftTab = new(Key.Q); - public Hotkey AssetLeftTab - { - get => _assetLeftTab; - set => SetProperty(ref _assetLeftTab, value); - } - - private Hotkey _assetRightTab = new(Key.E); - public Hotkey AssetRightTab - { - get => _assetRightTab; - set => SetProperty(ref _assetRightTab, value); - } - - private Hotkey _assetAddTab = new(Key.T, ModifierKeys.Control); - public Hotkey AssetAddTab - { - get => _assetAddTab; - set => SetProperty(ref _assetAddTab, value); - } - - private Hotkey _assetRemoveTab = new(Key.W, ModifierKeys.Control); - public Hotkey AssetRemoveTab - { - get => _assetRemoveTab; - set => SetProperty(ref _assetRemoveTab, value); - } - - private Hotkey _addAudio = new(Key.N, ModifierKeys.Control); - public Hotkey AddAudio - { - get => _addAudio; - set => SetProperty(ref _addAudio, value); - } - - private Hotkey _playPauseAudio = new(Key.K); - public Hotkey PlayPauseAudio - { - get => _playPauseAudio; - set => SetProperty(ref _playPauseAudio, value); - } - - private Hotkey _previousAudio = new(Key.J); - public Hotkey PreviousAudio - { - get => _previousAudio; - set => SetProperty(ref _previousAudio, value); - } - - private Hotkey _nextAudio = new(Key.L); - public Hotkey NextAudio - { - get => _nextAudio; - set => SetProperty(ref _nextAudio, value); - } - - private EMeshFormat _meshExportFormat = EMeshFormat.UEFormat; - public EMeshFormat MeshExportFormat - { - get => _meshExportFormat; - set => SetProperty(ref _meshExportFormat, value); - } - - private ENaniteMeshFormat _naniteMeshExportFormat = ENaniteMeshFormat.OnlyNaniteLOD; - public ENaniteMeshFormat NaniteMeshExportFormat - { - get => _naniteMeshExportFormat; - set => SetProperty(ref _naniteMeshExportFormat, value); - } - - private EMaterialFormat _materialExportFormat = EMaterialFormat.FirstLayer; - public EMaterialFormat MaterialExportFormat - { - get => _materialExportFormat; - set => SetProperty(ref _materialExportFormat, value); - } - - private ETextureFormat _textureExportFormat = ETextureFormat.Png; - public ETextureFormat TextureExportFormat - { - get => _textureExportFormat; - set => SetProperty(ref _textureExportFormat, value); - } - - private ESocketFormat _socketExportFormat = ESocketFormat.Bone; - public ESocketFormat SocketExportFormat - { - get => _socketExportFormat; - set => SetProperty(ref _socketExportFormat, value); - } - - private EFileCompressionFormat _compressionFormat = EFileCompressionFormat.ZSTD; - public EFileCompressionFormat CompressionFormat - { - get => _compressionFormat; - set => SetProperty(ref _compressionFormat, value); - } - - private ELodFormat _lodExportFormat = ELodFormat.FirstLod; - public ELodFormat LodExportFormat - { - get => _lodExportFormat; - set => SetProperty(ref _lodExportFormat, value); - } - - private bool _showSkybox = true; - public bool ShowSkybox - { - get => _showSkybox; - set => SetProperty(ref _showSkybox, value); - } - - private bool _showGrid = true; - public bool ShowGrid - { - get => _showGrid; - set => SetProperty(ref _showGrid, value); - } - - private bool _animateWithRotationOnly; - public bool AnimateWithRotationOnly - { - get => _animateWithRotationOnly; - set => SetProperty(ref _animateWithRotationOnly, value); - } - - private Camera.WorldMode _cameraMode = Camera.WorldMode.Arcball; - public Camera.WorldMode CameraMode - { - get => _cameraMode; - set => SetProperty(ref _cameraMode, value); - } - - private int _previewMaxTextureSize = 1024; - public int PreviewMaxTextureSize - { - get => _previewMaxTextureSize; - set => SetProperty(ref _previewMaxTextureSize, value); - } - - private bool _previewStaticMeshes = true; - public bool PreviewStaticMeshes - { - get => _previewStaticMeshes; - set => SetProperty(ref _previewStaticMeshes, value); - } - - private bool _previewSkeletalMeshes = true; - public bool PreviewSkeletalMeshes - { - get => _previewSkeletalMeshes; - set => SetProperty(ref _previewSkeletalMeshes, value); - } - - private bool _previewAnimations = true; - public bool PreviewAnimations - { - get => _previewAnimations; - set => SetProperty(ref _previewAnimations, value); - } - - private bool _previewMaterials = true; - public bool PreviewMaterials - { - get => _previewMaterials; - set => SetProperty(ref _previewMaterials, value); - } - - private bool _previewWorlds = true; - public bool PreviewWorlds - { - get => _previewWorlds; - set => SetProperty(ref _previewWorlds, value); - } - - private bool _saveMorphTargets = true; - public bool SaveMorphTargets - { - get => _saveMorphTargets; - set => SetProperty(ref _saveMorphTargets, value); - } - - private bool _saveEmbeddedMaterials = true; - public bool SaveEmbeddedMaterials - { - get => _saveEmbeddedMaterials; - set => SetProperty(ref _saveEmbeddedMaterials, value); - } - - private bool _saveSkeletonAsMesh; - public bool SaveSkeletonAsMesh - { - get => _saveSkeletonAsMesh; - set => SetProperty(ref _saveSkeletonAsMesh, value); - } - - private bool _saveHdrTexturesAsHdr = true; - public bool SaveHdrTexturesAsHdr - { - get => _saveHdrTexturesAsHdr; - set => SetProperty(ref _saveHdrTexturesAsHdr, value); - } - - private bool _featurePreviewNewAssetExplorer = true; - public bool FeaturePreviewNewAssetExplorer - { - get => _featurePreviewNewAssetExplorer; - set => SetProperty(ref _featurePreviewNewAssetExplorer, value); - } - - private bool _previewTexturesAssetExplorer = true; - public bool PreviewTexturesAssetExplorer - { - get => _previewTexturesAssetExplorer; - set => SetProperty(ref _previewTexturesAssetExplorer, value); + static UserSettings() + { + Default = new UserSettings(); + } + + private static bool _bSave = true; + public static void Save() + { + if (!_bSave || Default == null) return; + Default.PerDirectory[Default.CurrentDir.GameDirectory] = Default.CurrentDir; + File.WriteAllText(FilePath, JsonConvert.SerializeObject(Default, Formatting.Indented)); + } + + public static void Delete() + { + if (File.Exists(FilePath)) + { + _bSave = false; + File.Delete(FilePath); } } + + public static bool IsEndpointValid(EEndpointType type, out EndpointSettings endpoint) + { + endpoint = Default.CurrentDir.Endpoints[(int) type]; + return endpoint.Overwrite || endpoint.IsValid; + } + + public static ExportOptions GetExportOptions() + { + return new ExportOptions( + Default.MeshExportFormat, + Default.NaniteMeshExportFormat, + Default.MeshQuality, + Default.CurrentDir.TexturePlatform, + Default.TextureExportFormat, + Default.TextureQuality, + Default.SaveHdrTexturesAsHdr, + Default.MaterialExportFormat, + Default.SaveEmbeddedMaterials, + Default.SaveMorphTargets, + Default.SocketExportFormat, + Default.CompressionFormat, + Default.ExportAllTextureMips + ); + } + + private bool _showChangelog = true; + public bool ShowChangelog + { + get => _showChangelog; + set => SetProperty(ref _showChangelog, value); + } + + private string _outputDirectory; + public string OutputDirectory + { + get => _outputDirectory; + set => SetProperty(ref _outputDirectory, value); + } + + private string _rawDataDirectory; + public string RawDataDirectory + { + get => _rawDataDirectory; + set => SetProperty(ref _rawDataDirectory, value); + } + + private string _propertiesDirectory; + public string PropertiesDirectory + { + get => _propertiesDirectory; + set => SetProperty(ref _propertiesDirectory, value); + } + + private string _textureDirectory; + public string TextureDirectory + { + get => _textureDirectory; + set => SetProperty(ref _textureDirectory, value); + } + + private string _audioDirectory; + public string AudioDirectory + { + get => _audioDirectory; + set => SetProperty(ref _audioDirectory, value); + } + + private string _codeDirectory; + public string CodeDirectory + { + get => _codeDirectory; + set => SetProperty(ref _codeDirectory, value); + } + + private string _modelDirectory; + public string ModelDirectory + { + get => _modelDirectory; + set => SetProperty(ref _modelDirectory, value); + } + + private string _gameDirectory = string.Empty; + public string GameDirectory + { + get => _gameDirectory; + set => SetProperty(ref _gameDirectory, value); + } + + private int _lastOpenedSettingTab; + public int LastOpenedSettingTab + { + get => _lastOpenedSettingTab; + set => SetProperty(ref _lastOpenedSettingTab, value); + } + + private bool _isLoggerExpanded = true; + public bool IsLoggerExpanded + { + get => _isLoggerExpanded; + set => SetProperty(ref _isLoggerExpanded, value); + } + + private GridLength _avalonImageSize = new (200); + public GridLength AvalonImageSize + { + get => _avalonImageSize; + set => SetProperty(ref _avalonImageSize, value); + } + + private string _audioDeviceId; + public string AudioDeviceId + { + get => _audioDeviceId; + set => SetProperty(ref _audioDeviceId, value); + } + + private float _audioPlayerVolume = 50.0F; + public float AudioPlayerVolume + { + get => _audioPlayerVolume; + set => SetProperty(ref _audioPlayerVolume, value); + } + + private ELoadingMode _loadingMode = ELoadingMode.All; + public ELoadingMode LoadingMode + { + get => _loadingMode; + set => SetProperty(ref _loadingMode, value); + } + + private DateTime _lastUpdateCheck = DateTime.MinValue; + public DateTime LastUpdateCheck + { + get => _lastUpdateCheck; + set => SetProperty(ref _lastUpdateCheck, value); + } + + private DateTime _nextUpdateCheck = DateTime.Now; + public DateTime NextUpdateCheck + { + get => _nextUpdateCheck; + set => SetProperty(ref _nextUpdateCheck, value); + } + + private bool _keepDirectoryStructure = true; + public bool KeepDirectoryStructure + { + get => _keepDirectoryStructure; + set => SetProperty(ref _keepDirectoryStructure, value); + } + + private bool _showDecompileOption = false; + public bool ShowDecompileOption + { + get => _showDecompileOption; + set => SetProperty(ref _showDecompileOption, value); + } + + private ECompressedAudio _compressedAudioMode = ECompressedAudio.PlayDecompressed; + public ECompressedAudio CompressedAudioMode + { + get => _compressedAudioMode; + set => SetProperty(ref _compressedAudioMode, value); + } + + private EAesReload _aesReload = EAesReload.OncePerDay; + public EAesReload AesReload + { + get => _aesReload; + set => SetProperty(ref _aesReload, value); + } + + private EDiscordRpc _discordRpc = EDiscordRpc.Always; + public EDiscordRpc DiscordRpc + { + get => _discordRpc; + set => SetProperty(ref _discordRpc, value); + } + + private ELanguage _assetLanguage = ELanguage.English; + public ELanguage AssetLanguage + { + get => _assetLanguage; + set => SetProperty(ref _assetLanguage, value); + } + + private EIconStyle _cosmeticStyle = EIconStyle.Default; + public EIconStyle CosmeticStyle + { + get => _cosmeticStyle; + set => SetProperty(ref _cosmeticStyle, value); + } + + private bool _cosmeticDisplayAsset; + public bool CosmeticDisplayAsset + { + get => _cosmeticDisplayAsset; + set => SetProperty(ref _cosmeticDisplayAsset, value); + } + + private int _imageMergerMargin = 5; + public int ImageMergerMargin + { + get => _imageMergerMargin; + set => SetProperty(ref _imageMergerMargin, value); + } + + private bool _readScriptData; + public bool ReadScriptData + { + get => _readScriptData; + set => SetProperty(ref _readScriptData, value); + } + + private bool _readShaderMaps; + public bool ReadShaderMaps + { + get => _readShaderMaps; + set => SetProperty(ref _readShaderMaps, value); + } + + private bool _convertAudioOnBulkExport; + public bool ConvertAudioOnBulkExport + { + get => _convertAudioOnBulkExport; + set => SetProperty(ref _convertAudioOnBulkExport, value); + } + + private bool _mergeEditorOnlyDataExports = false; + public bool MergeEditorOnlyDataExports + { + get => _mergeEditorOnlyDataExports; + set => SetProperty(ref _mergeEditorOnlyDataExports, value); + } + + private bool _decompileLua; + public bool DecompileLua + { + get => _decompileLua; + set => SetProperty(ref _decompileLua, value); + } + + [JsonIgnore] + public EUnluacMode UnluacMode + { + get => UnluacFlags.HasFlag(EUnluacFlags.Disassemble) ? EUnluacMode.Disassemble : EUnluacMode.Decompile; + set + { + var withoutMode = UnluacFlags & ~(EUnluacFlags.Decompile | EUnluacFlags.Disassemble); + var modeFlag = value == EUnluacMode.Disassemble ? EUnluacFlags.Disassemble : EUnluacFlags.Decompile; + UnluacFlags = withoutMode | modeFlag; + } + } + + private EUnluacFlags _unluacFlags = EUnluacFlags.Decompile; + public EUnluacFlags UnluacFlags + { + get => _unluacFlags; + set + { + if (!SetProperty(ref _unluacFlags, value)) return; + RaisePropertyChanged(nameof(UnluacMode)); + } + } + + private EJsonHighlightTheme _jsonHighlightTheme; + public EJsonHighlightTheme JsonHighlightTheme + { + get => _jsonHighlightTheme; + set => SetProperty(ref _jsonHighlightTheme, value); + } + + private IDictionary _perDirectory = new Dictionary(); + public IDictionary PerDirectory + { + get => _perDirectory; + set => SetProperty(ref _perDirectory, value); + } + + [JsonIgnore] + public DirectorySettings CurrentDir { get; set; } + + /// + /// TO DELETEEEEEEEEEEEEE + /// + private IDictionary _manualGames = new Dictionary(); + public IDictionary ManualGames + { + get => _manualGames; + set => SetProperty(ref _manualGames, value); + } + + private AuthResponse _lastAuthResponse = new() {AccessToken = "", ExpiresAt = DateTime.Now}; + public AuthResponse LastAuthResponse + { + get => _lastAuthResponse; + set => SetProperty(ref _lastAuthResponse, value); + } + + private Hotkey _dirLeftTab = new(Key.A); + public Hotkey DirLeftTab + { + get => _dirLeftTab; + set => SetProperty(ref _dirLeftTab, value); + } + + private Hotkey _dirRightTab = new(Key.D); + public Hotkey DirRightTab + { + get => _dirRightTab; + set => SetProperty(ref _dirRightTab, value); + } + + private Hotkey _switchAssetExplorer = new(Key.Z); + public Hotkey SwitchAssetExplorer + { + get => _switchAssetExplorer; + set => SetProperty(ref _switchAssetExplorer, value); + } + + private Hotkey _assetLeftTab = new(Key.Q); + public Hotkey AssetLeftTab + { + get => _assetLeftTab; + set => SetProperty(ref _assetLeftTab, value); + } + + private Hotkey _assetRightTab = new(Key.E); + public Hotkey AssetRightTab + { + get => _assetRightTab; + set => SetProperty(ref _assetRightTab, value); + } + + private Hotkey _assetAddTab = new(Key.T, ModifierKeys.Control); + public Hotkey AssetAddTab + { + get => _assetAddTab; + set => SetProperty(ref _assetAddTab, value); + } + + private Hotkey _assetRemoveTab = new(Key.W, ModifierKeys.Control); + public Hotkey AssetRemoveTab + { + get => _assetRemoveTab; + set => SetProperty(ref _assetRemoveTab, value); + } + + private Hotkey _addAudio = new(Key.N, ModifierKeys.Control); + public Hotkey AddAudio + { + get => _addAudio; + set => SetProperty(ref _addAudio, value); + } + + private Hotkey _removeAudio = new(Key.X); + public Hotkey RemoveAudio + { + get => _removeAudio; + set => SetProperty(ref _removeAudio, value); + } + + private Hotkey _playPauseAudio = new(Key.K); + public Hotkey PlayPauseAudio + { + get => _playPauseAudio; + set => SetProperty(ref _playPauseAudio, value); + } + + private Hotkey _previousAudio = new(Key.J); + public Hotkey PreviousAudio + { + get => _previousAudio; + set => SetProperty(ref _previousAudio, value); + } + + private Hotkey _nextAudio = new(Key.L); + public Hotkey NextAudio + { + get => _nextAudio; + set => SetProperty(ref _nextAudio, value); + } + + private EMeshFormat _meshExportFormat = EMeshFormat.UEFormat; + public EMeshFormat MeshExportFormat + { + get => _meshExportFormat; + set => SetProperty(ref _meshExportFormat, value); + } + + private ENaniteMeshFormat _naniteMeshExportFormat = ENaniteMeshFormat.NaniteOnly; + public ENaniteMeshFormat NaniteMeshExportFormat + { + get => _naniteMeshExportFormat; + set => SetProperty(ref _naniteMeshExportFormat, value); + } + + private EMeshQuality _meshQuality = EMeshQuality.Highest; + public EMeshQuality MeshQuality + { + get => _meshQuality; + set => SetProperty(ref _meshQuality, value); + } + + private EMaterialDepth _materialExportFormat = EMaterialDepth.TopLayerOnly; + public EMaterialDepth MaterialExportFormat + { + get => _materialExportFormat; + set => SetProperty(ref _materialExportFormat, value); + } + + private ETextureFormat _textureExportFormat = ETextureFormat.Png; + public ETextureFormat TextureExportFormat + { + get => _textureExportFormat; + set => SetProperty(ref _textureExportFormat, value); + } + + private int _textureQuality = 100; + public int TextureQuality + { + get => _textureQuality; + set => SetProperty(ref _textureQuality, value); + } + + private ESocketFormat _socketExportFormat = ESocketFormat.Bone; + public ESocketFormat SocketExportFormat + { + get => _socketExportFormat; + set => SetProperty(ref _socketExportFormat, value); + } + + private EFileCompressionFormat _compressionFormat = EFileCompressionFormat.ZSTD; + public EFileCompressionFormat CompressionFormat + { + get => _compressionFormat; + set => SetProperty(ref _compressionFormat, value); + } + + private bool _showSkybox = true; + public bool ShowSkybox + { + get => _showSkybox; + set => SetProperty(ref _showSkybox, value); + } + + private bool _showGrid = true; + public bool ShowGrid + { + get => _showGrid; + set => SetProperty(ref _showGrid, value); + } + + private bool _animateWithRotationOnly; + public bool AnimateWithRotationOnly + { + get => _animateWithRotationOnly; + set => SetProperty(ref _animateWithRotationOnly, value); + } + + private Camera.WorldMode _cameraMode = Camera.WorldMode.Arcball; + public Camera.WorldMode CameraMode + { + get => _cameraMode; + set => SetProperty(ref _cameraMode, value); + } + + private int _previewMaxTextureSize = 1024; + public int PreviewMaxTextureSize + { + get => _previewMaxTextureSize; + set => SetProperty(ref _previewMaxTextureSize, value); + } + + private bool _previewStaticMeshes = true; + public bool PreviewStaticMeshes + { + get => _previewStaticMeshes; + set => SetProperty(ref _previewStaticMeshes, value); + } + + private bool _previewSkeletalMeshes = true; + public bool PreviewSkeletalMeshes + { + get => _previewSkeletalMeshes; + set => SetProperty(ref _previewSkeletalMeshes, value); + } + + private bool _previewAnimations = true; + public bool PreviewAnimations + { + get => _previewAnimations; + set => SetProperty(ref _previewAnimations, value); + } + + private bool _previewMaterials = true; + public bool PreviewMaterials + { + get => _previewMaterials; + set => SetProperty(ref _previewMaterials, value); + } + + private bool _previewWorlds = true; + public bool PreviewWorlds + { + get => _previewWorlds; + set => SetProperty(ref _previewWorlds, value); + } + + private bool _saveMorphTargets = true; + public bool SaveMorphTargets + { + get => _saveMorphTargets; + set => SetProperty(ref _saveMorphTargets, value); + } + + private bool _saveEmbeddedMaterials = true; + public bool SaveEmbeddedMaterials + { + get => _saveEmbeddedMaterials; + set => SetProperty(ref _saveEmbeddedMaterials, value); + } + + private bool _saveSkeletonAsMesh; + public bool SaveSkeletonAsMesh + { + get => _saveSkeletonAsMesh; + set => SetProperty(ref _saveSkeletonAsMesh, value); + } + + private bool _saveHdrTexturesAsHdr = true; + public bool SaveHdrTexturesAsHdr + { + get => _saveHdrTexturesAsHdr; + set => SetProperty(ref _saveHdrTexturesAsHdr, value); + } + + private bool _featurePreviewNewAssetExplorer = true; + public bool FeaturePreviewNewAssetExplorer + { + get => _featurePreviewNewAssetExplorer; + set => SetProperty(ref _featurePreviewNewAssetExplorer, value); + } + + private bool _previewTexturesAssetExplorer = true; + public bool PreviewTexturesAssetExplorer + { + get => _previewTexturesAssetExplorer; + set => SetProperty(ref _previewTexturesAssetExplorer, value); + } + + private EExplorerViewMode _explorerViewMode = EExplorerViewMode.Grid; + + public EExplorerViewMode ExplorerViewMode + { + get => _explorerViewMode; + set => SetProperty(ref _explorerViewMode, value); + } + + private bool _exportAllTextureMips = false; + public bool ExportAllTextureMips + { + get => _exportAllTextureMips; + set => SetProperty(ref _exportAllTextureMips, value); + } + + private bool _exportImmediately; + public bool ExportImmediately + { + get => _exportImmediately; + set => SetProperty(ref _exportImmediately, value); + } } diff --git a/FModel/ViewModels/ApiEndpoints/GitHubApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/GitHubApiEndpoint.cs index 4fd50731..f9165130 100644 --- a/FModel/ViewModels/ApiEndpoints/GitHubApiEndpoint.cs +++ b/FModel/ViewModels/ApiEndpoints/GitHubApiEndpoint.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; using FModel.Framework; using FModel.ViewModels.ApiEndpoints.Models; @@ -26,7 +27,7 @@ public class GitHubApiEndpoint(RestClient client) : AbstractApiProvider(client) public async Task GetUserAsync(string username) { - var request = new FRestRequest($"https://api.github.com/users/{username}"); + var request = new FRestRequest($"https://api.github.com/users/{Uri.EscapeDataString(username)}"); var response = await _client.ExecuteAsync(request).ConfigureAwait(false); return response.Data; } diff --git a/FModel/ViewModels/AssetsFolderViewModel.cs b/FModel/ViewModels/AssetsFolderViewModel.cs index 70f6eeaf..845a930a 100644 --- a/FModel/ViewModels/AssetsFolderViewModel.cs +++ b/FModel/ViewModels/AssetsFolderViewModel.cs @@ -230,17 +230,12 @@ public class AssetsFolderViewModel return null; } - var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); foreach (var entry in entries) { TreeItem lastNode = null; TreeItem parentItem = null; - var path = entry.Path; - if (path.StartsWith(localAppData, StringComparison.OrdinalIgnoreCase)) - path = path[localAppData.Length..].TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); - - var folders = path.Split('/', StringSplitOptions.RemoveEmptyEntries); + var folders = entry.Path.Split('/', StringSplitOptions.RemoveEmptyEntries); var builder = new StringBuilder(64); var parentNode = treeItems; diff --git a/FModel/ViewModels/AudioPlayerViewModel.cs b/FModel/ViewModels/AudioPlayerViewModel.cs index 997b986a..ee9f2af9 100644 --- a/FModel/ViewModels/AudioPlayerViewModel.cs +++ b/FModel/ViewModels/AudioPlayerViewModel.cs @@ -239,6 +239,20 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable }); } + public void Unload() + { + Application.Current.Dispatcher.Invoke(() => + { + _waveSource = null; + + PlayedFile = new AudioFile(-1, "No audio file"); + Spectrum = null; + + RaiseSourceEvent(ESourceEventType.Clearing); + ClearSoundOut(); + }); + } + public void AddToPlaylist(byte[] data, string filePath) { Application.Current.Dispatcher.Invoke(() => @@ -270,11 +284,30 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable if (_audioFiles.Count < 1) return; Application.Current.Dispatcher.Invoke(() => { + var removedPlaying = false; + if (PlayedFile.Id == SelectedAudioFile.Id) + { + removedPlaying = true; + Stop(); + } + _audioFiles.RemoveAt(SelectedAudioFile.Id); for (var i = 0; i < _audioFiles.Count; i++) { _audioFiles[i].Id = i; } + + if (_audioFiles.Count < 1) + { + Unload(); + return; + } + + SelectedAudioFile = SelectedAudioFile.Id >= _audioFiles.Count ? _audioFiles.Last() : _audioFiles[SelectedAudioFile.Id]; + + if (!removedPlaying) return; + Load(); + Play(); }); } @@ -526,6 +559,11 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable _soundOut.Volume = UserSettings.Default.AudioPlayerVolume / 100; } + private void ClearSoundOut() + { + _soundOut = null; + } + private IEnumerable EnumerateDevices() { using var deviceEnumerator = new MMDeviceEnumerator(); diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 07772cc3..fd589621 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -66,7 +66,7 @@ using CUE4Parse.UE4.Shaders; using CUE4Parse.UE4.Versions; using CUE4Parse.UE4.Wwise; using CUE4Parse.Utils; -using CUE4Parse_Conversion; +using CUE4Parse_Conversion.Exporters; using CUE4Parse_Conversion.Sounds; using EpicManifestParser; using EpicManifestParser.UE; @@ -190,27 +190,26 @@ public class CUE4ParseViewModel : ViewModel } default: { - var project = gameDirectory.SubstringBeforeLast(gameDirectory.Contains("eFootball") ? "\\pak" : "\\Content").SubstringAfterLast("\\"); - Provider = project switch + Provider = versionContainer.Game switch { - "StateOfDecay2" => new DefaultFileProvider(new DirectoryInfo(gameDirectory), + EGame.GAME_StateOfDecay2 => new DefaultFileProvider(new DirectoryInfo(gameDirectory), [ new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\Paks"), new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\DisabledPaks") ], SearchOption.AllDirectories, versionContainer, pathComparer), - "eFootball" => new DefaultFileProvider(new DirectoryInfo(gameDirectory), + EGame.GAME_eFootball => new DefaultFileProvider(new DirectoryInfo(gameDirectory), [ new(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\KONAMI\\eFootball\\ST\\Download") ], SearchOption.AllDirectories, versionContainer, pathComparer), - "DeadByDaylight" => new DefaultFileProvider(new DirectoryInfo(gameDirectory), + EGame.GAME_DeadByDaylight => new DefaultFileProvider(new DirectoryInfo(gameDirectory), [ new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\DeadByDaylight\\Saved\\PersistentDownloadDir\\DynamicContent") ], SearchOption.AllDirectories, versionContainer, pathComparer), - _ when versionContainer.Game is EGame.GAME_AshEchoes => new AEDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer), - _ when versionContainer.Game is EGame.GAME_BlackStigma => new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, StringComparer.Ordinal), - _ when versionContainer.Game is EGame.GAME_HonorofKingsWorld => new HoKWDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer), - _ when versionContainer.Game is EGame.GAME_LordOfMysteries => new LoMDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer), - _ when versionContainer.Game is EGame.GAME_ArcRaiders => new TheiaFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer), + EGame.GAME_AshEchoes => new AEDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer), + EGame.GAME_BlackStigma => new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, StringComparer.Ordinal), + EGame.GAME_HonorofKingsWorld => new HoKWDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer), + EGame.GAME_LordOfMysteries => new LoMDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer), + EGame.GAME_ArcRaiders => new TheiaFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer), _ => new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer) }; @@ -221,6 +220,7 @@ public class CUE4ParseViewModel : ViewModel Provider.ReadScriptData = UserSettings.Default.ReadScriptData; Provider.ReadShaderMaps = UserSettings.Default.ReadShaderMaps; Provider.ReadNaniteData = true; + PropertyUtil.SearchPropertyInTemplate = true; // search template properties when looking for a prop via GetOrDefault and cie GameDirectory = new GameDirectoryViewModel(); AssetsFolder = new AssetsFolderViewModel(); @@ -645,7 +645,7 @@ public class CUE4ParseViewModel : ViewModel Parallel.ForEach(folder.AssetsList.Assets, entry => { cancellationToken.ThrowIfCancellationRequested(); - ExportData(entry.Asset, false); + ExportData(entry.Asset); }); foreach (var f in folder.Folders) ExportFolder(cancellationToken, f); @@ -654,27 +654,6 @@ public class CUE4ParseViewModel : ViewModel public void ExtractFolder(CancellationToken cancellationToken, TreeItem folder, EBulkType bulk) => BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, bulk)); - public void ExtractFolder(CancellationToken cancellationToken, TreeItem folder) - => BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs)); - - public void SaveFolder(CancellationToken cancellationToken, TreeItem folder) - => BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Properties | EBulkType.Auto)); - - public void TextureFolder(CancellationToken cancellationToken, TreeItem folder) - => BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Textures | EBulkType.Auto)); - - public void ModelFolder(CancellationToken cancellationToken, TreeItem folder) - => BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Meshes | EBulkType.Auto)); - - public void AnimationFolder(CancellationToken cancellationToken, TreeItem folder) - => BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Animations | EBulkType.Auto)); - - public void AudioFolder(CancellationToken cancellationToken, TreeItem folder) - => BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Audio | EBulkType.Auto)); - - public void CodeFolder(CancellationToken cancellationToken, TreeItem folder) - => BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Code | EBulkType.Auto)); - public void Extract(CancellationToken cancellationToken, GameFile entry, bool addNewTab = false, EBulkType bulk = EBulkType.None) { ApplicationService.ApplicationView.IsAssetsExplorerVisible = false; @@ -699,7 +678,34 @@ public class CUE4ParseViewModel : ViewModel if (saveProperties || updateUi) { - TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(result.GetDisplayData(saveProperties), Formatting.Indented), saveProperties, updateUi); + var displayData = result.GetDisplayData(saveProperties); + + if (UserSettings.Default.MergeEditorOnlyDataExports && Provider.TryLoadPackage(entry.Path.SubstringBefore('.') + ".o.uasset", out var editorAsset)) + { + var pkg = Provider.LoadPackage(entry.Path); + var exports = pkg.GetExports().ToArray(); + var finalExports = new List(exports); + var editorOnlyDataExports = new HashSet(); + + foreach (var export in exports) + { + var editorData = editorAsset.GetExportOrNull(export.Name + "EditorOnlyData"); + if (editorData == null) + continue; + + export.Properties.AddRange(editorData.Properties); + editorOnlyDataExports.Add(editorData); + } + + if (editorOnlyDataExports.Count > 0) + { + finalExports.AddRange(editorAsset.GetExports().Where(editorExport => !editorOnlyDataExports.Contains(editorExport))); + } + + displayData = finalExports; + } + + TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(displayData, Formatting.Indented), saveProperties, updateUi); if (saveProperties) break; // do not search for viewable exports if we are dealing with jsons } @@ -837,6 +843,7 @@ public class CUE4ParseViewModel : ViewModel } case "ebd" when Provider.Versions.Game is EGame.GAME_ArcRaiders: case "json": + case "Json": { var data = Provider.SaveAsset(entry); using var stream = new MemoryStream(data) { Position = 0 }; @@ -1133,7 +1140,7 @@ public class CUE4ParseViewModel : ViewModel } else if (entry.NameWithoutExtension.Equals("key_manifest")) { - var keymanifest = new FAion2KeyManifestFile(entry, Provider); + var keymanifest = new FAion2KeyManifestFile(entry); TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(keymanifest, Formatting.Indented), saveProperties, updateUi); } else @@ -1222,12 +1229,12 @@ public class CUE4ParseViewModel : ViewModel return decompiled; } - public void ExtractAndScroll(CancellationToken cancellationToken, string fullPath, string objectName, string parentExportType) + public void ExtractAndScroll(CancellationToken cancellationToken, string fullPath, string objectName) { Log.Information("User CTRL-CLICKED to extract '{FullPath}'", fullPath); var entry = Provider[fullPath]; - TabControl.AddTab(entry, parentExportType); + TabControl.AddTab(entry); TabControl.SelectedTab.ScrollTrigger = objectName; var result = Provider.GetLoadPackageResult(entry, objectName); @@ -1267,7 +1274,16 @@ public class CUE4ParseViewModel : ViewModel } case UTexture when (isNone || saveTextures) && pointer.Object.Value is UTexture texture: { - TabControl.SelectedTab.AddImage(texture, saveTextures, updateUi); + if (saveTextures) + { + SaveExport(texture); + } + + if (updateUi) + { + TabControl.SelectedTab.AddImage(texture, false, true); + } + return false; } case USvgAsset when (isNone || saveTextures) && pointer.Object.Value is USvgAsset svgasset: @@ -1523,13 +1539,13 @@ public class CUE4ParseViewModel : ViewModel return false; } case UWorld when isNone && UserSettings.Default.PreviewWorlds: - case UBlueprintGeneratedClass when isNone && UserSettings.Default.PreviewWorlds && TabControl.SelectedTab.ParentExportType switch - { - "JunoBuildInstructionsItemDefinition" => true, - "JunoBuildingSetAccountItemDefinition" => true, - "JunoBuildingPropAccountItemDefinition" => true, - _ => false - }: + // case UBlueprintGeneratedClass when isNone && UserSettings.Default.PreviewWorlds && TabControl.SelectedTab.ParentExportType switch + // { + // "JunoBuildInstructionsItemDefinition" => true, + // "JunoBuildingSetAccountItemDefinition" => true, + // "JunoBuildingPropAccountItemDefinition" => true, + // _ => false + // }: case UPaperSprite when isNone && UserSettings.Default.PreviewMaterials: case UStaticMesh when isNone && UserSettings.Default.PreviewStaticMeshes: case USkeletalMesh when isNone && UserSettings.Default.PreviewSkeletalMeshes: @@ -1560,10 +1576,11 @@ public class CUE4ParseViewModel : ViewModel case UStaticMesh when HasFlag(bulk, EBulkType.Meshes): case USkeletalMesh when HasFlag(bulk, EBulkType.Meshes): case USkeleton when UserSettings.Default.SaveSkeletonAsMesh && HasFlag(bulk, EBulkType.Meshes): - // case UMaterialInstance when HasFlag(bulk, EBulkType.Materials): // read the fucking json - case UAnimSequenceBase when HasFlag(bulk, EBulkType.Animations): + // case UMaterialInterface when HasFlag(bulk, EBulkType.Materials): + case UAnimationAsset when HasFlag(bulk, EBulkType.Animations): + case UWorld when HasFlag(bulk, EBulkType.Worlds): { - SaveExport(pointer.Object.Value, updateUi); + SaveExport(pointer.Object.Value); return true; } default: @@ -1720,64 +1737,27 @@ public class CUE4ParseViewModel : ViewModel }); } - private void SaveExport(UObject export, bool updateUi = true) + private void SaveExport(UObject export) { - var toSave = new Exporter(export, UserSettings.Default.ExportOptions); - var toSaveDirectory = new DirectoryInfo(UserSettings.Default.ModelDirectory); - if (toSave.TryWriteToDir(toSaveDirectory, out var label, out var savedFilePath)) + try { - Interlocked.Increment(ref ExportedCount); - Log.Information("Successfully saved {FilePath}", savedFilePath); - if (updateUi) - { - FLogger.Append(ELog.Information, () => - { - FLogger.Text("Successfully saved ", Constants.WHITE); - FLogger.Link(label, savedFilePath, true); - }); - } + ExportSessionViewModel.Instance.Session.Add(export); } - else + catch (Exception e) { - Interlocked.Increment(ref FailedExportCount); - Log.Error("{FileName} could not be saved", export.Name); - FLogger.Append(ELog.Error, () => FLogger.Text($"Could not save '{export.Name}'", Constants.WHITE, true)); + Log.Error(e, "Could not add to export session"); } } - private readonly object _rawData = new (); - public void ExportData(GameFile entry, bool updateUi = true) + public void ExportData(GameFile entry) { - if (Provider.TrySavePackage(entry, out var assets)) + try { - string path = UserSettings.Default.RawDataDirectory; - Parallel.ForEach(assets, kvp => - { - lock (_rawData) - { - path = Path.Combine(UserSettings.Default.RawDataDirectory, UserSettings.Default.KeepDirectoryStructure ? kvp.Key : kvp.Key.SubstringAfterLast('/')).Replace('\\', '/'); - Directory.CreateDirectory(path.SubstringBeforeLast('/')); - File.WriteAllBytes(path, kvp.Value); - } - }); - - Interlocked.Increment(ref ExportedCount); - Log.Information("{FileName} successfully exported", entry.Name); - if (updateUi) - { - FLogger.Append(ELog.Information, () => - { - FLogger.Text("Successfully exported ", Constants.WHITE); - FLogger.Link(entry.Name, path, true); - }); - } + ExportSessionViewModel.Instance.Session.Add(new RawDataExporter(entry, Provider)); } - else + catch (Exception e) { - Interlocked.Increment(ref FailedExportCount); - Log.Error("{FileName} could not be exported", entry.Name); - if (updateUi) - FLogger.Append(ELog.Error, () => FLogger.Text($"Could not export '{entry.Name}'", Constants.WHITE, true)); + Log.Error(e, "Could not add to export session"); } } diff --git a/FModel/ViewModels/Commands/MenuCommand.cs b/FModel/ViewModels/Commands/MenuCommand.cs index fe223f88..06e17a1a 100644 --- a/FModel/ViewModels/Commands/MenuCommand.cs +++ b/FModel/ViewModels/Commands/MenuCommand.cs @@ -40,6 +40,9 @@ public class MenuCommand : ViewModelCommand case "Views_3dViewer": contextViewModel.CUE4Parse.SnooperViewer.Run(); break; + case "Views_ExportSession": + Helper.OpenWindow("Export Session", () => new ExportSessionWindow().Show()); + break; case "Views_AudioPlayer": Helper.OpenWindow("Audio Player", () => new AudioPlayer().Show()); break; diff --git a/FModel/ViewModels/Commands/RightClickMenuCommand.cs b/FModel/ViewModels/Commands/RightClickMenuCommand.cs index b490957c..7ad31b6f 100644 --- a/FModel/ViewModels/Commands/RightClickMenuCommand.cs +++ b/FModel/ViewModels/Commands/RightClickMenuCommand.cs @@ -67,6 +67,7 @@ public class RightClickMenuCommand : ViewModelCommand "Save_Properties" => (EAction.Export, EShowAssetType.None, EBulkType.Properties), "Save_Textures" => (EAction.Export, EShowAssetType.None, EBulkType.Textures), "Save_Models" => (EAction.Export, EShowAssetType.None, EBulkType.Meshes), + "Save_Worlds" => (EAction.Export, EShowAssetType.None, EBulkType.Worlds), "Save_Animations" => (EAction.Export, EShowAssetType.None, EBulkType.Animations), "Save_Audio" => (EAction.Export, EShowAssetType.None, EBulkType.Audio), "Save_Code" => (EAction.Export, EShowAssetType.None, EBulkType.Code), @@ -108,6 +109,7 @@ public class RightClickMenuCommand : ViewModelCommand EBulkType.Properties => (UserSettings.Default.PropertiesDirectory, "json files"), EBulkType.Textures => (UserSettings.Default.TextureDirectory, "textures"), EBulkType.Meshes => (UserSettings.Default.ModelDirectory, "models"), + EBulkType.Worlds => (UserSettings.Default.ModelDirectory, "worlds"), EBulkType.Animations => (UserSettings.Default.ModelDirectory, "animations"), EBulkType.Audio => (UserSettings.Default.AudioDirectory, "audio files"), EBulkType.Code => (UserSettings.Default.CodeDirectory, "code files"), @@ -126,16 +128,17 @@ public class RightClickMenuCommand : ViewModelCommand foreach (var folder in folders) { cancellationToken.ThrowIfCancellationRequested(); + var queuedBefore = ExportSessionViewModel.Instance.Session.TotalQueued; folderAction(folder); var path = Path.Combine(dirType, UserSettings.Default.KeepDirectoryStructure ? folder.PathAtThisPoint : folder.PathAtThisPoint.SubstringAfterLast('/')).Replace('\\', '/'); - LogExport(contextViewModel, folder.PathAtThisPoint, path, dirType, filetype); + LogExport(contextViewModel, folder.PathAtThisPoint, path, dirType, filetype, queuedBefore); } - Action fileAction = bulktype switch + Action fileAction = bulktype switch { - EBulkType.Raw => (entry, _, update) => contextViewModel.CUE4Parse.ExportData(entry, !update), - _ => (entry, bulk, update) => contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, bulk), + EBulkType.Raw => (entry, _) => contextViewModel.CUE4Parse.ExportData(entry), + _ => (entry, bulk) => contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, bulk), }; foreach (var group in assetsGroups) @@ -144,24 +147,31 @@ public class RightClickMenuCommand : ViewModelCommand var list = group.ToArray(); var update = list.Length > 1; var bulk = bulktype | (update ? EBulkType.Auto : EBulkType.None); + var queuedBefore = ExportSessionViewModel.Instance.Session.TotalQueued; foreach (var entry in list) { Thread.Yield(); cancellationToken.ThrowIfCancellationRequested(); - fileAction(entry, bulk, update); + fileAction(entry, bulk); } if (update) { var path = Path.Combine(dirType, UserSettings.Default.KeepDirectoryStructure ? directory : directory.SubstringAfterLast('/')).Replace('\\', '/'); - LogExport(contextViewModel, directory, path, dirType, filetype); + LogExport(contextViewModel, directory, path, dirType, filetype, queuedBefore); } } }); + + if (action is EAction.Export) + { + await ExportSessionViewModel.Instance.ExportAutomaticallyAsync(); + } } - private void LogExport(ApplicationViewModel contextViewModel, string directory, string path, string basePath, string fileType) + private void LogExport(ApplicationViewModel contextViewModel, string directory, string path, string basePath, string fileType, int queuedBefore = 0) { + var queuedDelta = ExportSessionViewModel.Instance.Session.TotalQueued - queuedBefore; if (contextViewModel.CUE4Parse.ExportedCount > 0) { FLogger.Append(ELog.Information, () => @@ -170,6 +180,13 @@ public class RightClickMenuCommand : ViewModelCommand FLogger.Link(directory, Path.Exists(path) ? path : basePath, true); }); } + else if (queuedDelta > 0) + { + FLogger.Append(ELog.Information, () => + { + FLogger.Text($"Queued {queuedDelta} {fileType} for export from {directory}{(UserSettings.Default.ExportImmediately ? ", exporting automatically..." : "")}", Constants.WHITE, true); + }); + } else if (contextViewModel.CUE4Parse.FailedExportCount == 0) { // Not an error because folder simply might not contain type of asset user is trying to save diff --git a/FModel/ViewModels/Commands/TabCommand.cs b/FModel/ViewModels/Commands/TabCommand.cs index a622d5e4..4259fcbc 100644 --- a/FModel/ViewModels/Commands/TabCommand.cs +++ b/FModel/ViewModels/Commands/TabCommand.cs @@ -1,3 +1,4 @@ +using System; using System.Windows; using AdonisUI.Controls; using FModel.Framework; @@ -31,9 +32,15 @@ public class TabCommand : ViewModelCommand case "Close_Other_Tabs": _applicationView.CUE4Parse.TabControl.RemoveOtherTabs(tabViewModel); break; + case "Assets_Show_Metadata": + _applicationView.CUE4Parse.ShowMetadata(tabViewModel.Entry); + break; case "Find_References": _applicationView.CUE4Parse.FindReferences(tabViewModel.Entry); break; + case "Assets_Decompile": + _applicationView.CUE4Parse.Decompile(tabViewModel.Entry); + break; case "Save_Data": await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.ExportData(tabViewModel.Entry)); break; @@ -55,6 +62,12 @@ public class TabCommand : ViewModelCommand _applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Meshes); }); break; + case "Save_Worlds": + await _threadWorkerView.Begin(cancellationToken => + { + _applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Worlds); + }); + break; case "Save_Animations": await _threadWorkerView.Begin(cancellationToken => { @@ -77,9 +90,26 @@ public class TabCommand : ViewModelCommand }.Show(); }); break; - case "Copy_Asset_Path": + case "File_Path": Clipboard.SetText(tabViewModel.Entry.Path); break; + case "File_Name": + Clipboard.SetText(tabViewModel.Entry.Name); + break; + case "Directory_Path": + Clipboard.SetText(tabViewModel.Entry.Directory); + break; + case "File_Path_No_Extension": + Clipboard.SetText(tabViewModel.Entry.PathWithoutExtension); + break; + case "File_Name_No_Extension": + Clipboard.SetText(tabViewModel.Entry.NameWithoutExtension); + break; + } + + if (parameter is string command && command.StartsWith("Save_", StringComparison.Ordinal)) // This is kinda bad + { + await ExportSessionViewModel.Instance.ExportAutomaticallyAsync(); } } } diff --git a/FModel/ViewModels/ExportOptionsViewModel.cs b/FModel/ViewModels/ExportOptionsViewModel.cs new file mode 100644 index 00000000..5f41ed1b --- /dev/null +++ b/FModel/ViewModels/ExportOptionsViewModel.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; +using System.Windows.Threading; +using CUE4Parse.UE4.Assets.Exports.Material; +using CUE4Parse.UE4.Assets.Exports.Texture; +using CUE4Parse_Conversion.Options; +using CUE4Parse_Conversion.Writers.UEFormat.Enums; +using FModel.Framework; +using FModel.Settings; + +namespace FModel.ViewModels; + +public class ExportOptionsViewModel : ViewModel +{ + public bool OverrideOptions + { + get; + set => SetProperty(ref field, value); + } + + private DispatcherTimer? _feedbackTimer; + public string? FeedbackMessage + { + get; + private set + { + if (!SetProperty(ref field, value)) return; + RaisePropertyChanged(nameof(HasFeedback)); + _feedbackTimer?.Stop(); + if (string.IsNullOrWhiteSpace(value)) return; + + if (_feedbackTimer == null) + { + _feedbackTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) }; +#pragma warning disable CA2011 + _feedbackTimer.Tick += (_, _) => FeedbackMessage = null; +#pragma warning restore CA2011 + } + _feedbackTimer.Start(); + } + } + public bool HasFeedback => FeedbackMessage != null; + + public string OutputDirectory + { + get; + set => SetProperty(ref field, value); + } + + public bool ShowExportImmediatelyOption { get; } + public bool ExportImmediately + { + get; + set => SetProperty(ref field, value); + } + + public IEnumerable MeshFormats { get; } = Enum.GetValues(); + public EMeshFormat SelectedMeshFormat + { + get; + set + { + if (!SetProperty(ref field, value)) return; + RaisePropertyChanged(nameof(SocketSettingsEnabled)); + RaisePropertyChanged(nameof(CompressionSettingsEnabled)); + RaisePropertyChanged(nameof(TextureFormatsEnabled)); + + if (value == EMeshFormat.USD) + { + _preUsdTextureFormat = SelectedTextureFormat; + SelectedTextureFormat = ETextureFormat.Png; + } + else if (_preUsdTextureFormat.HasValue) + { + SelectedTextureFormat = _preUsdTextureFormat.Value; + _preUsdTextureFormat = null; + } + } + } + + public IEnumerable NaniteMeshFormats { get; } = Enum.GetValues(); + public ENaniteMeshFormat SelectedNaniteMeshFormat + { + get; + set + { + if (!SetProperty(ref field, value)) return; + RaisePropertyChanged(nameof(ShowNaniteWarning)); + } + } + + public bool ShowNaniteWarning => SelectedNaniteMeshFormat != ENaniteMeshFormat.NoNanite; + + public IEnumerable MeshQualities { get; } = Enum.GetValues(); + public EMeshQuality SelectedMeshQuality + { + get; + set => SetProperty(ref field, value); + } + + public IEnumerable SocketFormats { get; } = Enum.GetValues(); + public ESocketFormat SelectedSocketFormat + { + get; + set => SetProperty(ref field, value); + } + + public bool SocketSettingsEnabled => SelectedMeshFormat == EMeshFormat.ActorX; + + public IEnumerable CompressionFormats { get; } = Enum.GetValues(); + public EFileCompressionFormat SelectedCompressionFormat + { + get; + set => SetProperty(ref field, value); + } + + public bool CompressionSettingsEnabled => SelectedMeshFormat == EMeshFormat.UEFormat; + + public IEnumerable MaterialDepths { get; } = Enum.GetValues(); + public EMaterialDepth SelectedMaterialDepth + { + get; + set => SetProperty(ref field, value); + } + public bool ExportMaterials + { + get; + set => SetProperty(ref field, value); + } + + public IEnumerable TexturePlatforms { get; } = Enum.GetValues(); + public ETexturePlatform SelectedTexturePlatform + { + get; + set => SetProperty(ref field, value); + } + + public IEnumerable TextureFormats { get; } = Enum.GetValues(); + public ETextureFormat SelectedTextureFormat + { + get; + set => SetProperty(ref field, value); + } + + private ETextureFormat? _preUsdTextureFormat; + public bool TextureFormatsEnabled => SelectedMeshFormat != EMeshFormat.USD; + + public bool ExportHdrTexturesAsHdr + { + get; + set => SetProperty(ref field, value); + } + + public int TextureQuality + { + get; + set => SetProperty(ref field, value); + } + + public bool ExportMorphTargets + { + get; + set => SetProperty(ref field, value); + } + + public bool ExportAllTextureMips + { + get; + set => SetProperty(ref field, value); + } + + public ExportOptionsViewModel(bool showExportImmediatelyOption = false) + { + ShowExportImmediatelyOption = showExportImmediatelyOption; + ResetToUserDefaults(); + } + + public void ResetToUserDefaults() + { + OutputDirectory = UserSettings.Default.ModelDirectory; + SelectedMeshFormat = UserSettings.Default.MeshExportFormat; + SelectedNaniteMeshFormat = UserSettings.Default.NaniteMeshExportFormat; + SelectedMeshQuality = UserSettings.Default.MeshQuality; + SelectedSocketFormat = UserSettings.Default.SocketExportFormat; + SelectedCompressionFormat = UserSettings.Default.CompressionFormat; + SelectedMaterialDepth = UserSettings.Default.MaterialExportFormat; + ExportMaterials = UserSettings.Default.SaveEmbeddedMaterials; + SelectedTexturePlatform = UserSettings.Default.CurrentDir.TexturePlatform; + SelectedTextureFormat = UserSettings.Default.TextureExportFormat; + ExportHdrTexturesAsHdr = UserSettings.Default.SaveHdrTexturesAsHdr; + ExportMorphTargets = UserSettings.Default.SaveMorphTargets; + TextureQuality = UserSettings.Default.TextureQuality; + ExportAllTextureMips = UserSettings.Default.ExportAllTextureMips; + ExportImmediately = UserSettings.Default.ExportImmediately; + + OverrideOptions = false; + FeedbackMessage = "Reset to defaults"; + } + + public void SaveAsUserDefaults() + { + UserSettings.Default.ModelDirectory = OutputDirectory; + UserSettings.Default.MeshExportFormat = SelectedMeshFormat; + UserSettings.Default.NaniteMeshExportFormat = SelectedNaniteMeshFormat; + UserSettings.Default.MeshQuality = SelectedMeshQuality; + UserSettings.Default.SocketExportFormat = SelectedSocketFormat; + UserSettings.Default.CompressionFormat = SelectedCompressionFormat; + UserSettings.Default.MaterialExportFormat = SelectedMaterialDepth; + UserSettings.Default.SaveEmbeddedMaterials = ExportMaterials; + UserSettings.Default.CurrentDir.TexturePlatform = SelectedTexturePlatform; + UserSettings.Default.TextureExportFormat = SelectedTextureFormat; + UserSettings.Default.SaveHdrTexturesAsHdr = ExportHdrTexturesAsHdr; + UserSettings.Default.SaveMorphTargets = ExportMorphTargets; + UserSettings.Default.TextureQuality = TextureQuality; + UserSettings.Default.ExportAllTextureMips = ExportAllTextureMips; + UserSettings.Default.ExportImmediately = ExportImmediately; + UserSettings.Save(); + + OverrideOptions = false; + FeedbackMessage = "Saved as default"; + } + + public ExportOptions BuildOptions() => new( + SelectedMeshFormat, + SelectedNaniteMeshFormat, + SelectedMeshQuality, + SelectedTexturePlatform, + SelectedTextureFormat, + TextureQuality, + ExportHdrTexturesAsHdr, + SelectedMaterialDepth, + ExportMaterials, + ExportMorphTargets, + SelectedSocketFormat, + SelectedCompressionFormat, + ExportAllTextureMips + ); +} diff --git a/FModel/ViewModels/ExportSessionViewModel.cs b/FModel/ViewModels/ExportSessionViewModel.cs new file mode 100644 index 00000000..6d225ef1 --- /dev/null +++ b/FModel/ViewModels/ExportSessionViewModel.cs @@ -0,0 +1,429 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Threading; +using CUE4Parse_Conversion; +using CUE4Parse_Conversion.Options; +using CUE4Parse.Utils; +using FModel.Extensions; +using FModel.Framework; +using FModel.Settings; +using FModel.Views; +using FModel.Views.Snooper; +using Serilog.Events; + +namespace FModel.ViewModels; + +public class ExportSessionViewModel : ViewModel +{ + public static ExportSessionViewModel Instance { get; } = new(); + + private DispatcherTimer? _toastTimer; + public bool ShowQueueToast + { + get; + set + { + if (!SetProperty(ref field, value)) return; + if (!value) + { + _toastTimer?.Stop(); + return; + } + + if (_toastTimer == null) + { + _toastTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(5) }; + _toastTimer.Tick += (_, _) => + { + field = false; + RaisePropertyChanged(nameof(ShowQueueToast)); + _toastTimer.Stop(); + }; + } + + _toastTimer.Stop(); + _toastTimer.Start(); + } + } + + private ExportSession? _session; + public ExportSession Session + { + get + { + if (_session != null) return _session; + _session = new ExportSession((args, ct) => + { + Application.Current.Dispatcher.Invoke(() => + { + var window = new StreamingLevelFilterWindow(new StreamingLevelFilterViewModel(args)); + _stopwatch.Stop(); + window.ShowDialog(); + _stopwatch.Start(); + }, DispatcherPriority.Normal, ct); + }); + _session.PropertyChanged += OnSessionPropertyChanged; + return _session; + } + } + + public ExportOptionsViewModel Options { get; } = new(); + + public bool IsRunning + { + get; + private set + { + if (!SetProperty(ref field, value)) return; + RaisePropertyChanged(nameof(CanExport)); + } + } + public bool IsFinished + { + get; + private set => SetProperty(ref field, value); + } + public bool CanExport => !IsRunning && Session.TotalQueued > 0; + + public int CompletedCount + { + get; + private set => SetProperty(ref field, value); + } + public int SucceededCount + { + get; + private set => SetProperty(ref field, value); + } + public int FailedCount + { + get; + private set => SetProperty(ref field, value); + } + public string? CurrentItemName + { + get; + private set => SetProperty(ref field, value); + } + public TimeSpan ElapsedTime + { + get; + private set => SetProperty(ref field, value); + } + public TimeSpan? EtaTime + { + get; + private set => SetProperty(ref field, value); + } + public bool IsCanceled + { + get; + private set => SetProperty(ref field, value); + } + public double ProgressValue + { + get; + private set => SetProperty(ref field, value); + } + + public ObservableCollection ClassGroups { get; } = []; + + private CancellationTokenSource? _cts; + private readonly Stopwatch _stopwatch = new(); + private readonly ConcurrentQueue _pendingLogs = new(); + private DispatcherTimer? _uiTimer; + + private ExportSessionViewModel() + { + ImGuiSink.Instance.OnExporterLogEvent += OnLogEvent; + } + + private void OnLogEvent(LogEvent log) + { + _pendingLogs.Enqueue(log); + Application.Current?.Dispatcher.InvokeAsync(DrainLogs); + } + + private int _previousCount; + private void OnSessionPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName != nameof(ExportSession.TotalQueued)) return; + + var count = _session?.TotalQueued ?? 0; + Application.Current?.Dispatcher.InvokeAsync(() => + { + if (count > 0 && _previousCount == 0) + { + ClearExportHistory(); + } + + ShowQueueToast = count switch + { + > 0 when _previousCount == 0 => true, + 0 => false, + _ => ShowQueueToast + }; + _previousCount = count; + RaisePropertyChanged(nameof(CanExport)); + }); + } + + public async Task ExportAsync() + { + if (IsRunning || Session.TotalQueued == 0) return; + + IsRunning = true; + IsFinished = false; + IsCanceled = false; + CompletedCount = 0; + SucceededCount = 0; + FailedCount = 0; + _stopwatch.Restart(); + + _cts = new CancellationTokenSource(); + StartUiTimer(); + + string exportDirectory; + ExportOptions exportOptions; + if (Options.OverrideOptions) + { + exportDirectory = Options.OutputDirectory; + exportOptions = Options.BuildOptions(); + } + else + { + exportDirectory = UserSettings.Default.ModelDirectory; + exportOptions = UserSettings.GetExportOptions(); + } + + var progress = new Progress(p => + { + Application.Current?.Dispatcher.InvokeAsync(() => + { + CompletedCount = p.Completed; + CurrentItemName = p.LastResult?.ObjectPath; + if (p.LastResult != null) + { + if (p.LastResult.Success) SucceededCount++; + else FailedCount++; + } + ProgressValue = p.Total > 0 ? (double)p.Completed / p.Total : 0; + }); + }); + + try + { + await Session.RunAsync(exportDirectory, exportOptions, progress, _cts.Token).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + Application.Current?.Dispatcher.InvokeAsync(() => IsCanceled = true); + } + finally + { + _stopwatch.Stop(); + Application.Current?.Dispatcher.InvokeAsync(() => + { + StopUiTimer(); + IsRunning = false; + IsFinished = !IsCanceled; + UpdateElapsedAndEta(); + }); + } + } + + public async Task ExportAutomaticallyAsync() + { + if (UserSettings.Default.ExportImmediately) + { + await ExportAsync(); + } + } + + public void CancelExport() + { + _cts?.Cancel(); + } + + public void ClearQueue() + { + _session?.Clear(); + ClearExportHistory(); + } + + public void RemoveFromQueue(ObjectGroupViewModel item) + { + if (IsRunning || _session?.Remove(item.Path) != true) + return; + + var group = ClassGroups.FirstOrDefault(x => x.Objects.Contains(item)); + if (group == null) + return; + + group.Objects.Remove(item); + if (group.Objects.Count == 0) + { + ClassGroups.Remove(group); + } + } + + private void StartUiTimer() + { + _uiTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) }; + _uiTimer.Tick += (_, _) => UpdateElapsedAndEta(); + _uiTimer.Start(); + } + + private void StopUiTimer() + { + _uiTimer?.Stop(); + _uiTimer = null; + } + + private void UpdateElapsedAndEta() + { + ElapsedTime = _stopwatch.Elapsed; + + var remaining = Session.TotalQueued; + if (IsRunning && remaining > 0 && CompletedCount > 1 && ElapsedTime.TotalSeconds > 0) + { + var rate = CompletedCount / ElapsedTime.TotalSeconds; + if (rate > 0) + { + EtaTime = TimeSpan.FromSeconds(remaining / rate); + return; + } + } + EtaTime = null; + } + + private void ClearExportHistory() + { + CompletedCount = 0; + SucceededCount = 0; + FailedCount = 0; + ProgressValue = 0; + ElapsedTime = TimeSpan.Zero; + EtaTime = null; + CurrentItemName = null; + IsFinished = false; + IsCanceled = false; + ClassGroups.Clear(); + } + + private void DrainLogs() + { + while (_pendingLogs.TryDequeue(out var log)) + { + var className = log.GetContext("ClassName"); + var objectPath = log.GetContext("ObjectPath"); + var filePath = log.GetContext("FilePath"); + + var cg = FindOrCreateClass(className); + var og = FindOrCreateObject(cg, objectPath); + if (log.Level >= LogEventLevel.Error) + { + og.ErrorCount++; + cg.ErrorCount++; + } + if (og.FirstFilePath == null && !string.IsNullOrEmpty(filePath)) + og.FirstFilePath = filePath; + og.Entries.Add(new LogEntryViewModel(log)); + } + } + + private ClassGroupViewModel FindOrCreateClass(string name) + { + var cg = ClassGroups.FirstOrDefault(c => c.Name == name); + if (cg != null) return cg; + cg = new ClassGroupViewModel(name); + ClassGroups.Add(cg); + return cg; + } + + private static ObjectGroupViewModel FindOrCreateObject(ClassGroupViewModel cg, string path) + { + var og = cg.Objects.FirstOrDefault(o => o.Path == path); + if (og != null) return og; + og = new ObjectGroupViewModel(path); + cg.Objects.Add(og); + return og; + } +} + +public class ClassGroupViewModel(string name) : ViewModel +{ + public string Name { get; } = name; + public ObservableCollection Objects { get; } = []; + + public bool IsExpanded + { + get; + set => SetProperty(ref field, value); + } + + public int ErrorCount + { + get; + set + { + SetProperty(ref field, value); + RaisePropertyChanged(nameof(HasErrors)); + } + } + public override bool HasErrors => ErrorCount > 0; +} + +public class ObjectGroupViewModel(string path) : ViewModel +{ + public string Path { get; } = path; + public string Name { get; } = path.SubstringAfterLast('.'); + public ObservableCollection Entries { get; } = []; + + public bool IsExpanded + { + get; + set => SetProperty(ref field, value); + } + + public int ErrorCount + { + get; + set + { + SetProperty(ref field, value); + RaisePropertyChanged(nameof(HasErrors)); + } + } + public override bool HasErrors => ErrorCount > 0; + + public string? FirstFilePath + { + get; + set + { + SetProperty(ref field, value); + RaisePropertyChanged(nameof(HasFilePath)); + } + } + public bool HasFilePath => FirstFilePath != null; +} + +public class LogEntryViewModel(LogEvent log) +{ + public LogEventLevel Level { get; } = log.Level; + public DateTimeOffset Timestamp { get; } = log.Timestamp; + public string Message { get; } = log.Exception switch + { + NullReferenceException or ArgumentException => log.RenderMessage(), + _ => log.Exception?.Message ?? log.RenderMessage() + }; + public Exception? Exception { get; } = log.Exception; +} diff --git a/FModel/ViewModels/GameFileViewModel.cs b/FModel/ViewModels/GameFileViewModel.cs index ea33dac9..f01ac7bf 100644 --- a/FModel/ViewModels/GameFileViewModel.cs +++ b/FModel/ViewModels/GameFileViewModel.cs @@ -170,7 +170,7 @@ public class GameFileViewModel(GameFile asset) : ViewModel if (Asset.Extension is "umap") { AssetCategory = EAssetCategory.World; - AssetActions = EBulkType.Meshes | EBulkType.Textures | EBulkType.Audio | EBulkType.Code; + AssetActions = EBulkType.Worlds | EBulkType.Textures | EBulkType.Audio | EBulkType.Code; ResolvedAssetType = "World"; Resolved |= EResolveCompute.Preview; return Task.CompletedTask; diff --git a/FModel/ViewModels/GameSelectorViewModel.cs b/FModel/ViewModels/GameSelectorViewModel.cs index 369945b2..6447e2d7 100644 --- a/FModel/ViewModels/GameSelectorViewModel.cs +++ b/FModel/ViewModels/GameSelectorViewModel.cs @@ -134,11 +134,27 @@ public class GameSelectorViewModel : ViewModel } } - var crashReportClientExe = Path.Combine(projectDir, "..", "Engine", "Binaries", "Win64", "CrashReportClient.exe"); - if (File.Exists(crashReportClientExe) && TryGetUeVersionFromExe(crashReportClientExe, out ueVersion)) + var projectEngineBinariesDir = Path.Combine(projectDir, "..", "Engine", "Binaries", "Win64"); + + if (Directory.Exists(projectEngineBinariesDir)) { - Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, crashReportClientExe); - return true; + var crashReportClientExe = Path.Combine(projectEngineBinariesDir, "CrashReportClient.exe"); + if (File.Exists(crashReportClientExe) && TryGetUeVersionFromExe(crashReportClientExe, out ueVersion)) + { + Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, crashReportClientExe); + return true; + } + if (Directory.GetFiles(projectEngineBinariesDir, "*-Win64-Shipping.exe") is { Length: > 0 } shipping) + { + foreach (var exe in shipping) + { + if (TryGetUeVersionFromExe(exe, out ueVersion)) + { + Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, exe); + return true; + } + } + } } ueVersion = EGame.GAME_UE4_LATEST; diff --git a/FModel/ViewModels/SettingsViewModel.cs b/FModel/ViewModels/SettingsViewModel.cs index 1441cd2a..0b91ff26 100644 --- a/FModel/ViewModels/SettingsViewModel.cs +++ b/FModel/ViewModels/SettingsViewModel.cs @@ -2,20 +2,16 @@ 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 FModel.Extensions; +using CUE4Parse_Conversion.Options; +using CUE4Parse_Conversion.Writers.UEFormat.Enums; +using CUE4Parse.UE4.Assets.Exports.Material; using FModel.Extensions.Themes; using FModel.Framework; using FModel.Services; using FModel.Settings; -using ICSharpCode.AvalonEdit.Highlighting; namespace FModel.ViewModels; @@ -30,13 +26,6 @@ public class SettingsViewModel : ViewModel set => SetProperty(ref _useCustomOutputFolders, value); } - private ETexturePlatform _selectedUePlatform; - public ETexturePlatform SelectedUePlatform - { - get => _selectedUePlatform; - set => SetProperty(ref _selectedUePlatform, value); - } - private EGame _selectedUeGame; public EGame SelectedUeGame { @@ -114,60 +103,6 @@ public class SettingsViewModel : ViewModel set => SetProperty(ref _selectedCosmeticStyle, value); } - private EMeshFormat _selectedMeshExportFormat; - public EMeshFormat SelectedMeshExportFormat - { - get => _selectedMeshExportFormat; - set - { - SetProperty(ref _selectedMeshExportFormat, value); - RaisePropertyChanged(nameof(SocketSettingsEnabled)); - RaisePropertyChanged(nameof(CompressionSettingsEnabled)); - } - } - - private ESocketFormat _selectedSocketExportFormat; - public ESocketFormat SelectedSocketExportFormat - { - get => _selectedSocketExportFormat; - set => SetProperty(ref _selectedSocketExportFormat, value); - } - - private EFileCompressionFormat _selectedCompressionFormat; - public EFileCompressionFormat SelectedCompressionFormat - { - get => _selectedCompressionFormat; - set => SetProperty(ref _selectedCompressionFormat, value); - } - - private ELodFormat _selectedLodExportFormat; - public ELodFormat SelectedLodExportFormat - { - get => _selectedLodExportFormat; - set => SetProperty(ref _selectedLodExportFormat, value); - } - - private ENaniteMeshFormat _selectedNaniteMeshExportFormat; - public ENaniteMeshFormat SelectedNaniteMeshExportFormat - { - get => _selectedNaniteMeshExportFormat; - set => SetProperty(ref _selectedNaniteMeshExportFormat, value); - } - - private EMaterialFormat _selectedMaterialExportFormat; - public EMaterialFormat SelectedMaterialExportFormat - { - get => _selectedMaterialExportFormat; - set => SetProperty(ref _selectedMaterialExportFormat, value); - } - - private ETextureFormat _selectedTextureExportFormat; - public ETextureFormat SelectedTextureExportFormat - { - get => _selectedTextureExportFormat; - set => SetProperty(ref _selectedTextureExportFormat, value); - } - private EJsonHighlightTheme _selectedJsonHighlightTheme; public EJsonHighlightTheme SelectedJsonHighlightTheme { @@ -189,8 +124,7 @@ public class SettingsViewModel : ViewModel set => SetProperty(ref _unluacOpcodeMap, value); } - public bool SocketSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.ActorX; - public bool CompressionSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.UEFormat; + public ExportOptionsViewModel Options { get; } = new(showExportImmediatelyOption: true); public ReadOnlyObservableCollection UeGames { get; private set; } public ReadOnlyObservableCollection AssetLanguages { get; private set; } @@ -198,14 +132,6 @@ public class SettingsViewModel : ViewModel public ReadOnlyObservableCollection DiscordRpcs { get; private set; } public ReadOnlyObservableCollection CompressedAudios { get; private set; } public ReadOnlyObservableCollection CosmeticStyles { get; private set; } - public ReadOnlyObservableCollection MeshExportFormats { get; private set; } - public ReadOnlyObservableCollection SocketExportFormats { get; private set; } - public ReadOnlyObservableCollection CompressionFormats { get; private set; } - public ReadOnlyObservableCollection LodExportFormats { get; private set; } - public ReadOnlyObservableCollection NaniteMeshExportFormats { get; private set; } - public ReadOnlyObservableCollection MaterialExportFormats { get; private set; } - public ReadOnlyObservableCollection TextureExportFormats { get; private set; } - public ReadOnlyObservableCollection Platforms { get; private set; } public ReadOnlyObservableCollection JsonHighlightThemes { get; private set; } private string _outputSnapshot; @@ -216,7 +142,6 @@ public class SettingsViewModel : ViewModel private string _codeSnapshot; private string _modelSnapshot; private string _gameSnapshot; - private ETexturePlatform _uePlatformSnapshot; private EGame _ueGameSnapshot; private IList _customVersionsSnapshot; private IDictionary _optionsSnapshot; @@ -224,13 +149,6 @@ public class SettingsViewModel : ViewModel private ELanguage _assetLanguageSnapshot; private ECompressedAudio _compressedAudioSnapshot; private EIconStyle _cosmeticStyleSnapshot; - private EMeshFormat _meshExportFormatSnapshot; - private ESocketFormat _socketExportFormatSnapshot; - private EFileCompressionFormat _compressionFormatSnapshot; - private ELodFormat _lodExportFormatSnapshot; - private ENaniteMeshFormat _naniteMeshExportFormatSnapshot; - private EMaterialFormat _materialExportFormatSnapshot; - private ETextureFormat _textureExportFormatSnapshot; private EJsonHighlightTheme _jsonHighlightThemeSnapshot; private bool _mappingsUpdate = false; @@ -250,7 +168,6 @@ public class SettingsViewModel : ViewModel _codeSnapshot = UserSettings.Default.CodeDirectory; _modelSnapshot = UserSettings.Default.ModelDirectory; _gameSnapshot = UserSettings.Default.GameDirectory; - _uePlatformSnapshot = UserSettings.Default.CurrentDir.TexturePlatform; _ueGameSnapshot = UserSettings.Default.CurrentDir.UeVersion; _customVersionsSnapshot = UserSettings.Default.CurrentDir.Versioning.CustomVersions; _optionsSnapshot = UserSettings.Default.CurrentDir.Versioning.Options; @@ -269,16 +186,8 @@ public class SettingsViewModel : ViewModel _assetLanguageSnapshot = UserSettings.Default.AssetLanguage; _compressedAudioSnapshot = UserSettings.Default.CompressedAudioMode; _cosmeticStyleSnapshot = UserSettings.Default.CosmeticStyle; - _meshExportFormatSnapshot = UserSettings.Default.MeshExportFormat; - _socketExportFormatSnapshot = UserSettings.Default.SocketExportFormat; - _compressionFormatSnapshot = UserSettings.Default.CompressionFormat; - _lodExportFormatSnapshot = UserSettings.Default.LodExportFormat; - _naniteMeshExportFormatSnapshot = UserSettings.Default.NaniteMeshExportFormat; - _materialExportFormatSnapshot = UserSettings.Default.MaterialExportFormat; - _textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat; _jsonHighlightThemeSnapshot = UserSettings.Default.JsonHighlightTheme; - SelectedUePlatform = _uePlatformSnapshot; SelectedUeGame = _ueGameSnapshot; SelectedCustomVersions = _customVersionsSnapshot; SelectedOptions = _optionsSnapshot; @@ -286,13 +195,6 @@ public class SettingsViewModel : ViewModel SelectedAssetLanguage = _assetLanguageSnapshot; SelectedCompressedAudio = _compressedAudioSnapshot; SelectedCosmeticStyle = _cosmeticStyleSnapshot; - SelectedMeshExportFormat = _meshExportFormatSnapshot; - SelectedSocketExportFormat = _socketExportFormatSnapshot; - SelectedCompressionFormat = _selectedCompressionFormat; - SelectedLodExportFormat = _lodExportFormatSnapshot; - SelectedNaniteMeshExportFormat = _naniteMeshExportFormatSnapshot; - SelectedMaterialExportFormat = _materialExportFormatSnapshot; - SelectedTextureExportFormat = _textureExportFormatSnapshot; CriwareDecryptionKey = _criwareDecryptionKey; UnluacOpcodeMap = _unluacOpcodeMap; SelectedJsonHighlightTheme = _jsonHighlightThemeSnapshot; @@ -305,14 +207,6 @@ public class SettingsViewModel : ViewModel DiscordRpcs = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateDiscordRpcs())); CompressedAudios = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateCompressedAudios())); CosmeticStyles = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateCosmeticStyles())); - MeshExportFormats = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateMeshExportFormat())); - SocketExportFormats = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateSocketExportFormat())); - CompressionFormats = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateCompressionFormat())); - LodExportFormats = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateLodExportFormat())); - NaniteMeshExportFormats = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateNaniteMeshExportFormat())); - MaterialExportFormats = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateMaterialExportFormat())); - TextureExportFormats = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateTextureExportFormat())); - Platforms = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateUePlatforms())); JsonHighlightThemes = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateJsonHighlightThemes())); } @@ -327,13 +221,12 @@ public class SettingsViewModel : ViewModel whatShouldIDo.Add(SettingsOut.ReloadMappings); if (_ueGameSnapshot != SelectedUeGame || _customVersionsSnapshot != SelectedCustomVersions || - _uePlatformSnapshot != SelectedUePlatform || _optionsSnapshot != SelectedOptions || // combobox + _optionsSnapshot != SelectedOptions || // combobox _mapStructTypesSnapshot != SelectedMapStructTypes || _gameSnapshot != UserSettings.Default.GameDirectory) // textbox restart = true; UserSettings.Default.CurrentDir.UeVersion = SelectedUeGame; - UserSettings.Default.CurrentDir.TexturePlatform = SelectedUePlatform; UserSettings.Default.CurrentDir.Versioning.CustomVersions = SelectedCustomVersions; UserSettings.Default.CurrentDir.Versioning.Options = SelectedOptions; UserSettings.Default.CurrentDir.Versioning.MapStructTypes = SelectedMapStructTypes; @@ -343,17 +236,12 @@ public class SettingsViewModel : ViewModel UserSettings.Default.AssetLanguage = SelectedAssetLanguage; UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio; UserSettings.Default.CosmeticStyle = SelectedCosmeticStyle; - UserSettings.Default.MeshExportFormat = SelectedMeshExportFormat; - UserSettings.Default.SocketExportFormat = SelectedSocketExportFormat; - UserSettings.Default.CompressionFormat = SelectedCompressionFormat; - UserSettings.Default.LodExportFormat = SelectedLodExportFormat; - UserSettings.Default.NaniteMeshExportFormat = SelectedNaniteMeshExportFormat; - UserSettings.Default.MaterialExportFormat = SelectedMaterialExportFormat; - UserSettings.Default.TextureExportFormat = SelectedTextureExportFormat; UserSettings.Default.AesReload = SelectedAesReload; UserSettings.Default.DiscordRpc = SelectedDiscordRpc; UserSettings.Default.JsonHighlightTheme = SelectedJsonHighlightTheme; + Options.SaveAsUserDefaults(); + if (SelectedDiscordRpc == EDiscordRpc.Never) _discordHandler.Shutdown(); @@ -370,13 +258,5 @@ public class SettingsViewModel : ViewModel private IEnumerable EnumerateDiscordRpcs() => Enum.GetValues(); private IEnumerable EnumerateCompressedAudios() => Enum.GetValues(); private IEnumerable EnumerateCosmeticStyles() => Enum.GetValues(); - private IEnumerable EnumerateMeshExportFormat() => Enum.GetValues(); - private IEnumerable EnumerateSocketExportFormat() => Enum.GetValues(); - private IEnumerable EnumerateCompressionFormat() => Enum.GetValues(); - private IEnumerable EnumerateLodExportFormat() => Enum.GetValues(); - private IEnumerable EnumerateNaniteMeshExportFormat() => Enum.GetValues(); - private IEnumerable EnumerateMaterialExportFormat() => Enum.GetValues(); - private IEnumerable EnumerateTextureExportFormat() => Enum.GetValues(); - private IEnumerable EnumerateUePlatforms() => Enum.GetValues(); private IEnumerable EnumerateJsonHighlightThemes() => Enum.GetValues(); } diff --git a/FModel/ViewModels/StreamingLevelFilterViewModel.cs b/FModel/ViewModels/StreamingLevelFilterViewModel.cs new file mode 100644 index 00000000..a77fc448 --- /dev/null +++ b/FModel/ViewModels/StreamingLevelFilterViewModel.cs @@ -0,0 +1,192 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using CUE4Parse_Conversion; +using CUE4Parse_Conversion.Dto; + +namespace FModel.ViewModels; + +public class StreamingLevelFilterViewModel +{ + public string WorldName { get; } + public int TotalCount { get; } + public List Children { get; } = []; + + public StreamingLevelFilterViewModel(StreamingLevelFilterArgs args) + { + WorldName = args.WorldName; + + foreach (var actor in args.Actors) + { + Children.Add(new ActorNodeVm(actor)); + } + + var worldLevels = new ActorNodeVm("Streaming Levels") { IsExpanded = true }; + foreach (var level in args.StreamingLevels) + { + worldLevels.Children.Add(new StreamingLevelNodeVm(level)); + } + if (worldLevels.Children.Count > 0) Children.Add(worldLevels); + + TotalCount = CountLevels(args.Actors) + args.StreamingLevels.Count; + } + + public void SkipAll() + { + foreach (var child in Children) + { + child.IsChecked = false; + } + } + + private int CountLevels(IReadOnlyList actors) + { + var n = 0; + foreach (var a in actors) n += CountFromActor(a); + return n; + } + + private int CountFromActor(ActorDto actor) + { + var n = actor.StreamingLevels?.Count ?? 0; + if (actor.RootComponent is { } comp) n += CountFromComponent(comp); + return n; + } + + private int CountFromComponent(SceneComponentDto comp) + { + var n = 0; + foreach (var a in comp.AttachedActors) n += CountFromActor(a); + foreach (var c in comp.Children) n += CountFromComponent(c); + return n; + } +} + +public class ActorNodeVm : TreeNodeVm +{ + public override string Name { get; } + public bool IsExpanded { get; set; } + public ObservableCollection Children { get; } = []; + + private static int _batch; + + public ActorNodeVm(string name) + { + Name = name; + Children.CollectionChanged += (_, e) => + { + if (e.NewItems is null) return; + + foreach (TreeNodeVm child in e.NewItems) + { + child.PropertyChanged += (_, args) => + { + if (args.PropertyName == nameof(IsChecked) && _batch == 0) + OnPropertyChanged(nameof(IsChecked)); + }; + } + }; + } + + public ActorNodeVm(ActorDto actor) : this(actor.Name) + { + if (actor.StreamingLevels is { Count: > 0 } streamingLevels) + foreach (var level in streamingLevels) + Children.Add(new StreamingLevelNodeVm(level)); + + CollectFromComponent(actor.RootComponent); + } + + private ActorNodeVm(SceneComponentDto component) : this(component.Name) + { + CollectFromComponent(component); + } + + private void NotifyIntermediates() + { + foreach (var child in Children.OfType()) + { + child.NotifyIntermediates(); + child.OnPropertyChanged(nameof(IsChecked)); + } + } + + private void CollectFromComponent(SceneComponentDto? comp) + { + if (comp is null) return; + foreach (var actor in comp.AttachedActors) + Children.Add(new ActorNodeVm(actor)); + foreach (var component in comp.Children) + Children.Add(new ActorNodeVm(component)); + } + + private IEnumerable AllLevels() + { + foreach (var child in Children) + { + switch (child) + { + case StreamingLevelNodeVm sl: + yield return sl; + break; + case ActorNodeVm actor: + { + foreach (var l in actor.AllLevels()) + { + yield return l; + } + break; + } + } + } + } + + public bool? IsChecked + { + get + { + var all = AllLevels().ToList(); + if (all.Count == 0) return false; + + var trueCount = all.Count(l => l.IsChecked); + if (trueCount == all.Count) return true; + if (trueCount == 0) return false; + return null; + } + set + { + var v = value ?? false; + _batch++; + foreach (var l in AllLevels()) + l.IsChecked = v; + NotifyIntermediates(); + _batch--; + OnPropertyChanged(); + } + } +} + +public class StreamingLevelNodeVm(StreamingLevel level) : TreeNodeVm +{ + public override string Name { get; } = level.World.Name; + + public bool IsChecked + { + get => level.IsPersistent; + set + { + level.IsPersistent = value; + OnPropertyChanged(); + } + } +} + +public abstract class TreeNodeVm : INotifyPropertyChanged +{ + public abstract string Name { get; } + + public event PropertyChangedEventHandler? PropertyChanged; + protected void OnPropertyChanged([CallerMemberName] string? name = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); +} diff --git a/FModel/ViewModels/TabControlViewModel.cs b/FModel/ViewModels/TabControlViewModel.cs index dbeb4423..481e52be 100644 --- a/FModel/ViewModels/TabControlViewModel.cs +++ b/FModel/ViewModels/TabControlViewModel.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Windows; using System.Windows.Media.Imaging; +using CUE4Parse_Conversion.Options; using CUE4Parse.FileProvider.Objects; using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse.Utils; @@ -113,7 +114,7 @@ public class TabImage : ViewModel else { ImageBuffer = imageData; - ExportName += "." + (NoAlpha ? "jpg" : "png"); + ExportName += "." + (NoAlpha || UserSettings.Default.TextureExportFormat == ETextureFormat.Jpeg ? "jpg" : "png"); } using var stream = new MemoryStream(imageData); @@ -132,8 +133,6 @@ public class TabImage : ViewModel public class TabItem : ViewModel { - public string ParentExportType { get; private set; } - private GameFile _entry; public GameFile Entry { @@ -267,10 +266,9 @@ public class TabItem : ViewModel private GoToCommand _goToCommand; public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(null); - public TabItem(GameFile entry, string parentExportType) + public TabItem(GameFile entry) { Entry = entry; - ParentExportType = parentExportType; _images = new ObservableCollection(); } @@ -278,7 +276,6 @@ public class TabItem : ViewModel { Entry = entry; TitleExtra = string.Empty; - ParentExportType = string.Empty; ScrollTrigger = null; Application.Current.Dispatcher.Invoke(() => { @@ -472,7 +469,7 @@ public class TabControlViewModel : ViewModel public void AddTab() => AddTab("New Tab"); public void AddTab(string title) => AddTab(new FakeGameFile(title)); - public void AddTab(GameFile entry, string parentExportType = null) + public void AddTab(GameFile entry) { if (SelectedTab?.Header == "New Tab") { @@ -483,7 +480,7 @@ public class TabControlViewModel : ViewModel if (!CanAddTabs) return; Application.Current.Dispatcher.Invoke(() => { - _tabItems.Add(new TabItem(entry, parentExportType ?? string.Empty)); + _tabItems.Add(new TabItem(entry)); SelectedTab = _tabItems.Last(); }); } diff --git a/FModel/ViewModels/UpdateViewModel.cs b/FModel/ViewModels/UpdateViewModel.cs index 26acdb88..60ae9129 100644 --- a/FModel/ViewModels/UpdateViewModel.cs +++ b/FModel/ViewModels/UpdateViewModel.cs @@ -63,7 +63,7 @@ public partial class UpdateViewModel : ViewModel var coAuthorMap = new Dictionary>(); foreach (var commit in Commits) { - if (!commit.Commit.Message.Contains("Co-authored-by")) + if (!commit.Commit.Message.Contains("Co-authored-by", StringComparison.OrdinalIgnoreCase)) continue; var regex = GetCoAuthorRegex(); @@ -111,7 +111,7 @@ public partial class UpdateViewModel : ViewModel foreach (var (commit, usernames) in coAuthorMap) { var coAuthors = usernames - .Where(username => authorCache.ContainsKey(username)) + .Where(authorCache.ContainsKey) .Select(username => authorCache[username]) .ToArray(); diff --git a/FModel/Views/AudioPlayer.xaml.cs b/FModel/Views/AudioPlayer.xaml.cs index 332b6101..f65660ce 100644 --- a/FModel/Views/AudioPlayer.xaml.cs +++ b/FModel/Views/AudioPlayer.xaml.cs @@ -75,6 +75,8 @@ public partial class AudioPlayer _applicationView.AudioPlayer.Previous(); else if (UserSettings.Default.NextAudio.IsTriggered(e.Key)) _applicationView.AudioPlayer.Next(); + else if (UserSettings.Default.RemoveAudio.IsTriggered(e.Key)) + _applicationView.AudioPlayer.Remove(); } private void OnAudioFileMouseDoubleClick(object sender, MouseButtonEventArgs e) diff --git a/FModel/Views/ExportSessionWindow.xaml b/FModel/Views/ExportSessionWindow.xaml new file mode 100644 index 00000000..bf1cb9af --- /dev/null +++ b/FModel/Views/ExportSessionWindow.xaml @@ -0,0 +1,547 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +