diff --git a/CUE4Parse b/CUE4Parse index d2163de3..763aca66 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit d2163de354e97a0e62222221b96666a678255884 +Subproject commit 763aca666c1782ce51287100641bc31d9fce0d41 diff --git a/FModel/App.xaml.cs b/FModel/App.xaml.cs index 16ad94dc..b10a118f 100644 --- a/FModel/App.xaml.cs +++ b/FModel/App.xaml.cs @@ -44,9 +44,9 @@ protected override void OnStartup(StartupEventArgs e) } if (!Directory.Exists(UserSettings.Default.OutputDirectory)) - { UserSettings.Default.OutputDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Output"); - } + if (!Directory.Exists(UserSettings.Default.ModelDirectory)) + UserSettings.Default.ModelDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Saves"); Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FModel")); Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Backups")); diff --git a/FModel/Creator/Bases/FN/BaseCommunity.cs b/FModel/Creator/Bases/FN/BaseCommunity.cs index d0066e94..0ae9ceb3 100644 --- a/FModel/Creator/Bases/FN/BaseCommunity.cs +++ b/FModel/Creator/Bases/FN/BaseCommunity.cs @@ -102,10 +102,19 @@ private string GetCosmeticSeason(string seasonNumber, bool bShort) if (!bShort) return base.GetCosmeticSeason(seasonNumber); var s = seasonNumber["Cosmetics.Filter.Season.".Length..]; var number = int.Parse(s); - if (number == 10) - s = "X"; - return number > 10 ? $"C{number / 10 + 1} S{s[^1..]}" : $"C1 S{s}"; + switch (number) + { + case 10: + s = "X"; + break; + case > 18: + number += 2; + s = number.ToString(); + break; + } + + return $"C{number / 10 + 1} S{s[^1..]}"; } private new void DrawBackground(SKCanvas c) diff --git a/FModel/Creator/Bases/FN/BaseIcon.cs b/FModel/Creator/Bases/FN/BaseIcon.cs index a58a0aa0..61114b2b 100644 --- a/FModel/Creator/Bases/FN/BaseIcon.cs +++ b/FModel/Creator/Bases/FN/BaseIcon.cs @@ -199,8 +199,17 @@ protected string GetCosmeticSeason(string seasonNumber) { var s = seasonNumber["Cosmetics.Filter.Season.".Length..]; var number = int.Parse(s); - if (number == 10) - s = "X"; + + switch (number) + { + case 10: + s = "X"; + break; + case > 18: + number += 2; + s = number.ToString(); + break; + } var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}"); var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in {0}."); diff --git a/FModel/Creator/Bases/FN/BaseIconStats.cs b/FModel/Creator/Bases/FN/BaseIconStats.cs index 706d0dd0..9a2e1d1e 100644 --- a/FModel/Creator/Bases/FN/BaseIconStats.cs +++ b/FModel/Creator/Bases/FN/BaseIconStats.cs @@ -142,10 +142,10 @@ private bool TryGetCurveTableStat(FStructFallback property, out float statValue) if (property.TryGetValue(out FStructFallback curve, "Curve") && curve.TryGetValue(out FName rowName, "RowName") && curve.TryGetValue(out UCurveTable curveTable, "CurveTable") && - curveTable.TryGetCurveTableRow(rowName.Text, StringComparison.OrdinalIgnoreCase, out var rowValue) && - rowValue.TryGetValue(out FSimpleCurveKey[] keys, "Keys") && keys.Length > 0) + curveTable.TryFindCurve(rowName, out var rowValue) && + rowValue is FSimpleCurve s && s.Keys.Length > 0) { - statValue = keys[0].Value; + statValue = s.Keys[0].Value; return true; } @@ -274,6 +274,8 @@ public void Draw(SKCanvas c, SKColor sliderColor, int width, int height, ref flo c.DrawText(_value.ToString(), new SKPoint(width - 50, y + 10), _statPaint); if (_maxValue < 1 || !float.TryParse(_value.ToString(), out var floatValue)) return; + if (floatValue < 0) + floatValue = 0; var sliderWidth = (sliderRight - height * 2) * (floatValue / _maxValue); c.DrawRect(new SKRect(height * 2, y, Math.Min(height * 2 + sliderWidth, sliderRight), y + 5), _statPaint); } diff --git a/FModel/Creator/Bases/SOD2/BaseDecayIcon.cs b/FModel/Creator/Bases/SOD2/BaseDecayIcon.cs deleted file mode 100644 index 3dbe6ced..00000000 --- a/FModel/Creator/Bases/SOD2/BaseDecayIcon.cs +++ /dev/null @@ -1,98 +0,0 @@ -using CUE4Parse.UE4.Assets.Exports; -using CUE4Parse.UE4.Assets.Objects; -using CUE4Parse.UE4.Objects.Core.i18N; -using FModel.Creator.Bases.FN; -using SkiaSharp; - -namespace FModel.Creator.Bases.SOD2 -{ - public class BaseDecayIcon : BaseIcon - { - private int _maxStackCount; - private readonly SKBitmap _backgroundOverlay; - - public BaseDecayIcon(UObject uObject, EIconStyle style) : base(uObject, style) - { - Margin = 0; - Background = new[] {SKColor.Parse("000000"), SKColor.Parse("B24E18")}; - _backgroundOverlay = Utils.GetBitmap("StateOfDecay2/Content/Art/UI/settings/icon_sod_eagle_09_square.icon_sod_eagle_09_square").Resize(512); - } - - public override void ParseForInfo() - { - if (Object.TryGetValue(out FStructFallback stackingInfo, "StackingInfo") && - stackingInfo.TryGetValue(out int maxStackCount, "MaxStackCount")) - _maxStackCount = maxStackCount; - - if (Object.Class.SuperStruct != null && Utils.TryGetPackageIndexExport(Object.Class.SuperStruct, out UObject t)) - { - // TODO - } - - if (Object.TryGetValue(out FStructFallback displayInfo, "DisplayInfo")) - { - if (displayInfo.TryGetValue(out FText displayName, "DisplayName")) - DisplayName = displayName.Text; - if (displayInfo.TryGetValue(out FText displayDescription, "DisplayDescription")) - Description = displayDescription.Text; - - if (displayInfo.TryGetValue(out string iconFills, "IconFills")) - Preview = Utils.GetBitmap(iconFills); - } - } - - // TODO - // - // ExtraLargeBackpackBase - for extra large - // SmallBackpackBase - for small - // ExtraSmallBackpackBase - for extra small - // MediumBackpackBase - for medium - // LargeBackpackBase - for large - - public override SKImage Draw() - { - using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(ret); - - DrawBackground(c); - DrawPreview(c); - - return SKImage.FromBitmap(ret); - } - - private new void DrawPreview(SKCanvas c) - { - c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, Margin, Width - Margin, Height - Margin), new SKPaint - { - // BlendMode = SKBlendMode.SrcATop -- Need Asval's assistance. - }); - } - - private new void DrawBackground(SKCanvas c) - { - c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Height - Margin), - new SKPaint - { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, Height / 2), Width / 5 * 2, - new[] {Background[0], Background[1]}, - SKShaderTileMode.Clamp) - }); - - for (var i = 0; i < _backgroundOverlay.Width; i++) - for (var j = 0; j < _backgroundOverlay.Height; j++) - if (_backgroundOverlay.GetPixel(i, j) == SKColors.Black) - { - _backgroundOverlay.SetPixel(i, j, SKColors.Transparent); - } - - c.DrawBitmap(_backgroundOverlay, new SKRect(Margin, Margin, Width - Margin, Height - Margin), new SKPaint - { - IsAntialias = true, - ColorFilter = SKColorFilter.CreateBlendMode(SKColors.Black.WithAlpha(150), SKBlendMode.DstIn), - ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0)) - }); - c.DrawColor(SKColors.Black.WithAlpha(125), SKBlendMode.DstIn); - } - } -} \ No newline at end of file diff --git a/FModel/Creator/CreatorPackage.cs b/FModel/Creator/CreatorPackage.cs index 0145853d..1490362f 100644 --- a/FModel/Creator/CreatorPackage.cs +++ b/FModel/Creator/CreatorPackage.cs @@ -5,7 +5,6 @@ using FModel.Creator.Bases.BB; using FModel.Creator.Bases.FN; using FModel.Creator.Bases.SB; -using FModel.Creator.Bases.SOD2; namespace FModel.Creator { @@ -230,38 +229,6 @@ public bool TryConstructCreator(out UCreator creator) case "GLeagueDivision": creator = new BaseDivision(_object, EIconStyle.Default); return true; - // State of Decay 2 - case "CureItem": - case "AmmoItem": - case "Pro_Brake_C": - case "BackpackItem": - case "MagicAmmoItem": - case "ConsumableItem": - case "MeleeWeaponItem": - case "CloseCombatItem": - case "FacilityModItem": - case "RangedWeaponItem": - case "MiscellaneousItem": - case "RepairVehicleItem": - case "ResourceItemBase_C": - case "FuelResourceBase_C": - case "MedsResourceBase_C": - case "PartsResourceBase_C": - case "RangedWeaponModItem": - case "VehicleDeliveryItem": - case "ConsumableBase_BP_C": - case "SmallBackpackBase_C": - case "LargeBackpackBase_C": - case "MediumBackpackBase_C": - case "ConsumableMedsBase_BP_C": - case "MaterialsResourceBase_C": - case "ExtraLargeBackpackBase_C": - case "ExtraSmallBackpackBase_C": - case "ConsumableStimsBase_BP_C": - case "Consumable_TimedStatBuff_Base_BP_C": - case "Consumable_SuspendFatigue_Base_BP_C": - creator = new BaseDecayIcon(_object, EIconStyle.Default); - return true; default: creator = null; return false; diff --git a/FModel/Enums.cs b/FModel/Enums.cs index 2e63af22..943cd18b 100644 --- a/FModel/Enums.cs +++ b/FModel/Enums.cs @@ -20,6 +20,7 @@ public enum SettingsOut { Restart, ReloadLocres, + CheckForUpdates, Nothing } diff --git a/FModel/Extensions/EnumExtensions.cs b/FModel/Extensions/EnumExtensions.cs index f5242580..400d91b1 100644 --- a/FModel/Extensions/EnumExtensions.cs +++ b/FModel/Extensions/EnumExtensions.cs @@ -1,8 +1,8 @@ -using FModel.Properties; using System; using System.ComponentModel; using System.Resources; using System.Runtime.CompilerServices; +using FModel.Properties; namespace FModel.Extensions { @@ -47,12 +47,12 @@ public static string GetLocalizedCategory(this Enum value, ResourceManager resou } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T ToEnum(this string value, T defaultValue) + public static T ToEnum(this string value, T defaultValue) where T : struct { - if (!Enum.TryParse(typeof(T), value, true, out var ret)) + if (!Enum.TryParse(value, true, out T ret)) return defaultValue; - return (T) ret; + return ret; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -81,4 +81,4 @@ public static T Previous(this Enum value) return i == -1 ? (T) values.GetValue(values.Length - 1) : (T) values.GetValue(i); } } -} \ No newline at end of file +} diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj index 28e1b01b..12a0a90f 100644 --- a/FModel/FModel.csproj +++ b/FModel/FModel.csproj @@ -5,13 +5,13 @@ net6.0-windows true FModel.ico - 4.0.0 - 4.0.2.0 - 4.0.2.0 + 4.2.0 + 4.2.0.0 + 4.2.0.0 false true win-x64 - true + true true FModel.App @@ -110,12 +110,12 @@ - + - + diff --git a/FModel/MainWindow.xaml b/FModel/MainWindow.xaml index be4aa554..fa5b7b35 100644 --- a/FModel/MainWindow.xaml +++ b/FModel/MainWindow.xaml @@ -74,7 +74,7 @@ - + @@ -94,7 +94,7 @@ - + @@ -147,24 +147,15 @@ - - - @@ -266,7 +257,7 @@ - + @@ -301,7 +292,7 @@ - + @@ -352,7 +343,7 @@ Width="16" Height="16" HorizontalAlignment="Center" Margin="0 0 3.5 0" /> - + @@ -391,7 +382,7 @@ - + @@ -400,7 +391,7 @@ - + @@ -409,7 +400,7 @@ - + @@ -456,22 +447,22 @@ - + - + - + - + + HeaderStringFormat="{}{0} Packages"> @@ -528,7 +519,7 @@ - + @@ -582,7 +573,7 @@ - + @@ -590,7 +581,7 @@ - + @@ -606,7 +597,7 @@ - + @@ -614,7 +605,7 @@ - + @@ -650,7 +641,7 @@ - + @@ -802,19 +793,6 @@ - - - - - - - - - - - - - - diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index c0983618..77b0540f 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -11,7 +11,6 @@ using FModel.ViewModels; using FModel.Views; using FModel.Views.Resources.Controls; -using ICSharpCode.AvalonEdit.Editing; namespace FModel { @@ -27,18 +26,12 @@ public partial class MainWindow public MainWindow() { - CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoExportData", typeof(MainWindow), new InputGestureCollection - {new KeyGesture(UserSettings.Default.AutoExportData.Key, UserSettings.Default.AutoExportData.Modifiers)}), OnAutoTriggerExecuted)); CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoSaveProps", typeof(MainWindow), new InputGestureCollection {new KeyGesture(UserSettings.Default.AutoSaveProps.Key, UserSettings.Default.AutoSaveProps.Modifiers)}), OnAutoTriggerExecuted)); CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoSaveTextures", typeof(MainWindow), new InputGestureCollection {new KeyGesture(UserSettings.Default.AutoSaveTextures.Key, UserSettings.Default.AutoSaveTextures.Modifiers)}), OnAutoTriggerExecuted)); - CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoSaveAnimations", typeof(MainWindow), new InputGestureCollection - {new KeyGesture(UserSettings.Default.AutoSaveAnimations.Key, UserSettings.Default.AutoSaveAnimations.Modifiers)}), OnAutoTriggerExecuted)); CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoOpenSounds", typeof(MainWindow), new InputGestureCollection {new KeyGesture(UserSettings.Default.AutoOpenSounds.Key, UserSettings.Default.AutoOpenSounds.Modifiers)}), OnAutoTriggerExecuted)); - CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoOpenMeshes", typeof(MainWindow), new InputGestureCollection - {new KeyGesture(UserSettings.Default.AutoOpenMeshes.Key, UserSettings.Default.AutoOpenMeshes.Modifiers)}), OnAutoTriggerExecuted)); CommandBindings.Add(new CommandBinding(new RoutedCommand("ReloadMappings", typeof(MainWindow), new InputGestureCollection {new KeyGesture(Key.F12)}), OnMappingsReload)); CommandBindings.Add(new CommandBinding(ApplicationCommands.Find, (s, e) => OnOpenAvalonFinder())); @@ -91,7 +84,7 @@ private void OnGridSplitterDoubleClick(object sender, MouseButtonEventArgs e) private void OnWindowKeyDown(object sender, KeyEventArgs e) { - if (e.OriginalSource is TextArea or TextBox) + if (e.OriginalSource is TextBox) return; if (_threadWorkerView.CanBeCanceled && e.Key == Key.Escape) @@ -137,24 +130,15 @@ private void OnAutoTriggerExecuted(object sender, ExecutedRoutedEventArgs e) { switch ((e.Command as RoutedCommand)?.Name) { - case "AutoExportData": - UserSettings.Default.IsAutoExportData = !UserSettings.Default.IsAutoExportData; - break; case "AutoSaveProps": UserSettings.Default.IsAutoSaveProps = !UserSettings.Default.IsAutoSaveProps; break; case "AutoSaveTextures": UserSettings.Default.IsAutoSaveTextures = !UserSettings.Default.IsAutoSaveTextures; break; - case "AutoSaveAnimations": - UserSettings.Default.IsAutoSaveAnimations = !UserSettings.Default.IsAutoSaveAnimations; - break; case "AutoOpenSounds": UserSettings.Default.IsAutoOpenSounds = !UserSettings.Default.IsAutoOpenSounds; break; - case "AutoOpenMeshes": - UserSettings.Default.IsAutoOpenMeshes = !UserSettings.Default.IsAutoOpenMeshes; - break; } } diff --git a/FModel/Resources/approaching_storm_cubemap.dds b/FModel/Resources/approaching_storm_cubemap.dds index c4322d7d..e598942c 100644 Binary files a/FModel/Resources/approaching_storm_cubemap.dds and b/FModel/Resources/approaching_storm_cubemap.dds differ diff --git a/FModel/Settings/UserSettings.cs b/FModel/Settings/UserSettings.cs index f7d686f5..1e6f80be 100644 --- a/FModel/Settings/UserSettings.cs +++ b/FModel/Settings/UserSettings.cs @@ -51,6 +51,13 @@ public string OutputDirectory set => SetProperty(ref _outputDirectory, value); } + private string _modelDirectory; + public string ModelDirectory + { + get => _modelDirectory; + set => SetProperty(ref _modelDirectory, value); + } + private string _gameDirectory; public string GameDirectory { @@ -58,6 +65,20 @@ public string GameDirectory set => SetProperty(ref _gameDirectory, value); } + private bool _overwriteMapping; + public bool OverwriteMapping + { + get => _overwriteMapping; + set => SetProperty(ref _overwriteMapping, value); + } + + private string _mappingFilePath; + public string MappingFilePath + { + get => _mappingFilePath; + set => SetProperty(ref _mappingFilePath, value); + } + private int _lastOpenedSettingTab; public int LastOpenedSettingTab { @@ -65,13 +86,6 @@ public int LastOpenedSettingTab set => SetProperty(ref _lastOpenedSettingTab, value); } - private bool _isAutoExportData; - public bool IsAutoExportData - { - get => _isAutoExportData; - set => SetProperty(ref _isAutoExportData, value); - } - private bool _isAutoSaveProps; public bool IsAutoSaveProps { @@ -86,13 +100,6 @@ public bool IsAutoSaveTextures set => SetProperty(ref _isAutoSaveTextures, value); } - private bool _isAutoSaveAnimations; - public bool IsAutoSaveAnimations - { - get => _isAutoSaveAnimations; - set => SetProperty(ref _isAutoSaveAnimations, value); - } - private bool _isAutoOpenSounds = true; public bool IsAutoOpenSounds { @@ -100,13 +107,6 @@ public bool IsAutoOpenSounds set => SetProperty(ref _isAutoOpenSounds, value); } - private bool _isAutoOpenMeshes = true; - public bool IsAutoOpenMeshes - { - get => _isAutoOpenMeshes; - set => SetProperty(ref _isAutoOpenMeshes, value); - } - private bool _isLoggerExpanded = true; public bool IsLoggerExpanded { @@ -235,7 +235,7 @@ public IDictionary Presets private IDictionary _overridedGame = new Dictionary { {FGame.Unknown, EGame.GAME_UE4_LATEST}, - {FGame.FortniteGame, EGame.GAME_UE4_LATEST}, + {FGame.FortniteGame, EGame.GAME_UE5_LATEST}, {FGame.ShooterGame, EGame.GAME_Valorant}, {FGame.DeadByDaylight, EGame.GAME_UE4_LATEST}, {FGame.OakGame, EGame.GAME_Borderlands3}, @@ -259,33 +259,6 @@ public IDictionary OverridedGame set => SetProperty(ref _overridedGame, value); } - private IDictionary _overridedUEVersion = new Dictionary - { - {FGame.Unknown, UE4Version.VER_UE4_DETERMINE_BY_GAME}, - {FGame.FortniteGame, UE4Version.VER_UE4_DETERMINE_BY_GAME}, - {FGame.ShooterGame, UE4Version.VER_UE4_DETERMINE_BY_GAME}, - {FGame.DeadByDaylight, UE4Version.VER_UE4_DETERMINE_BY_GAME}, - {FGame.OakGame, UE4Version.VER_UE4_DETERMINE_BY_GAME}, - {FGame.Dungeons, UE4Version.VER_UE4_DETERMINE_BY_GAME}, - {FGame.WorldExplorers, UE4Version.VER_UE4_DETERMINE_BY_GAME}, - {FGame.g3, UE4Version.VER_UE4_DETERMINE_BY_GAME}, - {FGame.StateOfDecay2, UE4Version.VER_UE4_DETERMINE_BY_GAME}, - {FGame.Prospect, UE4Version.VER_UE4_DETERMINE_BY_GAME}, - {FGame.Indiana, UE4Version.VER_UE4_DETERMINE_BY_GAME}, - {FGame.RogueCompany, UE4Version.VER_UE4_DETERMINE_BY_GAME}, - {FGame.SwGame, UE4Version.VER_UE4_DETERMINE_BY_GAME}, - {FGame.Platform, UE4Version.VER_UE4_DETERMINE_BY_GAME}, - {FGame.BendGame, UE4Version.VER_UE4_DETERMINE_BY_GAME}, - {FGame.TslGame, UE4Version.VER_UE4_DETERMINE_BY_GAME}, - {FGame.PortalWars, UE4Version.VER_UE4_DETERMINE_BY_GAME}, - {FGame.Gameface, UE4Version.VER_UE4_DETERMINE_BY_GAME} - }; - public IDictionary OverridedUEVersion - { - get => _overridedUEVersion; - set => SetProperty(ref _overridedUEVersion, value); - } - private IDictionary> _overridedCustomVersions = new Dictionary> { {FGame.Unknown, null}, @@ -364,7 +337,15 @@ public IDictionary> OverridedOptions new("Weapon Renders", "ShooterGame/Content/UI/Screens/OutOfGame/MainMenu/Collection/Assets/Large/") } }, - {FGame.DeadByDaylight, new List()}, + { + FGame.DeadByDaylight, new List + { + new("Audio", "DeadByDaylight/Content/WwiseAudio/Windows/"), + new("Characters", "DeadByDaylight/Content/Characters/"), + new("Icons", "DeadByDaylight/Content/UI/UMGAssets/Icons/"), + new("Strings", "DeadByDaylight/Content/Localization/") + } + }, {FGame.OakGame, new List()}, { FGame.Dungeons, new List @@ -462,48 +443,27 @@ public Hotkey AssetRemoveTab set => SetProperty(ref _assetRemoveTab, value); } - private Hotkey _autoExportData = new(Key.F1); - public Hotkey AutoExportData - { - get => _autoExportData; - set => SetProperty(ref _autoExportData, value); - } - - private Hotkey _autoSaveProps = new(Key.F2); + private Hotkey _autoSaveProps = new(Key.F1); public Hotkey AutoSaveProps { get => _autoSaveProps; set => SetProperty(ref _autoSaveProps, value); } - private Hotkey _autoSaveTextures = new(Key.F3); + private Hotkey _autoSaveTextures = new(Key.F2); public Hotkey AutoSaveTextures { get => _autoSaveTextures; set => SetProperty(ref _autoSaveTextures, value); } - private Hotkey _autoSaveAnimations = new(Key.F4); - public Hotkey AutoSaveAnimations - { - get => _autoSaveAnimations; - set => SetProperty(ref _autoSaveAnimations, value); - } - - private Hotkey _autoOpenSounds = new(Key.F5); + private Hotkey _autoOpenSounds = new(Key.F3); public Hotkey AutoOpenSounds { get => _autoOpenSounds; set => SetProperty(ref _autoOpenSounds, value); } - private Hotkey _autoOpenMeshes = new(Key.F6); - public Hotkey AutoOpenMeshes - { - get => _autoOpenMeshes; - set => SetProperty(ref _autoOpenMeshes, value); - } - private Hotkey _addAudio = new(Key.N, ModifierKeys.Control); public Hotkey AddAudio { @@ -546,6 +506,85 @@ public ELodFormat LodExportFormat set => SetProperty(ref _lodExportFormat, value); } + private bool _previewStaticMeshes = true; + public bool PreviewStaticMeshes + { + get => _previewStaticMeshes; + set + { + SetProperty(ref _previewStaticMeshes, value); + if (_previewStaticMeshes && SaveStaticMeshes) + SaveStaticMeshes = false; + } + } + + private bool _previewSkeletalMeshes = true; + public bool PreviewSkeletalMeshes + { + get => _previewSkeletalMeshes; + set + { + SetProperty(ref _previewSkeletalMeshes, value); + if (_previewSkeletalMeshes && SaveSkeletalMeshes) + SaveSkeletalMeshes = false; + } + } + + private bool _previewMaterials = true; + public bool PreviewMaterials + { + get => _previewMaterials; + set + { + SetProperty(ref _previewMaterials, value); + if (_previewMaterials && SaveMaterials) + SaveMaterials = false; + } + } + + private bool _saveStaticMeshes; + public bool SaveStaticMeshes + { + get => _saveStaticMeshes; + set + { + SetProperty(ref _saveStaticMeshes, value); + if (_saveStaticMeshes && PreviewStaticMeshes) + PreviewStaticMeshes = false; + } + } + + private bool _saveSkeletalMeshes; + public bool SaveSkeletalMeshes + { + get => _saveSkeletalMeshes; + set + { + SetProperty(ref _saveSkeletalMeshes, value); + if (_saveSkeletalMeshes && PreviewSkeletalMeshes) + PreviewSkeletalMeshes = false; + } + } + + private bool _saveMaterials; + public bool SaveMaterials + { + get => _saveMaterials; + set + { + SetProperty(ref _saveMaterials, value); + if (_saveMaterials && PreviewMaterials) + PreviewMaterials = false; + } + } + + private bool _saveAnimations; + public bool SaveAnimations + { + get => _saveAnimations; + set => SetProperty(ref _saveAnimations, value); + } + private bool _saveSkeletonAsMesh; public bool SaveSkeletonAsMesh { diff --git a/FModel/ViewModels/AssetsFolderViewModel.cs b/FModel/ViewModels/AssetsFolderViewModel.cs index 76ece9d0..763759cc 100644 --- a/FModel/ViewModels/AssetsFolderViewModel.cs +++ b/FModel/ViewModels/AssetsFolderViewModel.cs @@ -49,8 +49,8 @@ public string MountPoint private set => SetProperty(ref _mountPoint, value); } - private UE4Version _version; - public UE4Version Version + private int _version; + public int Version { get => _version; private set => SetProperty(ref _version, value); @@ -61,7 +61,7 @@ public UE4Version Version public RangeObservableCollection Folders { get; } public ICollectionView FoldersView { get; } - public TreeItem(string header, string package, string mountPoint, UE4Version version, string pathHere) + public TreeItem(string header, string package, string mountPoint, int version, string pathHere) { Header = header; Package = package; @@ -129,7 +129,7 @@ static TreeItem FindByHeaderOrNull(IReadOnlyList list, string header) if (lastNode == null) { var nodePath = builder.ToString(); - lastNode = new TreeItem(folder, item.Package, entry.Vfs.MountPoint, entry.Vfs.Ver, nodePath[..^1]); + lastNode = new TreeItem(folder, item.Package, entry.Vfs.MountPoint, entry.Vfs.Ver.Value, nodePath[..^1]); lastNode.Folders.SetSuppressionState(true); lastNode.AssetsList.Assets.SetSuppressionState(true); parentNode.Add(lastNode); @@ -168,4 +168,4 @@ static void InvokeOnCollectionChanged(TreeItem item) }); } } -} \ No newline at end of file +} diff --git a/FModel/ViewModels/BackupManagerViewModel.cs b/FModel/ViewModels/BackupManagerViewModel.cs index 8e4b3693..b7951379 100644 --- a/FModel/ViewModels/BackupManagerViewModel.cs +++ b/FModel/ViewModels/BackupManagerViewModel.cs @@ -62,7 +62,7 @@ public async Task CreateBackup() await _threadWorkerView.Begin(_ => { var backupFolder = Path.Combine(UserSettings.Default.OutputDirectory, "Backups"); - var fileName = $"{_gameName}_{DateTime.Now:MMddyyyy}.fbkp"; + var fileName = $"{_gameName}_{DateTime.Now:MM'_'dd'_'yyyy}.fbkp"; var fullPath = Path.Combine(backupFolder, fileName); using var fileStream = new FileStream(fullPath, FileMode.Create); @@ -114,4 +114,4 @@ private void SaveCheck(string fullPath, string fileName, string type1, string ty } } } -} \ No newline at end of file +} diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index dea08f3b..5c56b30b 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -59,11 +59,11 @@ public FGame Game set => SetProperty(ref _game, value); } - private bool _modelIsSwappingMaterial; - public bool ModelIsSwappingMaterial + private bool _modelIsOverwritingMaterial; + public bool ModelIsOverwritingMaterial { - get => _modelIsSwappingMaterial; - set => SetProperty(ref _modelIsSwappingMaterial, value); + get => _modelIsOverwritingMaterial; + set => SetProperty(ref _modelIsOverwritingMaterial, value); } public AbstractVfsFileProvider Provider { get; } @@ -85,9 +85,8 @@ public CUE4ParseViewModel(string gameDirectory) Provider = new StreamedFileProvider("FortniteLive", true, new VersionContainer( UserSettings.Default.OverridedGame[Game], - UserSettings.Default.OverridedUEVersion[Game], - UserSettings.Default.OverridedCustomVersions[Game], - UserSettings.Default.OverridedOptions[Game])); + customVersions: UserSettings.Default.OverridedCustomVersions[Game], + optionOverrides: UserSettings.Default.OverridedOptions[Game])); break; } case Constants._VAL_LIVE_TRIGGER: @@ -96,17 +95,16 @@ public CUE4ParseViewModel(string gameDirectory) Provider = new StreamedFileProvider("ValorantLive", true, new VersionContainer( UserSettings.Default.OverridedGame[Game], - UserSettings.Default.OverridedUEVersion[Game], - UserSettings.Default.OverridedCustomVersions[Game], - UserSettings.Default.OverridedOptions[Game])); + customVersions: UserSettings.Default.OverridedCustomVersions[Game], + optionOverrides: UserSettings.Default.OverridedOptions[Game])); break; } default: { Game = gameDirectory.SubstringBeforeLast("\\Content").SubstringAfterLast("\\").ToEnum(FGame.Unknown); - var versions = new VersionContainer( - UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedUEVersion[Game], - UserSettings.Default.OverridedCustomVersions[Game], UserSettings.Default.OverridedOptions[Game]); + var versions = new VersionContainer(UserSettings.Default.OverridedGame[Game], + customVersions: UserSettings.Default.OverridedCustomVersions[Game], + optionOverrides: UserSettings.Default.OverridedOptions[Game]); if (Game == FGame.StateOfDecay2) Provider = new DefaultFileProvider(new DirectoryInfo(gameDirectory), new List @@ -299,35 +297,44 @@ public async Task InitBenMappings() { await _threadWorkerView.Begin(cancellationToken => { - var mappingsFolder = Path.Combine(UserSettings.Default.OutputDirectory, ".data"); - var mappings = _apiEndpointView.BenbotApi.GetMappings(cancellationToken); - if (mappings is {Length: > 0}) + if (UserSettings.Default.OverwriteMapping && File.Exists(UserSettings.Default.MappingFilePath)) { - foreach (var mapping in mappings) + Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(UserSettings.Default.MappingFilePath); + FLogger.AppendInformation(); + FLogger.AppendText($"Mappings pulled from '{UserSettings.Default.MappingFilePath.SubstringAfterLast("\\")}'", Constants.WHITE, true); + } + else + { + var mappingsFolder = Path.Combine(UserSettings.Default.OutputDirectory, ".data"); + var mappings = _apiEndpointView.BenbotApi.GetMappings(cancellationToken); + if (mappings is {Length: > 0}) { - if (mapping.Meta.CompressionMethod != "Oodle") continue; - - var mappingPath = Path.Combine(mappingsFolder, mapping.FileName); - if (!File.Exists(mappingPath)) + foreach (var mapping in mappings) { - _apiEndpointView.BenbotApi.DownloadFile(mapping.Url, mappingPath); - } + if (mapping.Meta.CompressionMethod != "Oodle") continue; - Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(mappingPath); - FLogger.AppendInformation(); - FLogger.AppendText($"Mappings pulled from '{mapping.FileName}'", Constants.WHITE, true); - break; + var mappingPath = Path.Combine(mappingsFolder, mapping.FileName); + if (!File.Exists(mappingPath)) + { + _apiEndpointView.BenbotApi.DownloadFile(mapping.Url, mappingPath); + } + + Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(mappingPath); + FLogger.AppendInformation(); + FLogger.AppendText($"Mappings pulled from '{mapping.FileName}'", Constants.WHITE, true); + break; + } } - } - else - { - var latestUsmaps = new DirectoryInfo(mappingsFolder).GetFiles("*_oo.usmap"); - if (Provider.MappingsContainer != null || latestUsmaps.Length <= 0) return; + else + { + var latestUsmaps = new DirectoryInfo(mappingsFolder).GetFiles("*_oo.usmap"); + if (Provider.MappingsContainer != null || latestUsmaps.Length <= 0) return; - var latestUsmapInfo = latestUsmaps.OrderBy(f => f.LastWriteTime).Last(); - Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(latestUsmapInfo.FullName); - FLogger.AppendWarning(); - FLogger.AppendText($"Mappings pulled from '{latestUsmapInfo.Name}'", Constants.WHITE, true); + var latestUsmapInfo = latestUsmaps.OrderBy(f => f.LastWriteTime).Last(); + Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(latestUsmapInfo.FullName); + FLogger.AppendWarning(); + FLogger.AppendText($"Mappings pulled from '{latestUsmapInfo.Name}'", Constants.WHITE, true); + } } }); } @@ -365,9 +372,7 @@ await _threadWorkerView.Begin(cancellationToken => /// Functions only when LoadLocalizedResources is used prior to this (Asval: Why?). private async Task LoadHotfixedLocalizedResources() { - if (Game != FGame.FortniteGame) return; - - if (HotfixedResourcesDone) return; + if (Game != FGame.FortniteGame || HotfixedResourcesDone) return; await _threadWorkerView.Begin(cancellationToken => { var hotfixes = ApplicationService.ApiEndpointView.BenbotApi.GetHotfixes(cancellationToken, Provider.GetLanguageCode(UserSettings.Default.AssetLanguage)); @@ -395,7 +400,7 @@ public async Task LoadVirtualPaths() if (VirtualPathCount > 0) return; await _threadWorkerView.Begin(cancellationToken => { - VirtualPathCount = Provider.LoadVirtualPaths(UserSettings.Default.OverridedUEVersion[Game], cancellationToken); + VirtualPathCount = Provider.LoadVirtualPaths(UserSettings.Default.OverridedGame[Game].GetVersion(), cancellationToken); if (VirtualPathCount > 0) { FLogger.AppendInformation(); @@ -603,13 +608,10 @@ public void Extract(string fullPath, bool addNewTab = false, bool bulkSave = fal break; } case "ufont": - FLogger.AppendWarning(); - FLogger.AppendText($"Export '{fileName}' and change its extension if you want it to be an installable font file", Constants.WHITE, true); - break; case "otf": case "ttf": FLogger.AppendWarning(); - FLogger.AppendText($"Export '{fileName}' if you want it to be an installable font file", Constants.WHITE, true); + FLogger.AppendText($"Export '{fileName}' raw data and change its extension if you want it to be an installable font file", Constants.WHITE, true); break; case "ushaderbytecode": case "ushadercode": @@ -625,13 +627,10 @@ public void Extract(string fullPath, bool addNewTab = false, bool bulkSave = fal default: { FLogger.AppendWarning(); - FLogger.AppendText($"The file '{fileName}' is of an unknown type.", Constants.WHITE, true); + FLogger.AppendText($"The package '{fileName}' is of an unknown type.", Constants.WHITE, true); break; } } - - if (UserSettings.Default.IsAutoExportData) - ExportData(fullPath); } public void ExtractAndScroll(string fullPath, string objectName) @@ -646,9 +645,6 @@ public void ExtractAndScroll(string fullPath, string objectName) if (!exports.Any(CheckExport)) TabControl.SelectedTab.Image = null; - - if (UserSettings.Default.IsAutoExportData) - ExportData(fullPath); } private bool CheckExport(UObject export) // return true once you wanna stop searching for exports @@ -672,9 +668,9 @@ public void ExtractAndScroll(string fullPath, string objectName) SaveAndPlaySound(Path.Combine(TabControl.SelectedTab.Directory, TabControl.SelectedTab.Header.SubstringBeforeLast('.')).Replace('\\', '/'), audioFormat, data); return false; } - case UStaticMesh when UserSettings.Default.IsAutoOpenMeshes: - case USkeletalMesh when UserSettings.Default.IsAutoOpenMeshes: - case UMaterialInstance when UserSettings.Default.IsAutoOpenMeshes && !ModelIsSwappingMaterial && + case UStaticMesh when UserSettings.Default.PreviewStaticMeshes: + case USkeletalMesh when UserSettings.Default.PreviewSkeletalMeshes: + case UMaterialInstance when UserSettings.Default.PreviewMaterials && !ModelIsOverwritingMaterial && !(Game == FGame.FortniteGame && export.Owner != null && (export.Owner.Name.EndsWith($"/MI_OfferImages/{export.Name}", StringComparison.OrdinalIgnoreCase) || export.Owner.Name.EndsWith($"/RenderSwitch_Materials/{export.Name}", StringComparison.OrdinalIgnoreCase) || export.Owner.Name.EndsWith($"/MI_BPTile/{export.Name}", StringComparison.OrdinalIgnoreCase))): @@ -686,17 +682,20 @@ public void ExtractAndScroll(string fullPath, string objectName) }); return true; } - case UMaterialInstance m when ModelIsSwappingMaterial: + case UMaterialInstance m when ModelIsOverwritingMaterial: { Application.Current.Dispatcher.InvokeAsync(() => { var modelViewer = Helper.GetWindow("Model Viewer", () => new ModelViewer().Show()); - modelViewer.Swap(m); + modelViewer.Overwrite(m); }); return true; } + case UStaticMesh when UserSettings.Default.SaveStaticMeshes: + case USkeletalMesh when UserSettings.Default.SaveSkeletalMeshes: + case UMaterialInstance when UserSettings.Default.SaveMaterials: case USkeleton when UserSettings.Default.SaveSkeletonAsMesh: - case UAnimSequence when UserSettings.Default.IsAutoSaveAnimations: + case UAnimSequence when UserSettings.Default.SaveAnimations: { SaveExport(export); return true; @@ -744,7 +743,7 @@ private void SaveAndPlaySound(string fullPath, string ext, byte[] data) private void SaveExport(UObject export) { var toSave = new Exporter(export, UserSettings.Default.TextureExportFormat, UserSettings.Default.LodExportFormat, UserSettings.Default.MeshExportFormat); - var toSaveDirectory = new DirectoryInfo(Path.Combine(UserSettings.Default.OutputDirectory, "Saves")); + var toSaveDirectory = new DirectoryInfo(UserSettings.Default.ModelDirectory); if (toSave.TryWriteToDir(toSaveDirectory, out var savedFileName)) { Log.Information("Successfully saved {FileName}", savedFileName); diff --git a/FModel/ViewModels/Commands/LoadCommand.cs b/FModel/ViewModels/Commands/LoadCommand.cs index 6577c37e..c9c85f3b 100644 --- a/FModel/ViewModels/Commands/LoadCommand.cs +++ b/FModel/ViewModels/Commands/LoadCommand.cs @@ -42,7 +42,7 @@ public override async void Execute(LoadingModesViewModel contextViewModel, objec if (_applicationView.CUE4Parse.Provider.Files.Count <= 0) { FLogger.AppendError(); - FLogger.AppendText("An encrypted file has been found. In order to decrypt it, please specify a working AES encryption key", Constants.WHITE, true); + FLogger.AppendText("An encrypted archive has been found. In order to decrypt it, please specify a working AES encryption key", Constants.WHITE, true); return; } @@ -50,7 +50,7 @@ public override async void Execute(LoadingModesViewModel contextViewModel, objec _applicationView.CUE4Parse.Provider.MappingsContainer == null) { FLogger.AppendError(); - FLogger.AppendText("Mappings could not get pulled, extracting assets might not work properly. If so, press F12 or please restart.", Constants.WHITE, true); + FLogger.AppendText("Mappings could not get pulled, extracting packages might not work properly. If so, press F12 or please restart.", Constants.WHITE, true); } #if DEBUG @@ -154,7 +154,7 @@ private async Task FilterNewOrModifiedFilesToDisplay(CancellationToken cancellat if (!(bool) openFileDialog.ShowDialog()) return; FLogger.AppendInformation(); - FLogger.AppendText($"Backup file older than current game version is '{openFileDialog.FileName.SubstringAfterLast("\\")}'", Constants.WHITE, true); + FLogger.AppendText($"Backup file older than current game is '{openFileDialog.FileName.SubstringAfterLast("\\")}'", Constants.WHITE, true); await using var fileStream = new FileStream(openFileDialog.FileName, FileMode.Open); await using var memoryStream = new MemoryStream(); diff --git a/FModel/ViewModels/Commands/MenuCommand.cs b/FModel/ViewModels/Commands/MenuCommand.cs index 34609b57..d0778b7f 100644 --- a/FModel/ViewModels/Commands/MenuCommand.cs +++ b/FModel/ViewModels/Commands/MenuCommand.cs @@ -47,6 +47,10 @@ public override async void Execute(ApplicationViewModel contextViewModel, object case "Settings": Helper.OpenWindow("Settings", () => new SettingsView().Show()); break; + case "ModelSettings": + UserSettings.Default.LastOpenedSettingTab = contextViewModel.CUE4Parse.Game == FGame.FortniteGame ? 2 : 1; + Helper.OpenWindow("Settings", () => new SettingsView().Show()); + break; case "Help_About": Helper.OpenWindow("About", () => new About().Show()); break; @@ -101,9 +105,9 @@ private void LoopFolders(CancellationToken cancellationToken, TreeItem parent, b parent.IsExpanded = isExpanded; Thread.Sleep(10); } - + cancellationToken.ThrowIfCancellationRequested(); foreach (var f in parent.Folders) LoopFolders(cancellationToken, f, isExpanded); } } -} \ No newline at end of file +} diff --git a/FModel/ViewModels/GameSelectorViewModel.cs b/FModel/ViewModels/GameSelectorViewModel.cs index 6c61713d..fbf968df 100644 --- a/FModel/ViewModels/GameSelectorViewModel.cs +++ b/FModel/ViewModels/GameSelectorViewModel.cs @@ -66,6 +66,7 @@ private IEnumerable EnumerateDetectedGames() yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks"); yield return new DetectedGame { GameName = "Valorant [LIVE]", GameDirectory = Constants._VAL_LIVE_TRIGGER }; yield return GetMojangGame("MinecraftDungeons", "\\dungeons\\dungeons\\Dungeons\\Content\\Paks"); + yield return GetSteamGame(381210, "\\DeadByDaylight\\Content\\Paks"); // Dead By Daylight yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks"); // PUBG yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks"); // Splitgate yield return GetRockstarGamesGame("GTA III - Definitive Edition", "\\Gameface\\Content\\Paks"); @@ -287,6 +288,7 @@ private static List GetSteamLibs() var libraries = new List { steamPath }; var listFile = Path.Combine(steamPath, @"steamapps\libraryfolders.vdf"); + if (!File.Exists(listFile)) return new List(); var lines = File.ReadAllLines(listFile); foreach (var line in lines) { diff --git a/FModel/ViewModels/LoadingModesViewModel.cs b/FModel/ViewModels/LoadingModesViewModel.cs index c06619dd..b31cf110 100644 --- a/FModel/ViewModels/LoadingModesViewModel.cs +++ b/FModel/ViewModels/LoadingModesViewModel.cs @@ -1,10 +1,8 @@ -using FModel.Framework; -using FModel.ViewModels.Commands; using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; -using FModel.Settings; +using FModel.Framework; +using FModel.ViewModels.Commands; namespace FModel.ViewModels { @@ -20,6 +18,6 @@ public LoadingModesViewModel() Modes = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateLoadingModes())); } - private IEnumerable EnumerateLoadingModes() => Enum.GetValues(UserSettings.Default.LoadingMode.GetType()).Cast(); + private IEnumerable EnumerateLoadingModes() => Enum.GetValues(); } -} \ No newline at end of file +} diff --git a/FModel/ViewModels/MapViewerViewModel.cs b/FModel/ViewModels/MapViewerViewModel.cs index 556650c3..9153cd7b 100644 --- a/FModel/ViewModels/MapViewerViewModel.cs +++ b/FModel/ViewModels/MapViewerViewModel.cs @@ -10,6 +10,7 @@ using CUE4Parse.UE4.Assets.Objects; using CUE4Parse.UE4.Objects.Core.i18N; using CUE4Parse.UE4.Objects.Core.Math; +using CUE4Parse.UE4.Objects.GameplayTags; using CUE4Parse.UE4.Objects.UObject; using FModel.Creator; using FModel.Extensions; @@ -88,18 +89,11 @@ public bool BrVendingMachines set => SetProperty(ref _brVendingMachines, value, "ApolloGameplay_VendingMachines"); } - private bool _brFireflies; - public bool BrFireflies + private bool _brBountyBoards; + public bool BrBountyBoards { - get => _brFireflies; - set => SetProperty(ref _brFireflies, value, "ApolloGameplay_Fireflies"); - } - - private bool _brCubeMovements; - public bool BrCubeMovements - { - get => _brCubeMovements; - set => SetProperty(ref _brCubeMovements, value, "ApolloGameplay_CubeMovements"); + get => _brBountyBoards; + set => SetProperty(ref _brBountyBoards, value, "ApolloGameplay_BountyBoards"); } private bool _prLandmarks; @@ -273,11 +267,8 @@ private async void GenericToggle(string key, bool enabled) case "ApolloGameplay_VendingMachines": await LoadBrVendingMachines(); break; - case "ApolloGameplay_Fireflies": - await LoadFireflies(); - break; - case "ApolloGameplay_CubeMovements": - await LoadCubeMovements(); + case "ApolloGameplay_BountyBoards": + await LoadBountyBoards(); break; case "PapayaGameplay_CannonballGame": await LoadCannonballGame(); @@ -384,12 +375,9 @@ private async Task LoadBrMiniMap() await _threadWorkerView.Begin(_ => { if (!Utils.TryLoadObject("FortniteGame/Content/UI/IngameMap/UIMapManagerBR.Default__UIMapManagerBR_C", out UObject mapManager) || - !mapManager.TryGetValue(out UObject mapMaterial, "MapMaterial") || - !mapMaterial.TryGetValue(out FStructFallback cachedExpressionData, "CachedExpressionData") || - !cachedExpressionData.TryGetValue(out FStructFallback parameters, "Parameters") || - !parameters.TryGetValue(out UTexture2D[] textureValues, "TextureValues")) return; + !mapManager.TryGetValue(out UMaterial mapMaterial, "MapMaterial") || mapMaterial.ReferencedTextures.Count < 1) return; - _bitmaps[0][_FIRST_BITMAP] = new MapLayer{Layer = Utils.GetBitmap(textureValues[0]), IsEnabled = true}; + _bitmaps[0][_FIRST_BITMAP] = new MapLayer{Layer = Utils.GetBitmap(mapMaterial.ReferencedTextures[0] as UTexture2D), IsEnabled = true}; _brMiniMapImage = GetImageSource(_bitmaps[0][_FIRST_BITMAP].Layer); }); } @@ -402,8 +390,7 @@ private async Task LoadPrMiniMap() await _threadWorkerView.Begin(_ => { if (!Utils.TryLoadObject("FortniteGame/Content/UI/IngameMap/UIMapManagerPapaya.Default__UIMapManagerPapaya_C", out UObject mapManager) || - !mapManager.TryGetValue(out UMaterial mapMaterial, "MapMaterial") || - mapMaterial.ReferencedTextures.Count < 1) return; + !mapManager.TryGetValue(out UMaterial mapMaterial, "MapMaterial") || mapMaterial.ReferencedTextures.Count < 1) return; _bitmaps[1][_FIRST_BITMAP] = new MapLayer{Layer = Utils.GetBitmap(mapMaterial.ReferencedTextures[0] as UTexture2D), IsEnabled = true}; _prMiniMapImage = GetImageSource(_bitmaps[1][_FIRST_BITMAP].Layer); @@ -474,12 +461,16 @@ await _threadWorkerView.Begin(_ => var patrolsPathBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); using var c = new SKCanvas(patrolsPathBitmap); - var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Apollo_Terrain_NPCLibrary_Overlay_S18"); + var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Artemis_Overlay_S19_NPCLibrary"); foreach (var export in exports) { if (!export.ExportType.Equals("FortAthenaPatrolPath", StringComparison.OrdinalIgnoreCase) || !export.TryGetValue(out FPackageIndex[] patrolPoints, "PatrolPoints")) continue; + var displayName = export.Name["FortAthenaPatrolPath_".Length..]; + if (export.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags") && gameplayTags.GameplayTags.Length > 0) + displayName = gameplayTags.GameplayTags[0].Text["Athena.AI.SpawnLocation.Tandem.".Length..]; + if (!Utils.TryGetPackageIndexExport(patrolPoints[0], out UObject uObject) || !uObject.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || !Utils.TryGetPackageIndexExport(rootComponent, out uObject) || @@ -487,7 +478,6 @@ await _threadWorkerView.Begin(_ => var path = new SKPath(); var vector = GetMapPosition(relativeLocation, _brRadius); - var displayName = export.Name["FortAthenaPatrolPath_Tandem_S18_".Length..]; path.MoveTo(vector.X, vector.Y); for (var i = 1; i < patrolPoints.Length; i++) @@ -737,7 +727,7 @@ await _threadWorkerView.Begin(_ => var upgradeBenchesBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); using var c = new SKCanvas(upgradeBenchesBitmap); - var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Apollo_Terrain_NPCLibrary_Stations_UpgradeBenches"); + var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Artemis_Overlay_S19_UpgradeBenches"); foreach (var export in exports) { if (!export.ExportType.Equals("B_Athena_Spawner_UpgradeStation_C", StringComparison.OrdinalIgnoreCase)) continue; @@ -793,7 +783,7 @@ await _threadWorkerView.Begin(_ => var vendingMachinesBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); using var c = new SKCanvas(vendingMachinesBitmap); - var exports = Utils.LoadExports("FortniteGame/Content/Athena/Apollo/Maps/Special/ItemCollections/Apollo_Item_VendingMachines"); + var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Artemis_Overlay_S19_ServiceStations"); foreach (var export in exports) { if (!export.ExportType.Equals("B_Athena_Spawner_VendingMachine_MendingOnly_C", StringComparison.OrdinalIgnoreCase) && @@ -814,19 +804,19 @@ await _threadWorkerView.Begin(_ => }); } - private async Task LoadFireflies() + private async Task LoadBountyBoards() { await _threadWorkerView.Begin(_ => { _fillPaint.StrokeWidth = 5; - var firefliesBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(firefliesBitmap); + var bountyBoardsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(bountyBoardsBitmap); - var exports = Utils.LoadExports("FortniteGame/Content/Athena/Apollo/Maps/Special/ItemCollections/Apollo_Item_Fireflies"); + var exports = Utils.LoadExports("Bounties/Maps/BB_Overlay_S19_ServiceStations.umap"); foreach (var export in exports) { - if (!export.ExportType.Equals("BP_BGACSpawner_Fireflies_C", StringComparison.OrdinalIgnoreCase)) continue; - var displayName = $"FF_{export.Name["BP_BGACSpawnerFireFlies".Length..]}"; + if (!export.ExportType.Equals("B_Bounties_Spawner_BountyBoard_C", StringComparison.OrdinalIgnoreCase)) continue; + var displayName = $"BountyBoard_{export.Name["BP_BountyBoard_C_".Length..]}"; if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || @@ -838,7 +828,7 @@ await _threadWorkerView.Begin(_ => c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); } - _bitmaps[0]["ApolloGameplay_Fireflies"] = new MapLayer {Layer = firefliesBitmap, IsEnabled = false}; + _bitmaps[0]["ApolloGameplay_BountyBoards"] = new MapLayer {Layer = bountyBoardsBitmap, IsEnabled = false}; }); } @@ -874,93 +864,5 @@ await _threadWorkerView.Begin(_ => _bitmaps[0]["ApolloGameplay_TagsLocation"] = new MapLayer {Layer = tagsLocationBitmap, IsEnabled = false}; }); } - - /// - /// FortniteGame/Plugins/GameFeatures/CorruptionGameplay/Content/CorruptionGameplay_LevelOverlay.uasset - /// too lazy to filters - /// - private async Task LoadCubeMovements() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var cubeMovementsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(cubeMovementsBitmap); - - if (!Utils.TryLoadObject("/CorruptionGameplay/Levels/CorruptionGameplay_ApolloTerrain_Overlay.BP_CubeMovementGradient_2", out UObject overlay) || - !overlay.TryGetValue(out FSoftObjectPath[] cubeMovementStaticPaths, "cubeMovementStaticPaths") || cubeMovementStaticPaths.Length < 1) - return; - - var oldColor = _pathPaint.Color; - _pathPaint.Color = SKColors.Purple; - foreach (var cubeMovementStaticPath in cubeMovementStaticPaths) - { - var objectPath = cubeMovementStaticPath.AssetPathName.Text.SubstringBeforeLast("."); - var objectName = cubeMovementStaticPath.SubPathString.SubstringAfterLast("."); - if (!Utils.TryLoadObject($"{objectPath}.{objectName}", out UObject staticPath)) - return; - - DrawCubeMovements(c, staticPath, true); - } - - if (Utils.TryLoadObject("/CorruptionGameplay/Levels/CubeMovement/Apollo_CM_Gold_Overlay.CM_Spline_Gold", out UObject goldPath)) - { - _pathPaint.Color = SKColors.Gold; - DrawCubeMovements(c, goldPath, false); - } - - _pathPaint.Color = oldColor; - _bitmaps[0]["ApolloGameplay_CubeMovements"] = new MapLayer {Layer = cubeMovementsBitmap, IsEnabled = false}; - }); - } - - private void DrawCubeMovements(SKCanvas c, UObject staticPath, bool fixLocation) - { - if (!staticPath.TryGetValue(out FStructFallback[] pathTravelers, "PathTravelers") || pathTravelers.Length < 1 || - !pathTravelers[0].TryGetValue(out FPackageIndex[] generatedSplinesArray, "GeneratedSplinesArray") || generatedSplinesArray.Length < 1) - return; - - UObject uObject; - var parentRelativeLocation = new FVector(); - if (fixLocation) - { - if (!pathTravelers[0].TryGetValue(out FPackageIndex pathTraveler, "PathTraveler") || - !Utils.TryGetPackageIndexExport(pathTraveler, out uObject) || - !uObject.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out uObject) || - !uObject.TryGetValue(out parentRelativeLocation, "RelativeLocation")) - return; - } - - var bDone = false; - var path = new SKPath(); - foreach (var generatedSpline in generatedSplinesArray) - { - if (!Utils.TryGetPackageIndexExport(generatedSpline, out uObject) || - !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - if (!uObject.TryGetValue(out FStructFallback splineCurves, "SplineCurves") || - !splineCurves.TryGetValue(out FStructFallback positions, "Position") || - !positions.TryGetValue(out FStructFallback[] positionPoints, "Points")) continue; - - foreach (var positionPoint in positionPoints) - { - if (!positionPoint.TryGetValue(out FVector point, "OutVal")) continue; - - var vector = GetMapPosition(parentRelativeLocation + relativeLocation + point, _brRadius); - if (!bDone) - { - path.MoveTo(vector.X, vector.Y); - bDone = true; - } - else - { - path.LineTo(vector.X, vector.Y); - } - } - } - - c.DrawPath(path, _pathPaint); - } } } diff --git a/FModel/ViewModels/ModelViewerViewModel.cs b/FModel/ViewModels/ModelViewerViewModel.cs index 3bbf7c4b..9ef26e0b 100644 --- a/FModel/ViewModels/ModelViewerViewModel.cs +++ b/FModel/ViewModels/ModelViewerViewModel.cs @@ -1,19 +1,25 @@ -using System; +using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Linq; +using System.Numerics; using System.Threading.Tasks; using System.Windows; using System.Windows.Data; using System.Windows.Media.Media3D; +using CUE4Parse.UE4.Assets; using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports.Material; using CUE4Parse.UE4.Assets.Exports.SkeletalMesh; using CUE4Parse.UE4.Assets.Exports.StaticMesh; using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse.UE4.Objects.Core.Math; +using CUE4Parse.Utils; +using CUE4Parse_Conversion.Materials; using CUE4Parse_Conversion.Meshes; +using CUE4Parse_Conversion.Meshes.glTF; using CUE4Parse_Conversion.Meshes.PSK; using CUE4Parse_Conversion.Textures; using FModel.Framework; @@ -25,10 +31,18 @@ using Ookii.Dialogs.Wpf; using Serilog; using SharpDX; +using SharpGLTF.Geometry; +using SharpGLTF.Geometry.VertexTypes; +using SharpGLTF.Scenes; +using SharpGLTF.Schema2; +using SharpGLTF.Transforms; using SkiaSharp; using Camera = HelixToolkit.Wpf.SharpDX.Camera; using Geometry3D = HelixToolkit.SharpDX.Core.Geometry3D; using PerspectiveCamera = HelixToolkit.Wpf.SharpDX.PerspectiveCamera; +using Vector2 = SharpDX.Vector2; +using Vector3 = SharpDX.Vector3; +using VERTEX = SharpGLTF.Geometry.VertexTypes.VertexPositionNormalTangent; namespace FModel.ViewModels { @@ -36,7 +50,6 @@ public class ModelViewerViewModel : ViewModel { private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; - #region BINDINGS private EffectsManager _effectManager; public EffectsManager EffectManager { @@ -51,43 +64,11 @@ public Camera Cam set => SetProperty(ref _cam, value); } - private Geometry3D _xAxis; - public Geometry3D XAxis - { - get => _xAxis; - set => SetProperty(ref _xAxis, value); - } - - private Geometry3D _yAxis; - public Geometry3D YAxis - { - get => _yAxis; - set => SetProperty(ref _yAxis, value); - } - - private Geometry3D _zAxis; - public Geometry3D ZAxis - { - get => _zAxis; - set => SetProperty(ref _zAxis, value); - } - private ModelAndCam _selectedModel; // selected mesh public ModelAndCam SelectedModel { get => _selectedModel; - set - { - SetProperty(ref _selectedModel, value); - if (_selectedModel == null) return; - - XAxis = _selectedModel.XAxis; - YAxis = _selectedModel.YAxis; - ZAxis = _selectedModel.ZAxis; - Cam.UpDirection = new Vector3D(0, 1, 0); - Cam.Position = _selectedModel.Position; - Cam.LookDirection = _selectedModel.LookDirection; - } + set => SetProperty(ref _selectedModel, value); } private readonly ObservableCollection _loadedModels; // mesh list @@ -103,7 +84,6 @@ public bool AppendMode public bool CanAppend => SelectedModel != null; public TextureModel HDRi { get; private set; } - #endregion private readonly FGame _game; private readonly int[] _facesIndex = { 1, 0, 2 }; @@ -133,70 +113,64 @@ public async Task LoadExport(UObject export) ModelAndCam p; if (AppendMode && CanAppend) + { p = SelectedModel; + _loadedModels.Add(new ModelAndCam(export) {IsVisible = false}); + } else { p = new ModelAndCam(export); _loadedModels.Add(p); } - bool valid = false; await _threadWorkerView.Begin(_ => { - valid = export switch + switch (export) { - UStaticMesh st => TryLoadStaticMesh(st, p), - USkeletalMesh sk => TryLoadSkeletalMesh(sk, p), - UMaterialInstance mi => TryLoadMaterialInstance(mi, p), - _ => throw new ArgumentOutOfRangeException(nameof(export)) - }; + case UStaticMesh st: + LoadStaticMesh(st, p); + break; + case USkeletalMesh sk: + LoadSkeletalMesh(sk, p); + break; + case UMaterialInstance mi: + LoadMaterialInstance(mi, p); + break; + default: + throw new ArgumentOutOfRangeException(nameof(export)); + } }); - if (!valid) return; + + if (AppendMode && CanAppend) return; SelectedModel = p; + Cam.UpDirection = new Vector3D(0, 1, 0); + Cam.Position = p.Position; + Cam.LookDirection = p.LookDirection; } #region PUBLIC METHODS public void RenderingToggle() { if (SelectedModel == null) return; - foreach (var g in SelectedModel.Group3d) - { - if (g is not MeshGeometryModel3D geometryModel) - continue; - - geometryModel.IsRendering = !geometryModel.IsRendering; - } + SelectedModel.RenderingToggle = !SelectedModel.RenderingToggle; } public void WirefreameToggle() { if (SelectedModel == null) return; - foreach (var g in SelectedModel.Group3d) - { - if (g is not MeshGeometryModel3D geometryModel) - continue; + SelectedModel.WireframeToggle = !SelectedModel.WireframeToggle; + } - geometryModel.RenderWireframe = !geometryModel.RenderWireframe; - } + public void MaterialColorToggle() + { + if (SelectedModel == null) return; + SelectedModel.ShowMaterialColor = !SelectedModel.ShowMaterialColor; } public void DiffuseOnlyToggle() { if (SelectedModel == null) return; - foreach (var g in SelectedModel.Group3d) - { - if (g is not MeshGeometryModel3D { Material: PBRMaterial mat }) - continue; - - //mat.RenderAmbientOcclusionMap = !mat.RenderAmbientOcclusionMap; - mat.RenderDisplacementMap = !mat.RenderDisplacementMap; - //mat.RenderEmissiveMap = !mat.RenderEmissiveMap; - mat.RenderEnvironmentMap = !mat.RenderEnvironmentMap; - mat.RenderIrradianceMap = !mat.RenderIrradianceMap; - mat.RenderRoughnessMetallicMap = !mat.RenderRoughnessMetallicMap; - mat.RenderShadowMap = !mat.RenderShadowMap; - mat.RenderNormalMap = !mat.RenderNormalMap; - } + SelectedModel.DiffuseOnlyToggle = !SelectedModel.DiffuseOnlyToggle; } public void FocusOnSelectedMesh() @@ -204,83 +178,170 @@ public void FocusOnSelectedMesh() Cam.AnimateTo(SelectedModel.Position, SelectedModel.LookDirection, new Vector3D(0, 1, 0), 500); } - public void SaveLoadedModels() + public async Task SaveLoadedModels() { if (_loadedModels.Count < 1) return; var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = true}; if (folderBrowser.ShowDialog() == false) return; - foreach (var model in _loadedModels) + await _threadWorkerView.Begin(_ => { - var toSave = new CUE4Parse_Conversion.Exporter(model.Export, UserSettings.Default.TextureExportFormat, UserSettings.Default.LodExportFormat, UserSettings.Default.MeshExportFormat); - if (toSave.TryWriteToDir(new DirectoryInfo(folderBrowser.SelectedPath), out var savedFileName)) + foreach (var model in _loadedModels) { - Log.Information("Successfully saved {FileName}", savedFileName); - FLogger.AppendInformation(); - FLogger.AppendText($"Successfully saved {savedFileName}", Constants.WHITE, true); + var toSave = new CUE4Parse_Conversion.Exporter(model.Export, UserSettings.Default.TextureExportFormat, UserSettings.Default.LodExportFormat, UserSettings.Default.MeshExportFormat); + if (toSave.TryWriteToDir(new DirectoryInfo(folderBrowser.SelectedPath), out var savedFileName)) + { + Log.Information("Successfully saved {FileName}", savedFileName); + FLogger.AppendInformation(); + FLogger.AppendText($"Successfully saved {savedFileName}", Constants.WHITE, true); + } + else + { + Log.Error("{FileName} could not be saved", savedFileName); + FLogger.AppendError(); + FLogger.AppendText($"Could not save '{savedFileName}'", Constants.WHITE, true); + } } - else + }); + } + + public void SaveAsScene() + { + if (_loadedModels.Count < 1) return; + + var fileBrowser = new VistaSaveFileDialog + { + Title = "Save Loaded Models As...", + DefaultExt = ".glb", + Filter = "glTF Binary File (*.glb)|*.glb|glTF ASCII File (*.gltf)|*.gltf|All Files(*.*)|*.*", + AddExtension = true, + OverwritePrompt = true, + CheckPathExists = true + }; + + if (fileBrowser.ShowDialog() == false || string.IsNullOrEmpty(fileBrowser.FileName)) return; + + var sceneBuilder = new SceneBuilder(); + var materialExports = new List(); + foreach (var model in _loadedModels) + { + switch (model.Export) { - Log.Error("{FileName} could not be saved", savedFileName); - FLogger.AppendError(); - FLogger.AppendText($"Could not save '{savedFileName}'", Constants.WHITE, true); + case UStaticMesh sm: + { + var mesh = new MeshBuilder(sm.Name); + if (sm.TryConvert(out var convertedMesh) && convertedMesh.LODs.Count > 0) + { + var lod = convertedMesh.LODs.First(); + for (var i = 0; i < lod.Sections.Value.Length; i++) + { + Gltf.ExportStaticMeshSections(i, lod, lod.Sections.Value[i], materialExports, mesh); + } + sceneBuilder.AddRigidMesh(mesh, AffineTransform.Identity); + } + break; + } + case USkeletalMesh sk: + { + var mesh = new MeshBuilder(sk.Name); + + if (sk.TryConvert(out var convertedMesh) && convertedMesh.LODs.Count > 0) + { + var lod = convertedMesh.LODs.First(); + for (var i = 0; i < lod.Sections.Value.Length; i++) + { + Gltf.ExportSkelMeshSections(i, lod, lod.Sections.Value[i], materialExports, mesh); + } + var armatureNodeBuilder = new NodeBuilder(sk.Name+".ao"); + var armature = Gltf.CreateGltfSkeleton(convertedMesh.RefSkeleton, armatureNodeBuilder); + sceneBuilder.AddSkinnedMesh(mesh, Matrix4x4.Identity, armature); + } + break; + } } } + + var scene = sceneBuilder.ToGltf2(); + var fileName = fileBrowser.FileName; + if (fileName.EndsWith(".glb", StringComparison.OrdinalIgnoreCase)) + scene.SaveGLB(fileName); + else if (fileName.EndsWith(".gltf", StringComparison.OrdinalIgnoreCase)) + scene.SaveGLTF(fileName); + else if (fileName.EndsWith(".obj", StringComparison.OrdinalIgnoreCase)) + scene.SaveAsWavefront(fileName); + else + throw new ArgumentOutOfRangeException(nameof(fileName),$@"Unknown file format {fileName. SubstringAfterWithLast('.')}"); + + if (!CheckIfSaved(fileName)) return; + foreach (var materialExport in materialExports) + { + materialExport.TryWriteToDir(new DirectoryInfo(StringUtils.SubstringBeforeWithLast(fileName, '\\')), out _); + } } public void CopySelectedMaterialName() { - if (SelectedModel is not { } m || m.SelectedGeometry is null) + if (SelectedModel is not { } m || m.SelectedGeometry?.Tag is null) return; - Clipboard.SetText(m.SelectedGeometry.Name.TrimEnd()); + Clipboard.SetText(m.SelectedGeometry.DisplayName.TrimEnd()); } - public async Task TryChangeSelectedMaterial(UMaterialInstance materialInstance) + public async Task TryOverwriteMaterial(UMaterialInstance materialInstance) { - if (SelectedModel is not { } model || model.SelectedGeometry is null) - return false; + if (SelectedModel?.SelectedGeometry == null || _loadedModels.Count < 1) return false; PBRMaterial m = null; await _threadWorkerView.Begin(_ => { - var (material, _, _) = LoadMaterial(materialInstance); - m = material; + (m, var _, var _) = LoadMaterial(materialInstance); + + var obj = new ResolvedLoadedObject(materialInstance); + switch (_loadedModels[SelectedModel.SelectedGeometry.ExportIndex].Export) + { + case UStaticMesh { Materials: { } } st: + st.Materials[SelectedModel.SelectedGeometry.MaterialIndex] = obj; + break; + case USkeletalMesh sk: + sk.Materials[SelectedModel.SelectedGeometry.MaterialIndex].Material = obj; + break; + case UMaterialInstance: + SelectedModel.SwapExport(materialInstance); + break; + } }); - if (m == null) return false; - model.SelectedGeometry.Material = m; - return true; + SelectedModel.SelectedGeometry.Material = m; + return m != null; } #endregion - private bool TryLoadMaterialInstance(UMaterialInstance materialInstance, ModelAndCam cam) + private void LoadMaterialInstance(UMaterialInstance materialInstance, ModelAndCam cam) { var builder = new MeshBuilder(); - builder.AddSphere(Vector3.Zero, 10); - cam.TriangleCount = 1984; // no need to count + builder.AddBox(Vector3.Zero, 10, 10, 10); + cam.TriangleCount = 12; // no need to count - SetupCameraAndAxis(new FBox(new FVector(-11), new FVector(11)), cam); + SetupCameraAndAxis(new FBox(new FVector(-8), new FVector(8)), cam); var (m, isRendering, isTransparent) = LoadMaterial(materialInstance); Application.Current.Dispatcher.Invoke(() => { - cam.Group3d.Add(new MeshGeometryModel3D + cam.Group3d.Add(new CustomMeshGeometryModel3D { - Transform = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(1,0,0), -90)), - Name = FixName(materialInstance.Name), Geometry = builder.ToMeshGeometry3D(), - Material = m, IsTransparent = isTransparent, IsRendering = isRendering + Transform = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0,1,0), -45)), + DisplayName = materialInstance.Name, Geometry = builder.ToMeshGeometry3D(), MaterialIndex = 0, + Material = m, IsTransparent = isTransparent, IsRendering = isRendering, ExportIndex = _loadedModels.Count - 1 }); }); - return true; } - private bool TryLoadStaticMesh(UStaticMesh mesh, ModelAndCam cam) + private void LoadStaticMesh(UStaticMesh mesh, ModelAndCam cam) { if (!mesh.TryConvert(out var convertedMesh) || convertedMesh.LODs.Count <= 0) { - return false; + return; } SetupCameraAndAxis(convertedMesh.BoundingBox, cam); @@ -290,15 +351,13 @@ private bool TryLoadStaticMesh(UStaticMesh mesh, ModelAndCam cam) PushLod(lod.Sections.Value, lod.Verts, lod.Indices.Value, cam); break; } - - return true; } - private bool TryLoadSkeletalMesh(USkeletalMesh mesh, ModelAndCam cam) + private void LoadSkeletalMesh(USkeletalMesh mesh, ModelAndCam cam) { if (!mesh.TryConvert(out var convertedMesh) || convertedMesh.LODs.Count <= 0) { - return false; + return; } SetupCameraAndAxis(convertedMesh.BoundingBox, cam); @@ -308,16 +367,16 @@ private bool TryLoadSkeletalMesh(USkeletalMesh mesh, ModelAndCam cam) PushLod(lod.Sections.Value, lod.Verts, lod.Indices.Value, cam); break; } - - return true; } private void PushLod(CMeshSection[] sections, CMeshVertex[] verts, FRawStaticIndexBuffer indices, ModelAndCam cam) { - foreach (var section in sections) // each section is a mesh part with its own material + for (int i = 0; i < sections.Length; i++) // each section is a mesh part with its own material { + var section = sections[i]; var builder = new MeshBuilder(); cam.TriangleCount += section.NumFaces; // NumFaces * 3 (triangle) = next section FirstIndex + for (var j = 0; j < section.NumFaces; j++) // draw a triangle for each face { foreach (var t in _facesIndex) // triangle face 1 then 0 then 2 @@ -327,22 +386,23 @@ private void PushLod(CMeshSection[] sections, CMeshVertex[] verts, FRawStaticInd var p = new Vector3(vert.Position.X, vert.Position.Z, vert.Position.Y); // up direction is Y var n = new Vector3(vert.Normal.X, vert.Normal.Z, vert.Normal.Y); n.Normalize(); - var uv = new Vector2(vert.UV.U, vert.UV.V); - builder.AddNode(p, n, uv); + + builder.AddNode(p, n, new Vector2(vert.UV.U, vert.UV.V)); builder.TriangleIndices.Add(j * 3 + t); // one mesh part is "j * 3 + t" use "id" if you're building the full mesh } } - if (section.Material == null || !section.Material.TryLoad(out var unrealMaterial)) + if (section.Material == null || !section.Material.TryLoad(out var o) || o is not UMaterialInterface material) continue; - var (m, isRendering, isTransparent) = LoadMaterial(unrealMaterial); + var (m, isRendering, isTransparent) = LoadMaterial(material); Application.Current.Dispatcher.Invoke(() => { - cam.Group3d.Add(new MeshGeometryModel3D + cam.Group3d.Add(new CustomMeshGeometryModel3D { - Name = FixName(unrealMaterial.Name), Geometry = builder.ToMeshGeometry3D(), - Material = m, IsTransparent = isTransparent, IsRendering = isRendering + DisplayName = section.MaterialName ?? material.Name, MaterialIndex = section.MaterialIndex, + Geometry = builder.ToMeshGeometry3D(), Material = m, IsTransparent = isTransparent, + IsRendering = isRendering, ExportIndex = _loadedModels.Count - 1 }); }); } @@ -350,17 +410,37 @@ private void PushLod(CMeshSection[] sections, CMeshVertex[] verts, FRawStaticInd private (PBRMaterial material, bool isRendering, bool isTransparent) LoadMaterial(UMaterialInterface unrealMaterial) { - var m = new PBRMaterial { RenderShadowMap = true, EnableAutoTangent = true, RenderEnvironmentMap = true }; + PBRMaterial m = null; // default + Application.Current.Dispatcher.Invoke(() => // tweak this later + { + m = new PBRMaterial // recreate on ui thread + { + RenderShadowMap = true, EnableAutoTangent = true, RenderEnvironmentMap = true + }; + }); + var parameters = new CMaterialParams(); unrealMaterial.GetParams(parameters); var isRendering = !parameters.IsNull; if (isRendering) { - if (parameters.Diffuse is UTexture2D diffuse) - m.AlbedoMap = new TextureModel(diffuse.Decode()?.Encode().AsStream()); + if (!parameters.HasTopDiffuseTexture && parameters.DiffuseColor is { A: > 0 } diffuseColor) + { + Application.Current.Dispatcher.Invoke(() => m.AlbedoColor = new Color4(diffuseColor.R, diffuseColor.G, diffuseColor.B, diffuseColor.A)); + } + else if (parameters.Diffuse is UTexture2D diffuse) + { + var s = diffuse.Decode()?.Encode().AsStream(); + Application.Current.Dispatcher.Invoke(() => m.AlbedoMap = new TextureModel(s)); + } + if (parameters.Normal is UTexture2D normal) - m.NormalMap = new TextureModel(normal.Decode()?.Encode().AsStream()); + { + var s = normal.Decode()?.Encode().AsStream(); + Application.Current.Dispatcher.Invoke(() => m.NormalMap = new TextureModel(s)); + } + if (parameters.Specular is UTexture2D specular) { var mip = specular.GetFirstMip(); @@ -379,12 +459,14 @@ private void PushLod(CMeshSection[] sections, CMeshVertex[] verts, FRawStaticInd { var offset = 0; fixed (byte* d = data) + { for (var i = 0; i < mip.SizeX * mip.SizeY; i++) { d[offset] = 0; - (d[offset+1], d[offset+2]) = (d[offset+2], d[offset+1]); // swap G and B + (d[offset + 1], d[offset + 2]) = (d[offset + 2], d[offset + 1]); // swap G and B offset += 4; } + } } parameters.RoughnessValue = 1; parameters.MetallicValue = 1; @@ -392,20 +474,40 @@ private void PushLod(CMeshSection[] sections, CMeshVertex[] verts, FRawStaticInd } case FGame.ShooterGame: { - // Valorant's Specular Texture Channels - // R Metallic - // G Specular - // B Roughness - unsafe + var packedPBRType = specular.Name[(specular.Name.LastIndexOf('_') + 1)..]; + switch (packedPBRType) { - var offset = 0; - fixed (byte* d = data) - for (var i = 0; i < mip.SizeX * mip.SizeY; i++) + case "MRAE": // R: Metallic, G: AO (0-127) & Emissive (128-255), B: Roughness (Character PBR) + unsafe { - (d[offset], d[offset+2]) = (d[offset+2], d[offset]); // swap R and B - (d[offset], d[offset+1]) = (d[offset+1], d[offset]); // swap B and G - offset += 4; + var offset = 0; + fixed (byte* d = data) + { + for (var i = 0; i < mip.SizeX * mip.SizeY; i++) + { + (d[offset], d[offset + 2]) = (d[offset + 2], d[offset]); // swap R and B + (d[offset], d[offset + 1]) = (d[offset + 1], d[offset]); // swap R and G + offset += 4; + } + } } + break; + case "MRAS": // R: Metallic, B: Roughness, B: AO, A: Specular (Legacy PBR) + case "MRA": // R: Metallic, B: Roughness, B: AO (Environment PBR) + case "MRS": // R: Metallic, G: Roughness, B: Specular (Weapon PBR) + unsafe + { + var offset = 0; + fixed (byte* d = data) + { + for (var i = 0; i < mip.SizeX * mip.SizeY; i++) + { + (d[offset], d[offset + 2]) = (d[offset + 2], d[offset]); // swap R and B + offset += 4; + } + } + } + break; } parameters.RoughnessValue = 1; parameters.MetallicValue = 1; @@ -421,11 +523,13 @@ private void PushLod(CMeshSection[] sections, CMeshVertex[] verts, FRawStaticInd { var offset = 0; fixed (byte* d = data) + { for (var i = 0; i < mip.SizeX * mip.SizeY; i++) { - (d[offset], d[offset+2]) = (d[offset+2], d[offset]); // swap R and B + (d[offset], d[offset + 2]) = (d[offset + 2], d[offset]); // swap R and B offset += 4; } + } } break; } @@ -441,15 +545,30 @@ private void PushLod(CMeshSection[] sections, CMeshVertex[] verts, FRawStaticInd } // R -> AO G -> Roughness B -> Metallic - m.RoughnessMetallicMap = new TextureModel(bitmap.Encode(SKEncodedImageFormat.Png, 100).AsStream()); - m.RoughnessFactor = parameters.RoughnessValue; - m.MetallicFactor = parameters.MetallicValue; - m.RenderAmbientOcclusionMap = parameters.SpecularValue > 0; + var s = bitmap.Encode(SKEncodedImageFormat.Png, 100).AsStream(); + Application.Current.Dispatcher.Invoke(() => + { + m.RoughnessMetallicMap = new TextureModel(s); + m.RoughnessFactor = parameters.RoughnessValue; + m.MetallicFactor = parameters.MetallicValue; + m.RenderAmbientOcclusionMap = parameters.SpecularValue > 0; + }); + } + + if (parameters.HasTopEmissiveTexture && parameters.Emissive is UTexture2D emissive && parameters.EmissiveColor is { A: > 0 } emissiveColor) + { + var s = emissive.Decode()?.Encode().AsStream(); + var c = new Color4(emissiveColor.R, emissiveColor.G, emissiveColor.B, emissiveColor.A); + Application.Current.Dispatcher.Invoke(() => + { + m.EmissiveColor = c; + m.EmissiveMap = new TextureModel(s); + }); } } else { - m.AlbedoColor = new Color4(1, 0, 0, 1); + Application.Current.Dispatcher.Invoke(() => m.AlbedoColor = new Color4(1, 0, 0, 1)); } return (m, isRendering, parameters.IsTransparent); @@ -474,15 +593,20 @@ private void SetupCameraAndAxis(FBox box, ModelAndCam cam) cam.LookDirection = new Vector3D(-cam.Position.X + center.X, 0, -cam.Position.Z + center.Y); } - private string FixName(string input) + private bool CheckIfSaved(string path) { - if (input.Length < 1) - return "Material_Has_No_Name"; - - if (int.TryParse(input[0].ToString(), out _)) - input = input[1..]; + if (File.Exists(path)) + { + Log.Information("Successfully saved {FileName}", path); + FLogger.AppendInformation(); + FLogger.AppendText($"Successfully saved {path}", Constants.WHITE, true); + return true; + } - return input.Replace('-', '_'); + Log.Error("{FileName} could not be saved", path); + FLogger.AppendError(); + FLogger.AppendText($"Could not save '{path}'", Constants.WHITE, true); + return false; } public void Clear() @@ -497,7 +621,7 @@ public void Clear() public class ModelAndCam : ViewModel { - public UObject Export { get; } + public UObject Export { get; private set; } public Point3D Position { get; set; } public Vector3D LookDirection { get; set; } public Geometry3D XAxis { get; set; } @@ -505,8 +629,98 @@ public class ModelAndCam : ViewModel public Geometry3D ZAxis { get; set; } public int TriangleCount { get; set; } - private MeshGeometryModel3D _selectedGeometry; // selected material - public MeshGeometryModel3D SelectedGeometry + private bool _isVisible = true; + public bool IsVisible + { + get => _isVisible; + set => SetProperty(ref _isVisible, value); + } + + private bool _renderingToggle; + public bool RenderingToggle + { + get => _renderingToggle; + set + { + SetProperty(ref _renderingToggle, value); + foreach (var g in Group3d) + { + if (g is not CustomMeshGeometryModel3D geometryModel) + continue; + + geometryModel.IsRendering = !geometryModel.IsRendering; + } + } + } + + private bool _wireframeToggle; + public bool WireframeToggle + { + get => _wireframeToggle; + set + { + SetProperty(ref _wireframeToggle, value); + foreach (var g in Group3d) + { + if (g is not CustomMeshGeometryModel3D geometryModel) + continue; + + geometryModel.RenderWireframe = !geometryModel.RenderWireframe; + } + } + } + + private bool _showMaterialColor; + public bool ShowMaterialColor + { + get => _showMaterialColor; + set + { + SetProperty(ref _showMaterialColor, value); + for (int i = 0; i < Group3d.Count; i++) + { + if (Group3d[i] is not CustomMeshGeometryModel3D { Material: PBRMaterial material } m) + continue; + + var index = B(i); + material.RenderAlbedoMap = !_showMaterialColor; + + if (_showMaterialColor) + { + m.Tag = material.AlbedoColor; + material.AlbedoColor = new Color4(_table[C(index)] / 255, _table[C(index >> 1)] / 255, _table[C(index >> 2)] / 255, 1); + } + else material.AlbedoColor = (Color4) m.Tag; + } + } + } + + private bool _diffuseOnlyToggle; + public bool DiffuseOnlyToggle + { + get => _diffuseOnlyToggle; + set + { + SetProperty(ref _diffuseOnlyToggle, value); + foreach (var g in Group3d) + { + if (g is not CustomMeshGeometryModel3D { Material: PBRMaterial material }) + continue; + + material.RenderAmbientOcclusionMap = !material.RenderAmbientOcclusionMap; + material.RenderDisplacementMap = !material.RenderDisplacementMap; + // material.RenderEmissiveMap = !material.RenderEmissiveMap; + // material.RenderEnvironmentMap = !material.RenderEnvironmentMap; + material.RenderIrradianceMap = !material.RenderIrradianceMap; + material.RenderRoughnessMetallicMap = !material.RenderRoughnessMetallicMap; + material.RenderShadowMap = !material.RenderShadowMap; + material.RenderNormalMap = !material.RenderNormalMap; + } + } + } + + private CustomMeshGeometryModel3D _selectedGeometry; // selected material + public CustomMeshGeometryModel3D SelectedGeometry { get => _selectedGeometry; set => SetProperty(ref _selectedGeometry, value); @@ -519,6 +733,9 @@ public ObservableElement3DCollection Group3d set => SetProperty(ref _group3d, value); } + private readonly float[] _table = { 255 * 0.9f, 25 * 3.0f, 255 * 0.6f, 255 * 0.0f }; + private readonly int[] _table2 = { 0, 1, 2, 4, 7, 3, 5, 6 }; + public ModelAndCam(UObject export) { Export = export; @@ -526,6 +743,14 @@ public ModelAndCam(UObject export) Group3d = new ObservableElement3DCollection(); } + private int B(int x) => (x & 0xFFF8) | _table2[x & 7] ^ 7; + private int C(int x) => (x & 1) | ((x >> 2) & 2); + + public void SwapExport(UObject e) + { + Export = e; + } + public void Dispose() { TriangleCount = 0; @@ -537,4 +762,11 @@ public void Dispose() } } } + + public class CustomMeshGeometryModel3D : MeshGeometryModel3D + { + public string DisplayName { get; set; } + public int MaterialIndex { get; set; } + public int ExportIndex { get; set; } + } } diff --git a/FModel/ViewModels/SettingsViewModel.cs b/FModel/ViewModels/SettingsViewModel.cs index e047a7e1..7c1a5922 100644 --- a/FModel/ViewModels/SettingsViewModel.cs +++ b/FModel/ViewModels/SettingsViewModel.cs @@ -1,7 +1,6 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; using System.Threading.Tasks; using CUE4Parse.UE4.Objects.Core.Misc; using CUE4Parse.UE4.Objects.Core.Serialization; @@ -47,13 +46,6 @@ public EGame SelectedUeGame set => SetProperty(ref _selectedUeGame, value); } - private UE4Version _selectedUeVersion; - public UE4Version SelectedUeVersion - { - get => _selectedUeVersion; - set => SetProperty(ref _selectedUeVersion, value); - } - private List _selectedCustomVersions; public List SelectedCustomVersions { @@ -127,7 +119,6 @@ public ETextureFormat SelectedTextureExportFormat public ReadOnlyObservableCollection UpdateModes { get; private set; } public ObservableCollection Presets { get; private set; } public ReadOnlyObservableCollection UeGames { get; private set; } - public ReadOnlyObservableCollection UeVersions { get; private set; } public ReadOnlyObservableCollection AssetLanguages { get; private set; } public ReadOnlyObservableCollection AesReloads { get; private set; } public ReadOnlyObservableCollection DiscordRpcs { get; private set; } @@ -142,11 +133,11 @@ public ETextureFormat SelectedTextureExportFormat private readonly FGame _game; private Game _gamePreset; private string _outputSnapshot; + private string _modelSnapshot; private string _gameSnapshot; private EUpdateMode _updateModeSnapshot; private string _presetSnapshot; private EGame _ueGameSnapshot; - private UE4Version _ueVersionSnapshot; private List _customVersionsSnapshot; private Dictionary _optionsSnapshot; private ELanguage _assetLanguageSnapshot; @@ -164,11 +155,11 @@ public SettingsViewModel(FGame game) public void Initialize() { _outputSnapshot = UserSettings.Default.OutputDirectory; + _modelSnapshot = UserSettings.Default.ModelDirectory; _gameSnapshot = UserSettings.Default.GameDirectory; _updateModeSnapshot = UserSettings.Default.UpdateMode; _presetSnapshot = UserSettings.Default.Presets[_game]; _ueGameSnapshot = UserSettings.Default.OverridedGame[_game]; - _ueVersionSnapshot = UserSettings.Default.OverridedUEVersion[_game]; _customVersionsSnapshot = UserSettings.Default.OverridedCustomVersions[_game]; _optionsSnapshot = UserSettings.Default.OverridedOptions[_game]; _assetLanguageSnapshot = UserSettings.Default.AssetLanguage; @@ -181,7 +172,6 @@ public void Initialize() SelectedUpdateMode = _updateModeSnapshot; SelectedPreset = _presetSnapshot; SelectedUeGame = _ueGameSnapshot; - SelectedUeVersion = _ueVersionSnapshot; SelectedCustomVersions = _customVersionsSnapshot; SelectedOptions = _optionsSnapshot; SelectedAssetLanguage = _assetLanguageSnapshot; @@ -196,7 +186,6 @@ public void Initialize() UpdateModes = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateUpdateModes())); Presets = new ObservableCollection(EnumeratePresets()); UeGames = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateUeGames())); - UeVersions = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateUeVersions())); AssetLanguages = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateAssetLanguages())); AesReloads = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateAesReloads())); DiscordRpcs = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateDiscordRpcs())); @@ -226,7 +215,6 @@ public void SwitchPreset(string key) { if (_gamePreset?.Versions == null || !_gamePreset.Versions.TryGetValue(key, out var version)) return; SelectedUeGame = version.GameEnum.ToEnum(EGame.GAME_UE4_LATEST); - SelectedUeVersion = (UE4Version)version.UeVer; SelectedCustomVersions = new List(); foreach (var (guid, v) in version.CustomVersions) @@ -244,7 +232,6 @@ public void SwitchPreset(string key) public void ResetPreset() { SelectedUeGame = _ueGameSnapshot; - SelectedUeVersion = _ueVersionSnapshot; SelectedCustomVersions = _customVersionsSnapshot; SelectedOptions = _optionsSnapshot; } @@ -253,19 +240,22 @@ public SettingsOut Save() { var ret = SettingsOut.Nothing; - if (_ueGameSnapshot != SelectedUeGame || _ueVersionSnapshot != SelectedUeVersion || // comboboxes + if (_ueGameSnapshot != SelectedUeGame || // combobox _customVersionsSnapshot != SelectedCustomVersions || _optionsSnapshot != SelectedOptions || _outputSnapshot != UserSettings.Default.OutputDirectory || // textbox + _modelSnapshot != UserSettings.Default.ModelDirectory || // textbox _gameSnapshot != UserSettings.Default.GameDirectory) // textbox ret = SettingsOut.Restart; if (_assetLanguageSnapshot != SelectedAssetLanguage) ret = SettingsOut.ReloadLocres; + if (_updateModeSnapshot != SelectedUpdateMode) + ret = SettingsOut.CheckForUpdates; + UserSettings.Default.UpdateMode = SelectedUpdateMode; UserSettings.Default.Presets[_game] = SelectedPreset; UserSettings.Default.OverridedGame[_game] = SelectedUeGame; - UserSettings.Default.OverridedUEVersion[_game] = SelectedUeVersion; UserSettings.Default.OverridedCustomVersions[_game] = SelectedCustomVersions; UserSettings.Default.OverridedOptions[_game] = SelectedOptions; UserSettings.Default.AssetLanguage = SelectedAssetLanguage; @@ -283,20 +273,19 @@ public SettingsOut Save() return ret; } - private IEnumerable EnumerateUpdateModes() => Enum.GetValues(SelectedUpdateMode.GetType()).Cast(); + private IEnumerable EnumerateUpdateModes() => Enum.GetValues(); private IEnumerable EnumeratePresets() { yield return Constants._NO_PRESET_TRIGGER; } - private IEnumerable EnumerateUeGames() => Enum.GetValues(SelectedUeGame.GetType()).Cast(); - private IEnumerable EnumerateUeVersions() => Enum.GetValues(SelectedUeVersion.GetType()).Cast(); - private IEnumerable EnumerateAssetLanguages() => Enum.GetValues(SelectedAssetLanguage.GetType()).Cast(); - private IEnumerable EnumerateAesReloads() => Enum.GetValues(SelectedAesReload.GetType()).Cast(); - private IEnumerable EnumerateDiscordRpcs() => Enum.GetValues(SelectedDiscordRpc.GetType()).Cast(); - private IEnumerable EnumerateCompressedAudios() => Enum.GetValues(SelectedCompressedAudio.GetType()).Cast(); - private IEnumerable EnumerateCosmeticStyles() => Enum.GetValues(SelectedCosmeticStyle.GetType()).Cast(); - private IEnumerable EnumerateMeshExportFormat() => Enum.GetValues(SelectedMeshExportFormat.GetType()).Cast(); - private IEnumerable EnumerateLodExportFormat() => Enum.GetValues(SelectedLodExportFormat.GetType()).Cast(); - private IEnumerable EnumerateTextureExportFormat() => Enum.GetValues(SelectedTextureExportFormat.GetType()).Cast(); + private IEnumerable EnumerateUeGames() => Enum.GetValues(); + private IEnumerable EnumerateAssetLanguages() => Enum.GetValues(); + private IEnumerable EnumerateAesReloads() => Enum.GetValues(); + private IEnumerable EnumerateDiscordRpcs() => Enum.GetValues(); + private IEnumerable EnumerateCompressedAudios() => Enum.GetValues(); + private IEnumerable EnumerateCosmeticStyles() => Enum.GetValues(); + private IEnumerable EnumerateMeshExportFormat() => Enum.GetValues(); + private IEnumerable EnumerateLodExportFormat() => Enum.GetValues(); + private IEnumerable EnumerateTextureExportFormat() => Enum.GetValues(); } } diff --git a/FModel/ViewModels/TabControlViewModel.cs b/FModel/ViewModels/TabControlViewModel.cs index 3cb5efb8..cda2e627 100644 --- a/FModel/ViewModels/TabControlViewModel.cs +++ b/FModel/ViewModels/TabControlViewModel.cs @@ -4,22 +4,16 @@ using FModel.Settings; using FModel.ViewModels.Commands; using FModel.Views.Resources.Controls; - using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Highlighting; - using Microsoft.Win32; - using Serilog; - using SkiaSharp; - using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Windows; -using System.Windows.Media; using System.Windows.Media.Imaging; namespace FModel.ViewModels diff --git a/FModel/Views/About.xaml b/FModel/Views/About.xaml index 780a8f32..1b93d5e3 100644 --- a/FModel/Views/About.xaml +++ b/FModel/Views/About.xaml @@ -22,28 +22,28 @@ - + - + Text="Since the release in March 2019, we've continuously added new features and improved old ones. It started as a simple UE4 file explorer to parse packages and, as things progressed, became really popular to data-mine games and create items icons. FModel 4 is now the most complete and well-made version so far. It features dozens of settings for you to use to make FModel your own." /> + - + - + Text="TheGameVlog ♥, Quentin ♥, Maiky ♥, HYPEX ♥, AnimatedAspect, Evan, VenomLeaks, JayKey, Fevers, Netu, Laggy, s0ll, RazTracker, Mikey, kyle, Yanteh, Shiina, SexyNutella, Alexander, Jinx, koba, Tector, imatrix, LamZykoss, Frenzy Leaks, LlamaLeaks, xplore, XTigerHyperX, FunGames, WeLoveFortnite." /> + @@ -51,4 +51,4 @@ - \ No newline at end of file + diff --git a/FModel/Views/AesManager.xaml b/FModel/Views/AesManager.xaml index 07c45823..8b5e8429 100644 --- a/FModel/Views/AesManager.xaml +++ b/FModel/Views/AesManager.xaml @@ -27,13 +27,13 @@ - + + Text="In order to decipher archives' information, an AES key, in most cases, is needed. Here you can set the key for your static and dynamic archives. If you don't know what key to use for your set game, simply Google it. Keys must start with "0x" and contains 64 more characters." /> - + @@ -41,7 +41,7 @@ - + @@ -54,7 +54,7 @@ - + - + @@ -76,7 +76,7 @@ - + - + @@ -96,11 +90,11 @@ - + - + @@ -114,8 +108,10 @@ + @@ -133,7 +129,7 @@ - + diff --git a/FModel/Views/ModelViewer.xaml.cs b/FModel/Views/ModelViewer.xaml.cs index 55c729ce..cd675c13 100644 --- a/FModel/Views/ModelViewer.xaml.cs +++ b/FModel/Views/ModelViewer.xaml.cs @@ -6,6 +6,7 @@ using CUE4Parse.UE4.Assets.Exports.Material; using FModel.Services; using FModel.ViewModels; +using HelixToolkit.Wpf.SharpDX; using MessageBox = AdonisUI.Controls.MessageBox; using MessageBoxImage = AdonisUI.Controls.MessageBoxImage; @@ -23,12 +24,12 @@ public ModelViewer() } public async void Load(UObject export) => await _applicationView.ModelViewer.LoadExport(export); - public async void Swap(UMaterialInstance materialInstance) + public async void Overwrite(UMaterialInstance materialInstance) { - var sucess = await _applicationView.ModelViewer.TryChangeSelectedMaterial(materialInstance); + var sucess = await _applicationView.ModelViewer.TryOverwriteMaterial(materialInstance); if (sucess) { - _applicationView.CUE4Parse.ModelIsSwappingMaterial = false; + _applicationView.CUE4Parse.ModelIsOverwritingMaterial = false; } else { @@ -47,11 +48,11 @@ private void OnClosing(object sender, CancelEventArgs e) { _applicationView.ModelViewer.Clear(); _applicationView.ModelViewer.AppendMode = false; - _applicationView.CUE4Parse.ModelIsSwappingMaterial = false; + _applicationView.CUE4Parse.ModelIsOverwritingMaterial = false; MyAntiCrashGroup.ItemsSource = null; // <3 } - private void OnWindowKeyDown(object sender, KeyEventArgs e) + private async void OnWindowKeyDown(object sender, KeyEventArgs e) { switch (e.Key) { @@ -61,34 +62,49 @@ private void OnWindowKeyDown(object sender, KeyEventArgs e) case Key.H: _applicationView.ModelViewer.RenderingToggle(); break; - // case Key.D: - // _applicationView.ModelViewer.DiffuseOnlyToggle(); - // break; + case Key.D: + _applicationView.ModelViewer.DiffuseOnlyToggle(); + break; + case Key.M: + _applicationView.ModelViewer.MaterialColorToggle(); + break; case Key.Decimal: _applicationView.ModelViewer.FocusOnSelectedMesh(); break; + case Key.S when Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift): + _applicationView.ModelViewer.SaveAsScene(); + break; + case Key.S when Keyboard.Modifiers.HasFlag(ModifierKeys.Control): + await _applicationView.ModelViewer.SaveLoadedModels(); + break; } } + private void OnMouse3DDown(object sender, MouseDown3DEventArgs e) + { + if (!Keyboard.Modifiers.HasFlag(ModifierKeys.Shift) || e.HitTestResult.ModelHit is not CustomMeshGeometryModel3D m) return; + _applicationView.ModelViewer.SelectedModel.SelectedGeometry = m; + MaterialsListName.ScrollIntoView(m); + } + private void OnFocusClick(object sender, RoutedEventArgs e) => _applicationView.ModelViewer.FocusOnSelectedMesh(); private void OnCopyClick(object sender, RoutedEventArgs e) => _applicationView.ModelViewer.CopySelectedMaterialName(); - private void Save(object sender, RoutedEventArgs e) - => _applicationView.ModelViewer.SaveLoadedModels(); + private async void Save(object sender, RoutedEventArgs e) + => await _applicationView.ModelViewer.SaveLoadedModels(); - private void OnChangeMaterialClick(object sender, RoutedEventArgs e) + private void OnOverwriteMaterialClick(object sender, RoutedEventArgs e) { - _applicationView.CUE4Parse.ModelIsSwappingMaterial = true; - + _applicationView.CUE4Parse.ModelIsOverwritingMaterial = true; if (!_messageShown) { MessageBox.Show(new MessageBoxModel { Text = "Simply extract a material once FModel will be brought to the foreground. This message will be shown once per Model Viewer's lifetime, close it to begin.", - Caption = "How To Change Material?", + Caption = "How To Overwrite Material?", Icon = MessageBoxImage.Information, IsSoundEnabled = false }); diff --git a/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs b/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs index c1ca2c11..20555192 100644 --- a/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs +++ b/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs @@ -32,13 +32,7 @@ public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructio return base.CreateTextRun(startVisualColumn, context); } - private bool GamePathIsClickable() - { - if (string.IsNullOrEmpty(_gamePath)) - return false; - - return (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control; - } + private bool GamePathIsClickable() => !string.IsNullOrEmpty(_gamePath) && Keyboard.Modifiers == ModifierKeys.None; protected override void OnQueryCursor(QueryCursorEventArgs e) { @@ -49,7 +43,7 @@ protected override void OnQueryCursor(QueryCursorEventArgs e) protected override void OnMouseDown(MouseButtonEventArgs e) { - if (e.ChangedButton != MouseButton.Right && (e.ChangedButton != MouseButton.Left || !GamePathIsClickable())) + if (e.ChangedButton != MouseButton.Left || !GamePathIsClickable()) return; if (e.Handled || OnGamePathClicked == null) return; @@ -82,4 +76,4 @@ await _threadWorkerView.Begin(_ => return a; } } -} \ No newline at end of file +} diff --git a/FModel/Views/Resources/Controls/AvalonEditor.xaml b/FModel/Views/Resources/Controls/AvalonEditor.xaml index e1b45d20..f82ec447 100644 --- a/FModel/Views/Resources/Controls/AvalonEditor.xaml +++ b/FModel/Views/Resources/Controls/AvalonEditor.xaml @@ -109,7 +109,7 @@ - + > _savedCarets = new(); private NavigationList _caretsOffsets { - get - { - if (MyAvalonEditor.Document != null) - return _savedCarets.GetOrAdd(MyAvalonEditor.Document.FileName, () => new NavigationList()); - else - return new NavigationList(); - } + get => MyAvalonEditor.Document != null + ? _savedCarets.GetOrAdd(MyAvalonEditor.Document.FileName, () => new NavigationList()) + : new NavigationList(); } private bool _ignoreCaret = true; @@ -223,8 +219,9 @@ private void OnCloseClick(object sender, RoutedEventArgs e) private void OnTabClose(object sender, EventArgs eventArgs) { - if (sender is not TabControlViewModel tab|| eventArgs is not TabControlViewModel.TabEventArgs e) + if (eventArgs is not TabControlViewModel.TabEventArgs e || e.TabToRemove.Document == null) return; + var fileName = e.TabToRemove.Document.FileName; if (_savedCarets.ContainsKey(fileName)) _savedCarets.Remove(fileName); diff --git a/FModel/Views/Resources/Converters/FolderToSeparatorTagConverter.cs b/FModel/Views/Resources/Converters/FolderToSeparatorTagConverter.cs index 3e0f85b2..d36f66d3 100644 --- a/FModel/Views/Resources/Converters/FolderToSeparatorTagConverter.cs +++ b/FModel/Views/Resources/Converters/FolderToSeparatorTagConverter.cs @@ -10,7 +10,7 @@ public class FolderToSeparatorTagConverter : IValueConverter public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - return value != null ? $"{value.ToString()?.ToUpper()} ASSETS" : null; + return value != null ? $"{value.ToString()?.ToUpper()} PACKAGES" : null; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) @@ -18,4 +18,4 @@ public object ConvertBack(object value, Type targetType, object parameter, Cultu throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/FModel/Views/Resources/Converters/StringToGameConverter.cs b/FModel/Views/Resources/Converters/StringToGameConverter.cs index 6a93d827..a78b7015 100644 --- a/FModel/Views/Resources/Converters/StringToGameConverter.cs +++ b/FModel/Views/Resources/Converters/StringToGameConverter.cs @@ -25,6 +25,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn "MinecraftDungeons" => "Minecraft Dungeons", "shoebill" => "Star Wars: Jedi Fallen Order", "a99769d95d8f400baad1f67ab5dfe508" => "Core", + 381210 => "Dead By Daylight", 578080 => "PLAYERUNKNOWN'S BATTLEGROUNDS", 677620 => "Splitgate", _ => value, diff --git a/FModel/Views/Resources/Converters/TagToColorConverter.cs b/FModel/Views/Resources/Converters/TagToColorConverter.cs new file mode 100644 index 00000000..9b7012ea --- /dev/null +++ b/FModel/Views/Resources/Converters/TagToColorConverter.cs @@ -0,0 +1,28 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Media; +using HelixToolkit.Wpf.SharpDX; + +namespace FModel.Views.Resources.Converters +{ + public class TagToColorConverter : IValueConverter + { + public static readonly TagToColorConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is not PBRMaterial material) + return new SolidColorBrush(Colors.Red); + + return new SolidColorBrush(Color.FromScRgb( + material.AlbedoColor.Alpha, material.AlbedoColor.Red, + material.AlbedoColor.Green, material.AlbedoColor.Blue)); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/FModel/Views/Resources/Resources.xaml b/FModel/Views/Resources/Resources.xaml index e874d435..f2f6950c 100644 --- a/FModel/Views/Resources/Resources.xaml +++ b/FModel/Views/Resources/Resources.xaml @@ -70,7 +70,7 @@ M12 6.5c2.76 0 5 2.24 5 5 0 .51-.1 1-.24 1.46l3.06 3.06c1.39-1.23 2.49-2.77 3.18-4.53C21.27 7.11 17 4 12 4c-1.27 0-2.49.2-3.64.57l2.17 2.17c.47-.14.96-.24 1.47-.24zM2.71 3.16c-.39.39-.39 1.02 0 1.41l1.97 1.97C3.06 7.83 1.77 9.53 1 11.5 2.73 15.89 7 19 12 19c1.52 0 2.97-.3 4.31-.82l2.72 2.72c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L4.13 3.16c-.39-.39-1.03-.39-1.42 0zM12 16.5c-2.76 0-5-2.24-5-5 0-.77.18-1.5.49-2.14l1.57 1.57c-.03.18-.06.37-.06.57 0 1.66 1.34 3 3 3 .2 0 .38-.03.57-.07L14.14 16c-.65.32-1.37.5-2.14.5zm2.97-5.33c-.15-1.4-1.25-2.49-2.64-2.64l2.64 2.64z M3 5h2V3c-1.1 0-2 .9-2 2zm0 8h2v-2H3v2zm4 8h2v-2H7v2zM3 9h2V7H3v2zm10-6h-2v2h2V3zm6 0v2h2c0-1.1-.9-2-2-2zM5 21v-2H3c0 1.1.9 2 2 2zm-2-4h2v-2H3v2zM9 3H7v2h2V3zm2 18h2v-2h-2v2zm8-8h2v-2h-2v2zm0 8c1.1 0 2-.9 2-2h-2v2zm0-12h2V7h-2v2zm0 8h2v-2h-2v2zm-4 4h2v-2h-2v2zm0-16h2V3h-2v2zM8 17h8c.55 0 1-.45 1-1V8c0-.55-.45-1-1-1H8c-.55 0-1 .45-1 1v8c0 .55.45 1 1 1zm1-8h6v6H9V9z M3,13h2v-2H3V13z M7,21h2v-2H7V21z M13,3h-2v2h2V3z M19,3v2h2C21,3.9,20.1,3,19,3z M5,21v-2H3C3,20.1,3.9,21,5,21z M3,17h2 v-2H3V17z M11,21h2v-2h-2V21z M19,13h2v-2h-2V13z M19,9h2V7h-2V9z M15,5h2V3h-2V5z M7.83,5L7,4.17V3h2v2H7.83z M19.83,17L19,16.17 V15h2v2H19.83z M9,15v-3.17L12.17,15H9z M2.1,3.51c-0.39,0.39-0.39,1.02,0,1.41L4.17,7H3v2h2V7.83l2,2V16c0,0.55,0.45,1,1,1h6.17 l2,2H15v2h2v-1.17l2.07,2.07c0.39,0.39,1.02,0.39,1.41,0c0.39-0.39,0.39-1.02,0-1.41L3.51,3.51C3.12,3.12,2.49,3.12,2.1,3.51z M17,8c0-0.55-0.45-1-1-1H9.83l2,2H15v3.17l2,2V8z - M16 17.01V11c0-.55-.45-1-1-1s-1 .45-1 1v6.01h-1.79c-.45 0-.67.54-.35.85l2.79 2.78c.2.19.51.19.71 0l2.79-2.78c.32-.31.09-.85-.35-.85H16zM8.65 3.35L5.86 6.14c-.32.31-.1.85.35.85H8V13c0 .55.45 1 1 1s1-.45 1-1V6.99h1.79c.45 0 .67-.54.35-.85L9.35 3.35c-.19-.19-.51-.19-.7 0z + M16 17.01V11c0-.55-.45-1-1-1s-1 .45-1 1v6.01h-1.79c-.45 0-.67.54-.35.85l2.79 2.78c.2.19.51.19.71 0l2.79-2.78c.32-.31.09-.85-.35-.85H16zM8.65 3.35L5.86 6.14c-.32.31-.1.85.35.85H8V13c0 .55.45 1 1 1s1-.45 1-1V6.99h1.79c.45 0 .67-.54.35-.85L9.35 3.35c-.19-.19-.51-.19-.7 0z + + + + + - + + + + + + + + + + +