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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FModel/Views/ExportSessionWindow.xaml.cs b/FModel/Views/ExportSessionWindow.xaml.cs
new file mode 100644
index 00000000..46ae6a50
--- /dev/null
+++ b/FModel/Views/ExportSessionWindow.xaml.cs
@@ -0,0 +1,73 @@
+using System.Diagnostics;
+using System.IO;
+using System.Windows;
+using System.Windows.Controls;
+using FModel.ViewModels;
+
+namespace FModel.Views;
+
+public partial class ExportSessionWindow
+{
+ public ExportSessionWindow()
+ {
+ InitializeComponent();
+ }
+
+ private async void OnExportClick(object sender, RoutedEventArgs e)
+ {
+ if (sender is Button { DataContext: ExportSessionViewModel { CanExport: true } viewModel })
+ await viewModel.ExportAsync();
+ }
+
+ private void OnOkClick(object sender, RoutedEventArgs e)
+ {
+ Close();
+ }
+
+ private void OnCancelClick(object sender, RoutedEventArgs e)
+ {
+ if (sender is Button { DataContext: ExportSessionViewModel viewModel })
+ viewModel.CancelExport();
+ }
+
+ private void OnClearQueueClick(object sender, RoutedEventArgs e)
+ {
+ if (sender is Button { DataContext: ExportSessionViewModel viewModel })
+ viewModel.ClearQueue();
+ }
+
+ private void OnRemoveFromQueueClick(object sender, RoutedEventArgs e)
+ {
+ if (sender is Button { DataContext: ObjectGroupViewModel item } && DataContext is ExportSessionViewModel viewModel)
+ viewModel.RemoveFromQueue(item);
+ }
+
+ private void OnMakeDefaultOptions(object sender, RoutedEventArgs e)
+ {
+ if (sender is Button { DataContext: ExportSessionViewModel viewModel })
+ viewModel.Options.SaveAsUserDefaults();
+ }
+
+ private void OnResetOptions(object sender, RoutedEventArgs e)
+ {
+ if (sender is Button { DataContext: ExportSessionViewModel viewModel })
+ viewModel.Options.ResetToUserDefaults();
+ }
+
+ private void OnOpenInExplorerClick(object sender, RoutedEventArgs e)
+ {
+ if (sender is not Button { Tag: string path }) return;
+
+ try
+ {
+ if (File.Exists(path) || Directory.Exists(path))
+ {
+ Process.Start("explorer.exe", $"/select,\"{path}\"");
+ }
+ }
+ catch
+ {
+ //
+ }
+ }
+}
diff --git a/FModel/Views/Resources/Controls/Aed/GamePathElementGenerator.cs b/FModel/Views/Resources/Controls/Aed/GamePathElementGenerator.cs
index babf9a55..3b6a7efe 100644
--- a/FModel/Views/Resources/Controls/Aed/GamePathElementGenerator.cs
+++ b/FModel/Views/Resources/Controls/Aed/GamePathElementGenerator.cs
@@ -33,7 +33,6 @@ public class GamePathElementGenerator : VisualLineElementGenerator
if (!m.Success || m.Index != 0 ||
!m.Groups.TryGetValue("target", out var g)) return null;
- var parentExportType = CurrentContext.Document.GetParentExportType(offset);
- return new GamePathVisualLineText(g.Value, parentExportType, CurrentContext.VisualLine, g.Length + g.Index + 1);
+ return new GamePathVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1);
}
}
diff --git a/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs b/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs
index ea19954c..51d6a5d1 100644
--- a/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs
+++ b/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs
@@ -17,16 +17,14 @@ public class GamePathVisualLineText : VisualLineText
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
- public delegate void GamePathOnClick(string gamePath, string parentExportType);
+ public delegate void GamePathOnClick(string gamePath);
public event GamePathOnClick OnGamePathClicked;
private readonly string _gamePath;
- private readonly string _parentExportType;
- public GamePathVisualLineText(string gamePath, string parentExportType, VisualLine parentVisualLine, int length) : base(parentVisualLine, length)
+ public GamePathVisualLineText(string gamePath, VisualLine parentVisualLine, int length) : base(parentVisualLine, length)
{
_gamePath = gamePath;
- _parentExportType = parentExportType;
}
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
@@ -59,14 +57,14 @@ public class GamePathVisualLineText : VisualLineText
if (e.Handled || OnGamePathClicked == null)
return;
- OnGamePathClicked(_gamePath, _parentExportType);
+ OnGamePathClicked(_gamePath);
e.Handled = true;
}
protected override VisualLineText CreateInstance(int length)
{
- var a = new GamePathVisualLineText(_gamePath, _parentExportType, ParentVisualLine, length);
- a.OnGamePathClicked += async (gamePath, parentExportType) =>
+ var a = new GamePathVisualLineText(_gamePath, ParentVisualLine, length);
+ a.OnGamePathClicked += async gamePath =>
{
var obj = gamePath.SubstringAfterLast('.');
var package = gamePath.SubstringBeforeLast('.');
@@ -87,7 +85,7 @@ public class GamePathVisualLineText : VisualLineText
}
await _threadWorkerView.Begin(cancellationToken =>
- _applicationView.CUE4Parse.ExtractAndScroll(cancellationToken, fullPath, obj, parentExportType));
+ _applicationView.CUE4Parse.ExtractAndScroll(cancellationToken, fullPath, obj));
};
return a;
}
diff --git a/FModel/Views/Resources/Controls/Aup/SourceEventArgs.cs b/FModel/Views/Resources/Controls/Aup/SourceEventArgs.cs
index 102546ea..964638b7 100644
--- a/FModel/Views/Resources/Controls/Aup/SourceEventArgs.cs
+++ b/FModel/Views/Resources/Controls/Aup/SourceEventArgs.cs
@@ -4,7 +4,8 @@ namespace FModel.Views.Resources.Controls.Aup;
public enum ESourceEventType
{
- Loading
+ Loading,
+ Clearing
}
public class SourceEventArgs : EventArgs
@@ -15,4 +16,4 @@ public class SourceEventArgs : EventArgs
{
Event = e;
}
-}
\ No newline at end of file
+}
diff --git a/FModel/Views/Resources/Controls/Aup/SpectrumAnalyzer.cs b/FModel/Views/Resources/Controls/Aup/SpectrumAnalyzer.cs
index b1f5a6ce..20898e57 100644
--- a/FModel/Views/Resources/Controls/Aup/SpectrumAnalyzer.cs
+++ b/FModel/Views/Resources/Controls/Aup/SpectrumAnalyzer.cs
@@ -121,7 +121,7 @@ public sealed class SpectrumAnalyzer : UserControl
private void OnSourceEvent(object sender, SourceEventArgs e)
{
- if (e.Event != ESourceEventType.Loading) return;
+ if (e is not { Event: ESourceEventType.Loading } g) return;
_spectrumProvider = Source.Spectrum;
UpdateFrequencyMapping();
}
diff --git a/FModel/Views/Resources/Controls/Aup/Timeclock.cs b/FModel/Views/Resources/Controls/Aup/Timeclock.cs
index eab86f96..681a5618 100644
--- a/FModel/Views/Resources/Controls/Aup/Timeclock.cs
+++ b/FModel/Views/Resources/Controls/Aup/Timeclock.cs
@@ -116,9 +116,9 @@ public sealed class Timeclock : UserControl
private void OnSourceEvent(object sender, SourceEventArgs e)
{
- if (Source == null) return;
+ if (e is not { Event: ESourceEventType.Loading } || Source == null) return;
Label = Source.PlayedFile.FileName;
- Dispatcher.BeginInvoke((Action) CalculateTime);
+ Dispatcher.BeginInvoke(CalculateTime);
}
private void OnSourcePropertyChangedEvent(object sender, SourcePropertyChangedEventArgs e)
diff --git a/FModel/Views/Resources/Controls/Aup/Timeline.cs b/FModel/Views/Resources/Controls/Aup/Timeline.cs
index 07500fe0..b77593fe 100644
--- a/FModel/Views/Resources/Controls/Aup/Timeline.cs
+++ b/FModel/Views/Resources/Controls/Aup/Timeline.cs
@@ -100,8 +100,8 @@ public sealed class Timeline : UserControl
private void OnSourceEvent(object sender, SourceEventArgs e)
{
- if (Source == null) return;
- Dispatcher.BeginInvoke((Action) UpdateTimeline);
+ if (e is not { Event: ESourceEventType.Loading } || Source == null) return;
+ Dispatcher.BeginInvoke(UpdateTimeline);
}
private void OnSourcePropertyChangedEvent(object sender, SourcePropertyChangedEventArgs e)
@@ -347,4 +347,4 @@ public sealed class Timeline : UserControl
}
}
}
-}
\ No newline at end of file
+}
diff --git a/FModel/Views/Resources/Controls/ContextMenus/FileContextMenu.xaml b/FModel/Views/Resources/Controls/ContextMenus/FileContextMenu.xaml
index 28196a50..afba4a08 100644
--- a/FModel/Views/Resources/Controls/ContextMenus/FileContextMenu.xaml
+++ b/FModel/Views/Resources/Controls/ContextMenus/FileContextMenu.xaml
@@ -110,146 +110,189 @@
-