Fix new exporter / Export hotkeys / Marvel Tokon, Gears of War E-Day, Tamas: Shadowveil support
Some checks failed
FModel QA Builder / build (push) Has been cancelled

This commit is contained in:
LongerWarrior
2026-08-11 22:56:43 +03:00
committed by GitHub
29 changed files with 549 additions and 144 deletions

View File

@@ -129,11 +129,11 @@ public partial class App
.WriteTo.Logger(lc => lc
.Filter.ByExcluding(IsConversionLibrary)
.WriteTo.Console(outputTemplate: template1, theme: AnsiConsoleTheme.Literate)
.WriteTo.File(outputTemplate: template1, path: filePath))
.WriteTo.File(outputTemplate: template1, path: filePath, shared: true))
.WriteTo.Logger(lc => lc
.Filter.ByIncludingOnly(IsConversionLibrary)
.WriteTo.Console(outputTemplate: template2, theme: AnsiConsoleTheme.Literate)
.WriteTo.File(outputTemplate: template2, path: filePath))
.WriteTo.File(outputTemplate: template2, path: filePath, shared: true))
.MinimumLevel.Override("CUE4Parse_Conversion", LogEventLevel.Verbose).WriteTo.Sink(ImGuiSink.Instance)
.CreateLogger();

View File

@@ -130,6 +130,7 @@ public enum EAssetCategory : uint
SkeletalMesh = Mesh + 2,
CustomizableObject = Mesh + 3,
NaniteDisplacedMesh = Mesh + 4,
GeometryCollection = Mesh + 5,
Texture = AssetCategoryExtensions.CategoryBase + (3 << 16),
Materials = AssetCategoryExtensions.CategoryBase + (4 << 16),
Material = Materials + 1,
@@ -165,6 +166,7 @@ public enum EAssetCategory : uint
RocoKingdomWorld = GameSpecific + 3,
DeltaForce = GameSpecific + 4,
LegoBatman = GameSpecific + 5,
ArcSys = GameSpecific + 6,
}
public enum EUnluacMode

View File

@@ -0,0 +1,18 @@
using System.Windows;
using System.Windows.Media;
namespace FModel.Extensions;
public static class VisualTreeExtensions
{
public static T FindAncestor<T>(this DependencyObject current) where T : DependencyObject
{
while (current != null)
{
if (current is T t)
return t;
current = VisualTreeHelper.GetParent(current);
}
return null;
}
}

View File

@@ -27,11 +27,13 @@ public class Hotkey : ViewModel
public bool IsTriggered(Key e)
{
return e == Key && Keyboard.Modifiers.HasFlag(Modifiers);
return Key != Key.None && e == Key && Keyboard.Modifiers.HasFlag(Modifiers);
}
public override string ToString()
{
if (Key == Key.None) return string.Empty;
var str = new StringBuilder();
if (Modifiers.HasFlag(ModifierKeys.Control))
@@ -46,4 +48,4 @@ public class Hotkey : ViewModel
str.Append(Key);
return str.ToString();
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.ComponentModel;
using System.IO;
using System.Linq;
@@ -7,6 +8,7 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using FModel.Extensions;
using FModel.Services;
using FModel.Settings;
using FModel.ViewModels;
@@ -179,6 +181,22 @@ public partial class MainWindow
_ => CategoriesSelector.SelectedIndex
};
}
else if (_applicationView.Status.IsReady && UserSettings.Default.ExportData.IsTriggered(e.Key))
OnExportHotkey("Save_Data");
else if (_applicationView.Status.IsReady && UserSettings.Default.ExportProperties.IsTriggered(e.Key))
OnExportHotkey("Save_Properties");
else if (_applicationView.Status.IsReady && UserSettings.Default.ExportTextures.IsTriggered(e.Key))
OnExportHotkey("Save_Textures");
else if (_applicationView.Status.IsReady && UserSettings.Default.ExportModels.IsTriggered(e.Key))
OnExportHotkey("Save_Models");
else if (_applicationView.Status.IsReady && UserSettings.Default.ExportWorlds.IsTriggered(e.Key))
OnExportHotkey("Save_Worlds");
else if (_applicationView.Status.IsReady && UserSettings.Default.ExportAnimations.IsTriggered(e.Key))
OnExportHotkey("Save_Animations");
else if (_applicationView.Status.IsReady && UserSettings.Default.ExportAudio.IsTriggered(e.Key))
OnExportHotkey("Save_Audio");
else if (_applicationView.Status.IsReady && UserSettings.Default.ExportCode.IsTriggered(e.Key))
OnExportHotkey("Save_Code");
else if (_applicationView.Status.IsReady && UserSettings.Default.FeaturePreviewNewAssetExplorer && UserSettings.Default.SwitchAssetExplorer.IsTriggered(e.Key))
_applicationView.IsAssetsExplorerVisible = !_applicationView.IsAssetsExplorerVisible;
else if (UserSettings.Default.AssetAddTab.IsTriggered(e.Key))
@@ -379,6 +397,22 @@ public partial class MainWindow
childFolder.IsSelected = true;
}
private void OnExportHotkey(string trigger)
{
if (!_applicationView.Status.IsReady || Keyboard.FocusedElement is not DependencyObject focused)
return;
IList selection = focused.FindAncestor<TreeView>() == AssetsFolderName
? new[] { AssetsFolderName.SelectedItem }
: focused.FindAncestor<ListBox>()?.SelectedItems;
var exportable = selection?.OfType<object>().Where(static item => item is TreeItem or GameFileViewModel).ToArray() ?? [];
if (exportable.Length == 0)
return;
_applicationView.RightClickMenuCommand.Execute(new object[] { trigger, exportable });
}
private CustomPopupPlacement[] OnQueueToastCustomPopupPlacement(Size popupSize, Size targetSize, Point offset)
{
return

View File

@@ -64,12 +64,12 @@ public sealed class UserSettings : ViewModel
Default.TextureExportFormat,
Default.TextureQuality,
Default.SaveHdrTexturesAsHdr,
Default.ExportAllTextureMips,
Default.MaterialExportFormat,
Default.SaveEmbeddedMaterials,
Default.SaveMorphTargets,
Default.SocketExportFormat,
Default.CompressionFormat,
Default.ExportAllTextureMips
Default.CompressionFormat
);
}
@@ -431,6 +431,62 @@ public sealed class UserSettings : ViewModel
set => SetProperty(ref _nextAudio, value);
}
private Hotkey _exportData = new(Key.None);
public Hotkey ExportData
{
get => _exportData;
set => SetProperty(ref _exportData, value);
}
private Hotkey _exportProperties = new(Key.None);
public Hotkey ExportProperties
{
get => _exportProperties;
set => SetProperty(ref _exportProperties, value);
}
private Hotkey _exportTextures = new(Key.None);
public Hotkey ExportTextures
{
get => _exportTextures;
set => SetProperty(ref _exportTextures, value);
}
private Hotkey _exportModels = new(Key.None);
public Hotkey ExportModels
{
get => _exportModels;
set => SetProperty(ref _exportModels, value);
}
private Hotkey _exportWorlds = new(Key.None);
public Hotkey ExportWorlds
{
get => _exportWorlds;
set => SetProperty(ref _exportWorlds, value);
}
private Hotkey _exportAnimations = new(Key.None);
public Hotkey ExportAnimations
{
get => _exportAnimations;
set => SetProperty(ref _exportAnimations, value);
}
private Hotkey _exportAudio = new(Key.None);
public Hotkey ExportAudio
{
get => _exportAudio;
set => SetProperty(ref _exportAudio, value);
}
private Hotkey _exportCode = new(Key.None);
public Hotkey ExportCode
{
get => _exportCode;
set => SetProperty(ref _exportCode, value);
}
private EMeshFormat _meshExportFormat = EMeshFormat.UEFormat;
public EMeshFormat MeshExportFormat
{

View File

@@ -48,6 +48,7 @@ using CUE4Parse.UE4.Assets.Exports.StaticMesh;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Assets.Exports.Verse;
using CUE4Parse.UE4.Assets.Exports.Wwise;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.BinaryConfig;
using CUE4Parse.UE4.CriWare;
using CUE4Parse.UE4.CriWare.Readers;
@@ -86,6 +87,7 @@ using Serilog;
using SkiaSharp;
using Svg.Skia;
using UE4Config.Parsing;
using static CUE4Parse.UE4.Versions.EGame;
using Application = System.Windows.Application;
using FGuid = CUE4Parse.UE4.Objects.Core.Misc.FGuid;
@@ -192,24 +194,24 @@ public class CUE4ParseViewModel : ViewModel
{
Provider = versionContainer.Game switch
{
EGame.GAME_StateOfDecay2 => new DefaultFileProvider(new DirectoryInfo(gameDirectory),
GAME_StateOfDecay2 => new DefaultFileProvider(new DirectoryInfo(gameDirectory),
[
new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\Paks"),
new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\DisabledPaks")
], SearchOption.AllDirectories, versionContainer, pathComparer),
EGame.GAME_eFootball => new DefaultFileProvider(new DirectoryInfo(gameDirectory),
GAME_eFootball => new DefaultFileProvider(new DirectoryInfo(gameDirectory),
[
new(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\KONAMI\\eFootball\\ST\\Download")
], SearchOption.AllDirectories, versionContainer, pathComparer),
EGame.GAME_DeadByDaylight => new DefaultFileProvider(new DirectoryInfo(gameDirectory),
GAME_DeadByDaylight => new DefaultFileProvider(new DirectoryInfo(gameDirectory),
[
new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\DeadByDaylight\\Saved\\PersistentDownloadDir\\DynamicContent")
], SearchOption.AllDirectories, versionContainer, pathComparer),
EGame.GAME_AshEchoes => new AEDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer),
EGame.GAME_BlackStigma => new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, StringComparer.Ordinal),
EGame.GAME_HonorofKingsWorld => new HoKWDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer),
EGame.GAME_LordOfMysteries => new LoMDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer),
EGame.GAME_ArcRaiders => new TheiaFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer),
GAME_AshEchoes => new AEDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer),
GAME_BlackStigma => new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, StringComparer.Ordinal),
GAME_HonorofKingsWorld => new HoKWDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer),
GAME_LordOfMysteries => new LoMDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer),
GAME_ArcRaiders or GAME_Highguard or GAME_MARVELTokonFightingSouls => new TheiaFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer),
_ => new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer)
};
@@ -514,7 +516,7 @@ public class CUE4ParseViewModel : ViewModel
FLogger.Text("Additive animations have their reference pose stripped, which will lead to inaccurate preview and export", Constants.WHITE, true));
}
if (Provider.Versions.Game is EGame.GAME_UE4_LATEST or EGame.GAME_UE5_LATEST && !Provider.ProjectName.Equals("FortniteGame", StringComparison.OrdinalIgnoreCase)) // ignore fortnite globally
if (Provider.Versions.Game is GAME_UE4_LATEST or GAME_UE5_LATEST && !Provider.ProjectName.Equals("FortniteGame", StringComparison.OrdinalIgnoreCase)) // ignore fortnite globally
{
FLogger.Append(ELog.Warning, () =>
FLogger.Text($"Experimental UE version selected, likely unsuitable for '{Provider.GameDisplayName ?? Provider.ProjectName}'", Constants.WHITE, true));
@@ -734,17 +736,17 @@ public class CUE4ParseViewModel : ViewModel
break;
}
case "dat" when Provider.Versions.Game is EGame.GAME_Aion2:
case "dat" when Provider.Versions.Game is GAME_Aion2:
{
ProcessAion2DatFile(entry, updateUi, saveProperties);
break;
}
case "bytes" when Provider.Versions.Game is EGame.GAME_RocoKingdomWorld:
case "bytes" when Provider.Versions.Game is GAME_RocoKingdomWorld:
{
ProcessRocoBinFile(entry, updateUi, saveProperties);
break;
}
case "dbc" when Provider.Versions.Game is EGame.GAME_AshesOfCreation:
case "dbc" when Provider.Versions.Game is GAME_AshesOfCreation:
{
ProcessCacheDBFile(entry, updateUi, saveProperties);
break;
@@ -820,8 +822,8 @@ public class CUE4ParseViewModel : ViewModel
case "py":
case "md":
case "h":
case "non" when Provider.Versions.Game is EGame.GAME_RocoKingdomWorld:
case "cam" when Provider.Versions.Game is EGame.GAME_RocoKingdomWorld:
case "non" when Provider.Versions.Game is GAME_RocoKingdomWorld:
case "cam" when Provider.Versions.Game is GAME_RocoKingdomWorld:
// Uncharted Waters Origin
case "crn":
case "uwt":
@@ -841,7 +843,7 @@ public class CUE4ParseViewModel : ViewModel
break;
}
case "ebd" when Provider.Versions.Game is EGame.GAME_ArcRaiders:
case "ebd" when Provider.Versions.Game is GAME_ArcRaiders:
case "json":
case "Json":
{
@@ -982,7 +984,7 @@ public class CUE4ParseViewModel : ViewModel
break;
}
case "ustbin" when Provider.Versions.Game is EGame.GAME_DeltaForce:
case "ustbin" when Provider.Versions.Game is GAME_DeltaForce:
{
var archive = entry.CreateReader();
var ustbin = new FDeltaStringTable(archive);
@@ -1453,6 +1455,11 @@ public class CUE4ParseViewModel : ViewModel
SaveAndPlaySound(cancellationToken, outputPath, audioFormat, data, saveAudio, updateUi);
}
if (Provider.Versions.Game is GAME_GearsofWarEDay && akMediaAsset.CustomGameData is FByteBulkData bulkData)
{
var shouldDecompress = UserSettings.Default.CompressedAudioMode is ECompressedAudio.PlayDecompressed;
SaveAndPlaySound(cancellationToken, outputPath, "WEM", bulkData.Data, saveAudio, updateUi);
}
return false;
}
case UAkAudioEventData when (isNone || saveAudio) && pointer.Object.Value is UAkAudioEventData akAudioEventData:
@@ -1470,6 +1477,12 @@ public class CUE4ParseViewModel : ViewModel
SaveAndPlaySound(cancellationToken, outputPath, audioFormat, data, saveAudio, updateUi);
}
if (Provider.Versions.Game is GAME_GearsofWarEDay && akMediaAsset.CustomGameData is FByteBulkData bulkData)
{
var audioName = akMediaAsset.MediaName ?? $"{akAudioEventData.Outer.Name} ({akMediaAsset.ID})";
var outputPath = Path.Combine(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/').SubstringBeforeLast('/'), audioName);
SaveAndPlaySound(cancellationToken, outputPath, "WEM", bulkData.Data, saveAudio, updateUi);
}
}
}
return false;
@@ -1487,7 +1500,7 @@ public class CUE4ParseViewModel : ViewModel
// Borderlands 4
case UFaceFXAnimSet when (isNone || saveAudio) && pointer.Object.Value is UFaceFXAnimSet faceFXAnimSet:
{
if (Provider.Versions.Game is not EGame.GAME_Borderlands4)
if (Provider.Versions.Game is not GAME_Borderlands4)
return false;
var ownerDirectory = WwiseProvider.GetOwnerDirectory(faceFXAnimSet);

View File

@@ -61,7 +61,6 @@ public class ExportOptionsViewModel : ViewModel
set
{
if (!SetProperty(ref field, value)) return;
RaisePropertyChanged(nameof(SocketSettingsEnabled));
RaisePropertyChanged(nameof(CompressionSettingsEnabled));
RaisePropertyChanged(nameof(TextureFormatsEnabled));
@@ -105,8 +104,6 @@ public class ExportOptionsViewModel : ViewModel
set => SetProperty(ref field, value);
}
public bool SocketSettingsEnabled => SelectedMeshFormat == EMeshFormat.ActorX;
public IEnumerable<EFileCompressionFormat> CompressionFormats { get; } = Enum.GetValues<EFileCompressionFormat>();
public EFileCompressionFormat SelectedCompressionFormat
{
@@ -228,11 +225,11 @@ public class ExportOptionsViewModel : ViewModel
SelectedTextureFormat,
TextureQuality,
ExportHdrTexturesAsHdr,
ExportAllTextureMips,
SelectedMaterialDepth,
ExportMaterials,
ExportMorphTargets,
SelectedSocketFormat,
SelectedCompressionFormat,
ExportAllTextureMips
SelectedCompressionFormat
);
}

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -15,6 +17,7 @@ using FModel.Extensions;
using FModel.Framework;
using FModel.Settings;
using FModel.Views;
using FModel.Views.Resources.Controls;
using FModel.Views.Snooper;
using Serilog.Events;
@@ -175,9 +178,9 @@ public class ExportSessionViewModel : ViewModel
});
}
public async Task ExportAsync()
public async Task<IReadOnlyList<ExportResult>> ExportAsync()
{
if (IsRunning || Session.TotalQueued == 0) return;
if (IsRunning || Session.TotalQueued == 0) return null;
IsRunning = true;
IsFinished = false;
@@ -218,9 +221,10 @@ public class ExportSessionViewModel : ViewModel
});
});
IReadOnlyList<ExportResult> results = null;
try
{
await Session.RunAsync(exportDirectory, exportOptions, progress, _cts.Token).ConfigureAwait(false);
results = await Session.RunAsync(exportDirectory, exportOptions, progress, _cts.Token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -237,14 +241,86 @@ public class ExportSessionViewModel : ViewModel
UpdateElapsedAndEta();
});
}
return results;
}
public async Task ExportAutomaticallyAsync()
{
if (UserSettings.Default.ExportImmediately)
if (!UserSettings.Default.ExportImmediately) return;
var results = await ExportAsync();
if (results is { Count: > 0 }) LogSummary(results);
}
private static void LogSummary(IReadOnlyList<ExportResult> results)
{
if (results.Count == 1)
{
await ExportAsync();
var result = results[0];
switch (result.Success)
{
case true when result.DiskFilePaths is { Count: > 0 } files:
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully exported ", Constants.WHITE);
FLogger.Link(Path.GetFileName(files[0]), files[0], true);
});
break;
case false when result.Error is { } exception:
FLogger.Append(exception);
break;
}
return;
}
var failed = 0;
var groups = new Dictionary<string, (int Count, string[] Source, string[] Directory)>(StringComparer.OrdinalIgnoreCase);
foreach (var result in results)
{
if (!result.Success)
{
failed++;
continue;
}
if (result.DiskFilePaths == null) continue;
foreach (var file in result.DiskFilePaths)
{
var extension = Path.GetExtension(file).TrimStart('.');
var source = SplitDirectory(result.ObjectPath);
var directory = SplitDirectory(file);
groups[extension] = groups.TryGetValue(extension, out var group)
? (group.Count + 1, CommonPrefix(group.Source, source), CommonPrefix(group.Directory, directory))
: (1, source, directory);
}
}
foreach (var (extension, group) in groups)
{
var source = string.Join('/', group.Source);
var directory = string.Join(Path.DirectorySeparatorChar, group.Directory);
FLogger.Append(ELog.Information, () =>
{
FLogger.Text($"Successfully exported {group.Count} {extension} from ", Constants.WHITE);
if (directory.Length > 0) FLogger.Link(source, directory, true);
else FLogger.Text(source, Constants.WHITE, true);
});
}
if (failed > 0)
{
FLogger.Append(ELog.Error, () => FLogger.Text($"Failed to export {failed} asset{(failed == 1 ? "" : "s")}, open the Export Session window for more details.", Constants.WHITE, true));
}
}
private static string[] SplitDirectory(string path) => Path.GetDirectoryName(path)?.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) ?? [];
private static string[] CommonPrefix(string[] a, string[] b)
{
var i = 0;
while (i < a.Length && i < b.Length && string.Equals(a[i], b[i], StringComparison.OrdinalIgnoreCase)) i++;
return i == a.Length ? a : a[..i];
}
public void CancelExport()
@@ -384,6 +460,7 @@ public class ClassGroupViewModel(string name) : ViewModel
public class ObjectGroupViewModel(string path) : ViewModel
{
public string Path { get; } = path;
public string Directory { get; } = path.SubstringBeforeLast('/');
public string Name { get; } = path.SubstringAfterLast('.');
public ObservableCollection<LogEntryViewModel> Entries { get; } = [];
@@ -426,4 +503,29 @@ public class LogEntryViewModel(LogEvent log)
_ => log.Exception?.Message ?? log.RenderMessage()
};
public Exception? Exception { get; } = log.Exception;
public IReadOnlyList<ExceptionDetailsViewModel> ExceptionDetails { get; } =
log.Exception is { } exception ? [new ExceptionDetailsViewModel(exception)] : [];
}
public class ExceptionDetailsViewModel
{
public string Header { get; }
public string Details { get; }
public ExceptionDetailsViewModel(Exception exception)
{
var text = exception.ToString().ReplaceLineEndings("\n");
var newline = text.IndexOf('\n');
if (newline < 0)
{
Header = text;
Details = string.Empty;
}
else
{
Header = text[..newline];
Details = text[(newline + 1)..];
}
}
}

View File

@@ -11,6 +11,7 @@ using CUE4Parse.GameTypes.Borderlands3.Assets.Exports;
using CUE4Parse.GameTypes.Borderlands4.Assets.Exports;
using CUE4Parse.GameTypes.FN.Assets.Exports.DataAssets;
using CUE4Parse.GameTypes.LegoBatman.Assets;
using CUE4Parse.GameTypes.RED.Assets.Exports;
using CUE4Parse.GameTypes.SMG.UE4.Assets.Exports.Wwise;
using CUE4Parse.GameTypes.SMG.UE4.Assets.Objects;
using CUE4Parse.GameTypes.SquareEnix.UE4.Assets.Exports;
@@ -26,6 +27,7 @@ using CUE4Parse.UE4.Assets.Exports.Engine.Font;
using CUE4Parse.UE4.Assets.Exports.Fmod;
using CUE4Parse.UE4.Assets.Exports.FMod;
using CUE4Parse.UE4.Assets.Exports.Foliage;
using CUE4Parse.UE4.Assets.Exports.GeometryCollection;
using CUE4Parse.UE4.Assets.Exports.Internationalization;
using CUE4Parse.UE4.Assets.Exports.LevelSequence;
using CUE4Parse.UE4.Assets.Exports.Material;
@@ -217,6 +219,7 @@ public class GameFileViewModel(GameFile asset) : ViewModel
USkeletalMesh => (EAssetCategory.SkeletalMesh, EBulkType.Meshes),
UCustomizableObject => (EAssetCategory.CustomizableObject, EBulkType.None),
UNaniteDisplacedMesh => (EAssetCategory.NaniteDisplacedMesh, EBulkType.None),
UGeometryCollection => (EAssetCategory.GeometryCollection, EBulkType.None),
UTexture => (EAssetCategory.Texture, EBulkType.Textures),
@@ -265,6 +268,7 @@ public class GameFileViewModel(GameFile asset) : ViewModel
UGbxGraphAsset or UDialogScriptData or UDialogPerformanceData when GameVersion is EGame.GAME_Borderlands4 or EGame.GAME_Borderlands3 => (EAssetCategory.Borderlands, EBulkType.Audio), // Borderlands 4; Borderlands 3;
UFaceFXAnimSet when GameVersion is EGame.GAME_Borderlands4 => (EAssetCategory.Borderlands, EBulkType.Audio), // Borderlands 4;
UWubAudioEvent or UWubDialogueEvent when GameVersion is EGame.GAME_LEGOBatmanLegacyoftheDarkKnight => (EAssetCategory.LegoBatman, EBulkType.Audio), // Lego Batman: Legacy of the Dark Knight;
UREDBinaryObject => (EAssetCategory.ArcSys, EBulkType.None), // Arc System Works games;
_ => (EAssetCategory.All, EBulkType.None),
};

View File

@@ -517,8 +517,16 @@ public class TabControlViewModel : ViewModel
}
public event EventHandler OnTabRemove;
public void GoLeftTab() => SelectedTab = _tabItems.Previous(SelectedTab);
public void GoRightTab() => SelectedTab = _tabItems.Next(SelectedTab);
public void GoLeftTab()
{
if (_tabItems.Count > 0)
SelectedTab = _tabItems.Previous(SelectedTab);
}
public void GoRightTab()
{
if (_tabItems.Count > 0)
SelectedTab = _tabItems.Next(SelectedTab);
}
public void RemoveOtherTabs(TabItem tab)
{

View File

@@ -131,8 +131,25 @@
</Setter>
</Style>
<Style x:Key="ExceptionTreeViewItemStyle"
TargetType="TreeViewItem">
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
<Setter Property="Focusable"
Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeViewItem">
<ContentPresenter ContentSource="Header"
HorizontalAlignment="Stretch"
Margin="0,2,8,6" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="LogEntryTemplate" DataType="{x:Type vm:LogEntryViewModel}">
<Grid Margin="0 1" ToolTip="{Binding Exception}">
<Grid Margin="0 1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
@@ -182,7 +199,6 @@
<DataTemplate x:Key="ObjectGroupHeaderTemplate" DataType="{x:Type vm:ObjectGroupViewModel}">
<Grid VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
@@ -196,11 +212,19 @@
</Canvas>
</Viewbox>
<TextBlock Grid.Column="1" Text="{Binding Name}" VerticalAlignment="Center"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
ToolTip="{Binding Path}" />
<TextBlock Grid.Column="1" Margin="0,0,5,0"
VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"
ToolTip="{Binding Path}">
<Run Text="{Binding Name, Mode=OneWay}"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
<Run Text=" - "
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<Run Text="{Binding Directory, Mode=OneWay}" FontSize="11"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
</TextBlock>
<Button Grid.Column="3" ToolTip="Remove from queue" Click="OnRemoveFromQueueClick"
<Button Grid.Column="2" ToolTip="Remove from queue" Click="OnRemoveFromQueueClick"
Visibility="{Binding DataContext.CanExport, RelativeSource={RelativeSource AncestorType=adonisControls:AdonisWindow}, Converter={StaticResource BoolToVisibilityConverter}}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Padding="0" Margin="0" Focusable="False"
Foreground="#EF5350" Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}">
@@ -212,7 +236,7 @@
</Viewbox>
</Button>
<Button Grid.Column="4" ToolTip="Open in Explorer" Click="OnOpenInExplorerClick"
<Button Grid.Column="3" ToolTip="Open in Explorer" Click="OnOpenInExplorerClick"
Tag="{Binding FirstFilePath}"
Visibility="{Binding HasFilePath, Converter={StaticResource BoolToVisibilityConverter}}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}"
@@ -362,7 +386,7 @@
<ProgressBar Grid.Row="1" Height="5" Margin="0 5 0 5" Maximum="1" Value="{Binding ProgressValue, Mode=OneWay, FallbackValue=0.5}" />
<TabControl Grid.Row="2" adonisExtensions:LayerExtension.Layer="2"
<TabControl x:Name="Tabs" Grid.Row="2" adonisExtensions:LayerExtension.Layer="2"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer0BackgroundBrush}}">
<TabItem Header="Export Logs">
<Grid>
@@ -389,7 +413,8 @@
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
VirtualizingPanel.ScrollUnit="Item"
ScrollViewer.CanContentScroll="True">
ScrollViewer.CanContentScroll="True"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem" BasedOn="{StaticResource StretchTreeViewItemStyle}">
<Style.Triggers>
@@ -414,9 +439,43 @@
HorizontalAlignment="Stretch" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type vm:LogEntryViewModel}">
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource LogEntryTemplate}" />
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type vm:LogEntryViewModel}"
ItemsSource="{Binding ExceptionDetails}"
ItemContainerStyle="{StaticResource ExceptionTreeViewItemStyle}">
<ContentPresenter Content="{Binding}"
ContentTemplate="{StaticResource LogEntryTemplate}"
HorizontalAlignment="Stretch" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type vm:ExceptionDetailsViewModel}">
<Border Padding="8"
CornerRadius="3"
BorderThickness="2,0,0,0"
BorderBrush="#EF5350"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer1BackgroundBrush}}">
<RichTextBox IsReadOnly="True"
IsDocumentEnabled="False"
MaxHeight="220"
FontSize="10"
BorderThickness="0"
Padding="0"
Background="Transparent"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<FlowDocument PagePadding="0">
<Paragraph Margin="0">
<Run Text="{Binding Header, Mode=OneWay}"
FontWeight="SemiBold"
Foreground="#EF5350" />
<LineBreak />
<Run Text="{Binding Details, Mode=OneWay}"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
</Paragraph>
</FlowDocument>
</RichTextBox>
</Border>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>

View File

@@ -16,7 +16,10 @@ public partial class ExportSessionWindow
private async void OnExportClick(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: ExportSessionViewModel { CanExport: true } viewModel })
{
Tabs.SelectedIndex = 0;
await viewModel.ExportAsync();
}
}
private void OnOkClick(object sender, RoutedEventArgs e)

View File

@@ -58,4 +58,5 @@
<SolidColorBrush x:Key="RocoKingdomWorldBrush" Color="#fecf4d"></SolidColorBrush>
<SolidColorBrush x:Key="DeltaForceBrush" Color="LightGreen"></SolidColorBrush>
<SolidColorBrush x:Key="BatmanBrush" Color="Gold"></SolidColorBrush>
<SolidColorBrush x:Key="ArcSysBrush" Color="Red"></SolidColorBrush>
</ResourceDictionary>

View File

@@ -122,7 +122,8 @@
</Viewbox>
</MenuItem.Icon>
<MenuItem Command="{Binding RightClickMenuCommand}">
<MenuItem Command="{Binding RightClickMenuCommand}"
InputGestureText="{Binding ExportData, Source={x:Static settings:UserSettings.Default}}">
<MenuItem.Header>
<TextBlock
Text="{Binding PlacementTarget.SelectedItem.Asset.Extension,
@@ -144,7 +145,9 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Properties (.json)" Command="{Binding RightClickMenuCommand}">
<MenuItem Header="Properties (.json)"
Command="{Binding RightClickMenuCommand}"
InputGestureText="{Binding ExportProperties, Source={x:Static settings:UserSettings.Default}}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Properties" />
@@ -159,7 +162,9 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Texture" Command="{Binding RightClickMenuCommand}">
<MenuItem Header="Texture"
Command="{Binding RightClickMenuCommand}"
InputGestureText="{Binding ExportTextures, Source={x:Static settings:UserSettings.Default}}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Textures" />
@@ -185,7 +190,9 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Model" Command="{Binding RightClickMenuCommand}">
<MenuItem Header="Model"
Command="{Binding RightClickMenuCommand}"
InputGestureText="{Binding ExportModels, Source={x:Static settings:UserSettings.Default}}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Models" />
@@ -211,7 +218,9 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="World" Command="{Binding RightClickMenuCommand}">
<MenuItem Header="World"
Command="{Binding RightClickMenuCommand}"
InputGestureText="{Binding ExportWorlds, Source={x:Static settings:UserSettings.Default}}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Worlds" />
@@ -241,7 +250,9 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Animation" Command="{Binding RightClickMenuCommand}">
<MenuItem Header="Animation"
Command="{Binding RightClickMenuCommand}"
InputGestureText="{Binding ExportAnimations, Source={x:Static settings:UserSettings.Default}}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Animations" />
@@ -267,7 +278,9 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Audio" Command="{Binding RightClickMenuCommand}">
<MenuItem Header="Audio"
Command="{Binding RightClickMenuCommand}"
InputGestureText="{Binding ExportAudio, Source={x:Static settings:UserSettings.Default}}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Audio" />

View File

@@ -1,5 +1,6 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:settings="clr-namespace:FModel.Settings"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
x:Class="FModel.Views.Resources.Controls.ContextMenus.FolderContextMenuDictionary">
@@ -18,7 +19,8 @@
</MenuItem.Icon>
<MenuItem Header="Raw Data (.uasset)"
Command="{Binding RightClickMenuCommand}">
Command="{Binding RightClickMenuCommand}"
InputGestureText="{Binding ExportData, Source={x:Static settings:UserSettings.Default}}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Data" />
@@ -38,7 +40,8 @@
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Properties (.json)"
Command="{Binding RightClickMenuCommand}">
Command="{Binding RightClickMenuCommand}"
InputGestureText="{Binding ExportProperties, Source={x:Static settings:UserSettings.Default}}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Properties" />
@@ -58,7 +61,8 @@
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Textures"
Command="{Binding RightClickMenuCommand}">
Command="{Binding RightClickMenuCommand}"
InputGestureText="{Binding ExportTextures, Source={x:Static settings:UserSettings.Default}}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Textures" />
@@ -78,7 +82,8 @@
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Decompiled Blueprints"
Command="{Binding RightClickMenuCommand}">
Command="{Binding RightClickMenuCommand}"
InputGestureText="{Binding ExportCode, Source={x:Static settings:UserSettings.Default}}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Code" />
@@ -98,7 +103,8 @@
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Models"
Command="{Binding RightClickMenuCommand}">
Command="{Binding RightClickMenuCommand}"
InputGestureText="{Binding ExportModels, Source={x:Static settings:UserSettings.Default}}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Models" />
@@ -118,7 +124,8 @@
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Worlds"
Command="{Binding RightClickMenuCommand}">
Command="{Binding RightClickMenuCommand}"
InputGestureText="{Binding ExportWorlds, Source={x:Static settings:UserSettings.Default}}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Worlds" />
@@ -143,7 +150,8 @@
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Animations"
Command="{Binding RightClickMenuCommand}">
Command="{Binding RightClickMenuCommand}"
InputGestureText="{Binding ExportAnimations, Source={x:Static settings:UserSettings.Default}}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Animations" />
@@ -163,7 +171,8 @@
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Audio"
Command="{Binding RightClickMenuCommand}">
Command="{Binding RightClickMenuCommand}"
InputGestureText="{Binding ExportAudio, Source={x:Static settings:UserSettings.Default}}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Audio" />

View File

@@ -3,6 +3,7 @@ using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using FModel.Extensions;
using FModel.Services;
using FModel.Settings;
using FModel.ViewModels;
@@ -23,7 +24,7 @@ public partial class FolderContextMenuDictionary
if (sender is not ContextMenu { PlacementTarget: FrameworkElement fe } menu)
return;
var listBox = FindAncestor<ListBox>(fe);
var listBox = fe.FindAncestor<ListBox>();
if (listBox != null)
{
menu.DataContext = listBox.DataContext;
@@ -31,7 +32,7 @@ public partial class FolderContextMenuDictionary
return;
}
var treeView = FindAncestor<TreeView>(fe);
var treeView = fe.FindAncestor<TreeView>();
if (treeView != null)
{
menu.DataContext = treeView.DataContext;
@@ -39,17 +40,6 @@ public partial class FolderContextMenuDictionary
}
}
private static T FindAncestor<T>(DependencyObject current) where T : DependencyObject
{
while (current != null)
{
if (current is T t)
return t;
current = VisualTreeHelper.GetParent(current);
}
return null;
}
private void OnFavoriteDirectoryClick(object sender, RoutedEventArgs e)
{
if (sender is not MenuItem { CommandParameter: IEnumerable<object> list } || list.FirstOrDefault() is not TreeItem folder)

View File

@@ -47,14 +47,22 @@
<Button Grid.Row="0" Grid.Column="4" Content="..." MinWidth="28" Click="OnBrowseOutputDirectory" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Export Immediately" VerticalAlignment="Center" Margin="0 8 0 0"
Visibility="{Binding ShowExportImmediatelyOption, Converter={StaticResource BoolToVisibilityConverter}}"
ToolTip="Start exporting automatically after all selected assets have been queued." />
<CheckBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 8 0 0"
IsChecked="{Binding ExportImmediately, Mode=TwoWay}"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}"
Visibility="{Binding ShowExportImmediatelyOption, Converter={StaticResource BoolToVisibilityConverter}}"
ToolTip="Start exporting automatically after all selected assets have been queued." />
Visibility="{Binding ShowExportImmediatelyOption, Converter={StaticResource BoolToVisibilityConverter}}" />
<Grid Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 8 0 0"
Visibility="{Binding ShowExportImmediatelyOption, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0"
IsChecked="{Binding ExportImmediately, Mode=TwoWay}"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="5 0 0 0" FontSize="11"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}"
Text="Start exporting automatically after all selected assets have been queued." />
</Grid>
<Separator Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" Tag="MESH" Margin="0 10 0 10" />
@@ -117,12 +125,10 @@
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="6" Grid.Column="0" Text="Socket Format" VerticalAlignment="Center" Margin="0 0 0 5"
IsEnabled="{Binding SocketSettingsEnabled}" />
<TextBlock Grid.Row="6" Grid.Column="0" Text="Socket Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5"
ItemsSource="{Binding SocketFormats}"
SelectedItem="{Binding SelectedSocketFormat, Mode=TwoWay}"
IsEnabled="{Binding SocketSettingsEnabled}">
SelectedItem="{Binding SelectedSocketFormat, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />

View File

@@ -0,0 +1,24 @@
using System;
using System.Windows;
using System.Windows.Controls;
namespace FModel.Views.Resources.Controls;
public class HotkeyPresenter : ItemsControl
{
public static readonly DependencyProperty GestureProperty = DependencyProperty.Register(
nameof(Gesture), typeof(string), typeof(HotkeyPresenter), new PropertyMetadata(null, OnGestureChanged));
public string Gesture
{
get => (string) GetValue(GestureProperty);
set => SetValue(GestureProperty, value);
}
private static void OnGestureChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((HotkeyPresenter) d).ItemsSource = e.NewValue is string gesture
? gesture.Split('+', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
: null;
}
}

View File

@@ -89,4 +89,4 @@ public class HotkeyTextBox : TextBox
break;
}
}
}
}

View File

@@ -34,6 +34,7 @@ public class FileToGeometryConverter : IMultiValueConverter
EAssetCategory.SkeletalMesh => ("SkeletalMeshIconAlt", "NeutralBrush"),
EAssetCategory.CustomizableObject => ("StaticMeshIconAlt", "CustomizableObjectBrush"),
EAssetCategory.NaniteDisplacedMesh => ("StaticMeshIconAlt", "NaniteDisplacedMeshBrush"),
EAssetCategory.GeometryCollection => ("GeometryCollectionIcon", "NeutralBrush"),
EAssetCategory.Material => ("MaterialIcon", "MaterialBrush"),
EAssetCategory.MaterialEditorData => ("MaterialIcon", "MaterialEditorBrush"),
@@ -93,11 +94,13 @@ public class FileToGeometryConverter : IMultiValueConverter
EAssetCategory.ByteCode => ("CodeIcon", "CodeBrush"),
// Game specific below
EAssetCategory.Borderlands => ("BorderlandsIcon", "BorderlandsBrush"),
EAssetCategory.Aion2 => ("AionIcon", "AionBrush"),
EAssetCategory.RocoKingdomWorld => ("RocoKingdomWorldIcon", "RocoKingdomWorldBrush"),
EAssetCategory.DeltaForce => ("DeltaForceIcon", "DeltaForceBrush"),
EAssetCategory.LegoBatman => ("BatmanIcon", "BatmanBrush"),
EAssetCategory.ArcSys => ("ArcSysIcon", "ArcSysBrush"),
_ => ("AssetIcon", "NeutralBrush")
};

File diff suppressed because one or more lines are too long

View File

@@ -1124,6 +1124,28 @@
</Setter>
</Style>
<Style TargetType="{x:Type controls:HotkeyPresenter}">
<Setter Property="Focusable" Value="False" />
<Setter Property="IsHitTestVisible" Value="False" />
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border Margin="2, 0, 0, 0" Padding="5, 2" CornerRadius="3"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer1HighlightBrush}}">
<TextBlock Text="{Binding}" FontSize="10" />
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Type MenuItem}" TargetType="{x:Type MenuItem}" BasedOn="{StaticResource {x:Type MenuItem}}">
<Setter Property="Template">
<Setter.Value>
@@ -1175,13 +1197,13 @@
HorizontalAlignment="Stretch"
Margin="{Binding Margin, ElementName=ItemHeader}"/>
<TextBlock x:Name="RippleLayerInputGesturePresenter"
Grid.Column="2"
Text="{TemplateBinding InputGestureText}"
TextElement.Foreground="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type adonisControls:RippleHost}}}"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
Margin="{Binding Margin, ElementName=InputGesturePresenter}"/>
<controls:HotkeyPresenter x:Name="RippleLayerInputGesturePresenter"
Grid.Column="2"
Gesture="{TemplateBinding InputGestureText}"
TextElement.Foreground="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type adonisControls:RippleHost}}}"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Margin="{Binding Margin, ElementName=InputGesturePresenter}"/>
<Viewbox x:Name="RippleLayerArrowPresenter"
Grid.Column="3"
@@ -1227,13 +1249,13 @@
HorizontalAlignment="Stretch"
Margin="4, 0"/>
<TextBlock x:Name="InputGesturePresenter"
Grid.Column="3"
Text="{TemplateBinding InputGestureText}"
Foreground="{TemplateBinding Foreground}"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
Margin="4, 0"/>
<controls:HotkeyPresenter x:Name="InputGesturePresenter"
Grid.Column="3"
Gesture="{TemplateBinding InputGestureText}"
TextElement.Foreground="{TemplateBinding Foreground}"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Margin="4, 0"/>
<Viewbox x:Name="ArrowPresenter"
Grid.Column="4"

View File

@@ -428,6 +428,15 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
@@ -435,24 +444,24 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Left Switch on Directory Tab" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="0" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding DirLeftTab, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Right Switch on Directory Tab" VerticalAlignment="Center" />
<controls:HotkeyTextBox Grid.Row="1" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}"
HotKey="{Binding DirRightTab, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<Separator Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Style="{StaticResource CustomSeparator}" />
<!-- <TextBlock Grid.Row="0" Grid.Column="0" Text="Left Switch on Directory Tab" VerticalAlignment="Center" Margin="0 0 0 5" /> -->
<!-- <controls:HotkeyTextBox Grid.Row="0" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5" -->
<!-- HotKey="{Binding DirLeftTab, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" /> -->
<!-- <TextBlock Grid.Row="1" Grid.Column="0" Text="Right Switch on Directory Tab" VerticalAlignment="Center" /> -->
<!-- <controls:HotkeyTextBox Grid.Row="1" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" -->
<!-- HotKey="{Binding DirRightTab, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" /> -->
<!-- -->
<!-- <Separator Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Style="{StaticResource CustomSeparator}" /> -->
<TextBlock Grid.Row="3" Grid.Column="0" Text="Switch Asset Explorer" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="3" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding SwitchAssetExplorer, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="Left Switch on Asset Tab" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="4" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding AssetLeftTab, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="5" Grid.Column="0" Text="Right Switch on Asset Tab" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="5" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding AssetRightTab, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<!-- <TextBlock Grid.Row="4" Grid.Column="0" Text="Left Switch on Asset Tab" VerticalAlignment="Center" Margin="0 0 0 5" /> -->
<!-- <controls:HotkeyTextBox Grid.Row="4" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5" -->
<!-- HotKey="{Binding AssetLeftTab, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" /> -->
<!-- <TextBlock Grid.Row="5" Grid.Column="0" Text="Right Switch on Asset Tab" VerticalAlignment="Center" Margin="0 0 0 5" /> -->
<!-- <controls:HotkeyTextBox Grid.Row="5" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5" -->
<!-- HotKey="{Binding AssetRightTab, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" /> -->
<TextBlock Grid.Row="6" Grid.Column="0" Text="Add Asset Tab" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="6" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding AssetAddTab, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
@@ -462,20 +471,47 @@
<Separator Grid.Row="8" Grid.Column="0" Grid.ColumnSpan="3" Style="{StaticResource CustomSeparator}" />
<TextBlock Grid.Row="9" Grid.Column="0" Text="Add Audio File" VerticalAlignment="Center" Margin="0 0 0 5" />
<TextBlock Grid.Row="9" Grid.Column="0" Text="Export Raw Data" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="9" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding AddAudio, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="10" Grid.Column="0" Text="Play / Pause Current Audio" VerticalAlignment="Center" Margin="0 0 0 5" />
HotKey="{Binding ExportData, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="10" Grid.Column="0" Text="Export Properties" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="10" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding PlayPauseAudio, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="11" Grid.Column="0" Text="Previous Audio" VerticalAlignment="Center" Margin="0 0 0 5" />
HotKey="{Binding ExportProperties, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="11" Grid.Column="0" Text="Export Textures" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="11" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding PreviousAudio, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="12" Grid.Column="0" Text="Next Audio" VerticalAlignment="Center" Margin="0 0 0 5" />
HotKey="{Binding ExportTextures, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="12" Grid.Column="0" Text="Export Models" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="12" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding NextAudio, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="13" Grid.Column="0" Text="Remove Selected Audio" VerticalAlignment="Center" Margin="0 0 0 5" />
HotKey="{Binding ExportModels, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="13" Grid.Column="0" Text="Export Worlds" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="13" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding ExportWorlds, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="14" Grid.Column="0" Text="Export Animations" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="14" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding ExportAnimations, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="15" Grid.Column="0" Text="Export Audio" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="15" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding ExportAudio, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="16" Grid.Column="0" Text="Export Decompiled Blueprints" VerticalAlignment="Center" />
<controls:HotkeyTextBox Grid.Row="16" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}"
HotKey="{Binding ExportCode, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<Separator Grid.Row="17" Grid.Column="0" Grid.ColumnSpan="3" Style="{StaticResource CustomSeparator}" />
<TextBlock Grid.Row="18" Grid.Column="0" Text="Add Audio File" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="18" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding AddAudio, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="19" Grid.Column="0" Text="Play / Pause Current Audio" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="19" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding PlayPauseAudio, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="20" Grid.Column="0" Text="Previous Audio" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="20" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding PreviousAudio, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="21" Grid.Column="0" Text="Next Audio" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="21" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding NextAudio, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="22" Grid.Column="0" Text="Remove Selected Audio" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="22" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding RemoveAudio, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
</Grid>
</DataTemplate>
@@ -549,14 +585,13 @@
ToolTip="Emulate Luaj's permissive parser">
</CheckBox>
<Separator Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="11" Style="{StaticResource CustomSeparator}" />
<Separator Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="11"
Tag="OPCODE MAPPING"
Style="{StaticResource CustomSeparator}" />
<TextBlock Grid.Row="3"
Grid.Column="0"
Text="OpcodeMap"
VerticalAlignment="Top"
Margin="0 0 0 5"/>
<TextBox Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="10"
<TextBox Grid.Row="3" Grid.ColumnSpan="12"
Margin="0 0 0 5" Height="300"
FontSize="14" Padding="8"
HorizontalAlignment="Stretch" VerticalAlignment="Top"
@@ -703,7 +738,7 @@
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource KeyboardIcon}" />
</Canvas>
</Viewbox>
<TextBlock Text="Keybindings" HorizontalAlignment="Left" VerticalAlignment="Center" />
<TextBlock Text="Hotkeys" HorizontalAlignment="Left" VerticalAlignment="Center" />
</StackPanel>
</TreeViewItem.Header>
</TreeViewItem>

View File

@@ -252,13 +252,15 @@ public partial class SettingsView
if (sender is not TextBox textBox)
return;
string input = textBox.Text?.Trim() ?? string.Empty;
var input = textBox.Text.Trim();
if (string.IsNullOrEmpty(input))
return;
if (TryParseKey(input, out ulong parsed))
{
_applicationView.SettingsView.CriwareDecryptionKey = 0;
}
else if (TryParseKey(input, out ulong parsed))
{
_applicationView.SettingsView.CriwareDecryptionKey = parsed;
}
}
private static bool TryParseKey(string text, out ulong value)

View File

@@ -136,9 +136,9 @@ public class StaticModel : UModel<MeshVertex>
}
Box = staticMesh.Bounds * Constants.SCALE_DOWN_RATIO;
for (int i = 0; i < export.Sockets.Length; i++)
foreach (var s in export.Sockets ?? [])
{
if (export.Sockets[i].Load<UStaticMeshSocket>() is not { } socket) continue;
if (s.Load<UStaticMeshSocket>() is not { } socket) continue;
Sockets.Add(new Socket(socket));
}
}

View File

@@ -191,7 +191,6 @@ public class Options
{
var guid = o.LightingGuid;
if (Textures.TryGetValue(guid, out texture)) return texture != null;
if (o.Format == EPixelFormat.PF_BC6H) return false; // BC6H is not supported by Decode thus randomly crashes the app
var bitmap = o switch
{

View File

@@ -160,9 +160,10 @@ public class Renderer : IDisposable
model.Skeleton.Animate(animSet);
Options.AddAnimation(animation);
foreach (var notifyEvent in animBase.Notifies)
foreach (var notifyEvent in animBase.Notifies ?? [])
{
if (!notifyEvent.NotifyStateClass.TryLoad(out UObject notifyClass) ||
if (notifyEvent.NotifyStateClass is null ||
!notifyEvent.NotifyStateClass.TryLoad(out UObject notifyClass) ||
!notifyClass.TryGetValue(out UObject export, "SkeletalMeshProp", "StaticMeshProp", "Mesh", "SkeletalMeshTemplate"))
continue;