Merge branch '4sval:dev' into ExportingFix

This commit is contained in:
Krowe Moh 2026-05-19 07:46:32 +10:00 committed by GitHub
commit 521fe894c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 590 additions and 19 deletions

@ -1 +1 @@
Subproject commit a3821bdc34ef75f10a2003092ad2502dc648e53a
Subproject commit dc70cfbc92436bd8c6308a5139c1baa94bd37799

View File

@ -162,4 +162,11 @@ public enum EAssetCategory : uint
Borderlands = GameSpecific + 1,
Aion2 = GameSpecific + 2,
RocoKingdomWorld = GameSpecific + 3,
DeltaForce = GameSpecific + 4,
}
public enum EUnluacMode
{
Decompile,
Disassemble,
}

View File

@ -14,6 +14,7 @@ public static class AvalonExtensions
private static readonly IHighlightingDefinition _cppHighlighter = LoadHighlighter("Cpp.xshd");
private static readonly IHighlightingDefinition _changelogHighlighter = LoadHighlighter("Changelog.xshd");
private static readonly IHighlightingDefinition _verseHighlighter = LoadHighlighter("Verse.xshd");
private static readonly IHighlightingDefinition _luaHighlighter = LoadHighlighter("Lua.xshd");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static IHighlightingDefinition LoadHighlighter(string resourceName)
@ -29,6 +30,9 @@ public static class AvalonExtensions
{
switch (ext)
{
case "lua":
case "luac":
return _luaHighlighter;
case "ini":
case "csv":
return _iniHighlighter;

View File

@ -81,6 +81,7 @@
<None Remove="Resources\Xml.xshd" />
<None Remove="Resources\Cpp.xshd" />
<None Remove="Resources\Changelog.xshd" />
<None Remove="Resources\Lua.xshd" />
<None Remove="Resources\unix.png" />
<None Remove="Resources\linux.png" />
<None Remove="Resources\stateofdecay2.png" />
@ -129,6 +130,7 @@
<EmbeddedResource Include="Resources\Verse.xshd" />
<EmbeddedResource Include="Resources\Xml.xshd" />
<EmbeddedResource Include="Resources\Cpp.xshd" />
<EmbeddedResource Include="Resources\Lua.xshd" />
<EmbeddedResource Include="Resources\Changelog.xshd" />
<EmbeddedResource Include="Resources\default.frag" />
<EmbeddedResource Include="Resources\default.vert" />

View File

@ -124,7 +124,8 @@ public partial class MainWindow
{
if (UserSettings.Default.DiscordRpc == EDiscordRpc.Always)
_discordHandler.Initialize(_applicationView.GameDisplayName);
})
}),
UserSettings.Default.DecompileLua ? ApplicationViewModel.InitUnluac() : Task.CompletedTask
).ConfigureAwait(false);
#if DEBUG

230
FModel/Resources/Lua.xshd Normal file
View File

@ -0,0 +1,230 @@
<SyntaxDefinition name="Lua" extensions=".lua;.luac" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008">
<Color name="Keyword1" foreground="#C586C0" fontWeight="bold" />
<Color name="Keyword2" foreground="#569CD6" fontWeight="bold" />
<Color name="Comment" foreground="#6A9955" />
<Color name="String" foreground="#D69D85" />
<Color name="Number" foreground="#B5CEA8" />
<Color name="Function" foreground="#DCDCAA" />
<Color name="Punctuation" foreground="#89DDFF" />
<Color name="ObjectName" foreground="#3DC9B0" />
<Color name="Constant" foreground="#9CDCFE" />
<RuleSet>
<Rule color="Comment">--.*$</Rule>
<Rule color="String">&quot;([^&quot;\\]|\\.)*&quot;</Rule>
<Rule color="String">&apos;([^&apos;\\]|\\.)*&apos;</Rule>
<Rule color="Number">\b\d+\.\d+([eE][+-]?\d+)?\b</Rule>
<Rule color="Number">\b\d+[eE][+-]?\d+\b</Rule>
<Rule color="Number">\b\d+\b</Rule>
<Keywords color="Keyword1">
<Word>return</Word>
<Word>function</Word>
<Word>goto</Word>
<Word>end</Word>
<Word>if</Word>
<Word>else</Word>
<Word>elseif</Word>
<Word>then</Word>
<Word>for</Word>
<Word>in</Word>
<Word>until</Word>
<Word>while</Word>
<Word>break</Word>
<Word>or</Word>
<Word>and</Word>
<Word>repeat</Word>
<Word>do</Word>
</Keywords>
<Keywords color="Keyword2">
<Word>local</Word>
<Word>nil</Word>
<Word>not</Word>
<Word>true</Word>
<Word>false</Word>
</Keywords>
<Keywords color="Function">
<!-- Core functions -->
<Word>assert</Word>
<Word>collectgarbage</Word>
<Word>error</Word>
<Word>ipairs</Word>
<Word>next</Word>
<Word>pairs</Word>
<Word>pcall</Word>
<Word>print</Word>
<Word>rawequal</Word>
<Word>rawget</Word>
<Word>rawlen</Word>
<Word>rawset</Word>
<Word>select</Word>
<Word>setmetatable</Word>
<Word>tonumber</Word>
<Word>tostring</Word>
<Word>type</Word>
<Word>xpcall</Word>
<Word>getmetatable</Word>
<Word>require</Word>
<Word>module</Word>
<!-- Modules / tables -->
<Word>math</Word>
<Word>string</Word>
<Word>table</Word>
<Word>coroutine</Word>
<Word>os</Word>
<Word>io</Word>
<Word>utf8</Word>
<Word>bit32</Word>
<Word>package</Word>
<Word>debug</Word>
<!-- Bit32 / bitwise functions -->
<Word>arshift</Word>
<Word>band</Word>
<Word>bnot</Word>
<Word>bor</Word>
<Word>bxor</Word>
<Word>btest</Word>
<Word>extract</Word>
<Word>lrotate</Word>
<Word>lshift</Word>
<Word>replace</Word>
<Word>rrotate</Word>
<Word>rshift</Word>
<!-- Coroutine functions -->
<Word>create</Word>
<Word>resume</Word>
<Word>running</Word>
<Word>status</Word>
<Word>wrap</Word>
<Word>yield</Word>
<Word>isyieldable</Word>
<!-- Debug functions -->
<Word>getuservalue</Word>
<Word>gethook</Word>
<Word>getinfo</Word>
<Word>getlocal</Word>
<Word>getregistry</Word>
<Word>getupvalue</Word>
<Word>upvaluejoin</Word>
<Word>upvalueid</Word>
<Word>setuservalue</Word>
<Word>sethook</Word>
<Word>setlocal</Word>
<Word>setupvalue</Word>
<Word>traceback</Word>
<!-- IO functions -->
<Word>close</Word>
<Word>flush</Word>
<Word>input</Word>
<Word>lines</Word>
<Word>open</Word>
<Word>output</Word>
<Word>popen</Word>
<Word>read</Word>
<Word>tmpfile</Word>
<Word>seek</Word>
<Word>setvbuf</Word>
<Word>write</Word>
<!-- String functions -->
<Word>byte</Word>
<Word>char</Word>
<Word>dump</Word>
<Word>find</Word>
<Word>format</Word>
<Word>gmatch</Word>
<Word>gsub</Word>
<Word>len</Word>
<Word>lower</Word>
<Word>match</Word>
<Word>rep</Word>
<Word>reverse</Word>
<Word>sub</Word>
<Word>upper</Word>
<Word>pack</Word>
<Word>packsize</Word>
<Word>unpack</Word>
<Word>concat</Word>
<Word>maxn</Word>
<Word>insert</Word>
<Word>move</Word>
<Word>offset</Word>
<Word>codepoint</Word>
<Word>codes</Word>
<Word>charpattern</Word>
<!-- OS / Time functions -->
<Word>clock</Word>
<Word>date</Word>
<Word>difftime</Word>
<Word>execute</Word>
<Word>exit</Word>
<Word>getenv</Word>
<Word>remove</Word>
<Word>rename</Word>
<Word>setlocale</Word>
<Word>time</Word>
<Word>loadlib</Word>
<Word>searchpath</Word>
<Word>seeall</Word>
<Word>preload</Word>
<Word>cpath</Word>
<Word>path</Word>
<Word>searchers</Word>
<Word>loaded</Word>
<!-- Math functions / constants -->
<Word>abs</Word>
<Word>acos</Word>
<Word>asin</Word>
<Word>atan</Word>
<Word>atan2</Word>
<Word>ceil</Word>
<Word>cos</Word>
<Word>cosh</Word>
<Word>deg</Word>
<Word>exp</Word>
<Word>floor</Word>
<Word>fmod</Word>
<Word>ult</Word>
<Word>log</Word>
<Word>log10</Word>
<Word>max</Word>
<Word>min</Word>
<Word>modf</Word>
<Word>pi</Word>
<Word>rad</Word>
<Word>random</Word>
<Word>randomseed</Word>
<Word>sin</Word>
<Word>sqrt</Word>
<Word>tan</Word>
<Word>sinh</Word>
<Word>tanh</Word>
<Word>pow</Word>
<Word>frexp</Word>
<Word>ldexp</Word>
<Word>huge</Word>
<Word>maxinteger</Word>
<Word>mininteger</Word>
</Keywords>
<Rule color="Punctuation">(\|)|(&lt;&lt;)|(&gt;&gt;)|(\/\/)|(==)|(~=)|(&lt;=)|(&gt;=)|(&lt;)|(&gt;)|(=)|(\()|(\))|(\{)|(\})|(\[)|(\])|(::)|(:)|(;)|(,)|(\.\.\.)|(\.\.)|(\.)|[+\-*%\^#&amp;~]</Rule>
<Rule color="ObjectName">(?&lt;=function\s)[A-Za-z0-9_]+(?=\.)</Rule>
<Rule color="Function">(?&lt;=\.)[A-Za-z0-9_]+(?=\()</Rule>
<Rule color="Function">(?&lt;=function\s)[A-Za-z0-9_]+(?=\s*\()</Rule> <!-- Standalone function name -->
<Rule color="Constant">\b[A-Z_][A-Z0-9_]*\b</Rule>
</RuleSet>
</SyntaxDefinition>

View File

@ -25,7 +25,8 @@ public class DirectorySettings : ViewModel, ICloneable
Directories = old?.Directories ?? CustomDirectory.Default(gameName),
AesKeys = old?.AesKeys ?? new AesResponse { MainKey = aes, DynamicKeys = null },
LastAesReload = old?.LastAesReload ?? DateTime.Today.AddDays(-1),
CriwareDecryptionKey = old?.CriwareDecryptionKey ?? 0
CriwareDecryptionKey = old?.CriwareDecryptionKey ?? 0,
UnluacOpCodeMap = old?.UnluacOpCodeMap ?? ""
};
}
@ -106,6 +107,13 @@ public class DirectorySettings : ViewModel, ICloneable
set => SetProperty(ref _criwareDecryptionKey, value);
}
private string _unluacOpCodeMap;
public string UnluacOpCodeMap
{
get => _unluacOpCodeMap;
set => SetProperty(ref _unluacOpCodeMap, value);
}
private bool Equals(DirectorySettings other)
{
return GameDirectory == other.GameDirectory && UeVersion == other.UeVersion;

View File

@ -11,6 +11,7 @@ using CUE4Parse_Conversion.Animations;
using CUE4Parse_Conversion.Meshes;
using CUE4Parse_Conversion.Textures;
using CUE4Parse_Conversion.UEFormat.Enums;
using CUE4Parse.UE4.Lua.unluac;
using FModel.Framework;
using FModel.ViewModels;
using FModel.ViewModels.ApiEndpoints.Models;
@ -280,6 +281,36 @@ namespace FModel.Settings
set => SetProperty(ref _convertAudioOnBulkExport, value);
}
private bool _decompileLua;
public bool DecompileLua
{
get => _decompileLua;
set => SetProperty(ref _decompileLua, value);
}
[JsonIgnore]
public EUnluacMode UnluacMode
{
get => UnluacFlags.HasFlag(EUnluacFlags.Disassemble) ? EUnluacMode.Disassemble : EUnluacMode.Decompile;
set
{
var withoutMode = UnluacFlags & ~(EUnluacFlags.Decompile | EUnluacFlags.Disassemble);
var modeFlag = value == EUnluacMode.Disassemble ? EUnluacFlags.Disassemble : EUnluacFlags.Decompile;
UnluacFlags = withoutMode | modeFlag;
}
}
private EUnluacFlags _unluacFlags = EUnluacFlags.Decompile;
public EUnluacFlags UnluacFlags
{
get => _unluacFlags;
set
{
if (!SetProperty(ref _unluacFlags, value)) return;
RaisePropertyChanged(nameof(UnluacMode));
}
}
private IDictionary<string, DirectorySettings> _perDirectory = new Dictionary<string, DirectorySettings>();
public IDictionary<string, DirectorySettings> PerDirectory
{

View File

@ -9,6 +9,7 @@ using System.Windows;
using CUE4Parse_Conversion.Textures.BC;
using CUE4Parse.Compression;
using CUE4Parse.Encryption.Aes;
using CUE4Parse.UE4.Lua.unluac;
using CUE4Parse.UE4.Objects.Core.Misc;
using CUE4Parse.UE4.VirtualFileSystem;
using FModel.Extensions;
@ -340,4 +341,12 @@ public class ApplicationViewModel : ViewModel
DetexHelper.Initialize(detexPath);
}
public static async Task InitUnluac()
{
var unluacPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", UnluacHelper.DllName);
await UnluacHelper.InitializeAsync(unluacPath).ConfigureAwait(false);
if (UnluacHelper.Instance is null)
FLogger.Append(ELog.Error, () => FLogger.Text("Failed to download unluac", Constants.WHITE, true));
}
}

View File

@ -545,7 +545,7 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
if (Spectrum != null && PlayedFile.PlaybackState == PlaybackState.Playing)
{
FftData = new float[4096];
FftData = new float[4096+4];
Spectrum.GetFftData(FftData);
RaiseSourcePropertyChangedEvent(ESourceProperty.FftData, FftData);
}

View File

@ -23,6 +23,8 @@ using CUE4Parse.GameTypes.AshEchoes.FileProvider;
using CUE4Parse.GameTypes.Borderlands3.Assets.Exports;
using CUE4Parse.GameTypes.Borderlands4.Assets.Exports;
using CUE4Parse.GameTypes.Borderlands4.Wwise;
using CUE4Parse.GameTypes.DFHO.Assets.Objects;
using CUE4Parse.GameTypes.HonorOfKings.FileProvider;
using CUE4Parse.GameTypes.KRD.Assets.Exports;
using CUE4Parse.GameTypes.RocoKingdomWorld.Assets.Objects;
using CUE4Parse.GameTypes.SMG.UE4.Assets.Exports.Wwise;
@ -41,12 +43,14 @@ 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;
using CUE4Parse.UE4.FMod;
using CUE4Parse.UE4.IO;
using CUE4Parse.UE4.Localization;
using CUE4Parse.UE4.Lua.unluac;
using CUE4Parse.UE4.Objects.Core.Serialization;
using CUE4Parse.UE4.Objects.Engine;
using CUE4Parse.UE4.Objects.UObject;
@ -194,6 +198,7 @@ public class CUE4ParseViewModel : ViewModel
], SearchOption.AllDirectories, versionContainer, pathComparer),
_ when versionContainer.Game is EGame.GAME_AshEchoes => new AEDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer),
_ when versionContainer.Game is EGame.GAME_BlackStigma => new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, StringComparer.Ordinal),
_ when versionContainer.Game is EGame.GAME_HonorofKingsWorld => new HoKWDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer),
_ => new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer)
};
@ -395,6 +400,16 @@ public class CUE4ParseViewModel : ViewModel
});
}
private ITypeMappingsProvider SelectMappingsProvider(string path)
{
if (path.EndsWith(".jmap.gz", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".jmap", StringComparison.OrdinalIgnoreCase))
{
return new JmapTypeMappingsProvider(path);
}
return new FileUsmapTypeMappingsProvider(path);
}
public Task InitMappings(bool force = false)
{
if (!UserSettings.IsEndpointValid(EEndpointType.Mapping, out var endpoint))
@ -408,7 +423,7 @@ public class CUE4ParseViewModel : ViewModel
var l = ELog.Information;
if (endpoint.Overwrite && File.Exists(endpoint.FilePath))
{
Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(endpoint.FilePath);
Provider.MappingsContainer = SelectMappingsProvider(endpoint.FilePath);
}
else if (endpoint.IsValid)
{
@ -434,7 +449,7 @@ public class CUE4ParseViewModel : ViewModel
_apiEndpointView.DownloadFile(mapping.Url, mappingPath);
}
Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(mappingPath);
Provider.MappingsContainer = SelectMappingsProvider(mappingPath);
break;
}
}
@ -695,6 +710,18 @@ public class CUE4ParseViewModel : ViewModel
ProcessCacheDBFile(entry, updateUi, saveProperties);
break;
}
case "luac":
case "lua":
{
var data = Provider.SaveAsset(entry);
byte[] decompiled = ProcessLuaFile(data);
using var stream = new MemoryStream(decompiled);
using var reader = new StreamReader(stream);
TabControl.SelectedTab.SetDocumentText(reader.ReadToEnd(), saveProperties, updateUi);
break;
}
case "upluginmanifest":
case "code-workspace":
case "projectstore":
@ -717,13 +744,13 @@ public class CUE4ParseViewModel : ViewModel
case "verse":
case "html":
case "json5":
case "json":
case "uref":
case "cube":
case "usda":
case "ocio":
case "data" when Provider.ProjectName is "OakGame":
case "scss":
case "yaml":
case "ini":
case "txt":
case "log":
@ -746,11 +773,11 @@ public class CUE4ParseViewModel : ViewModel
case "apx":
case "udn":
case "doc":
case "lua":
case "vdf":
case "yml":
case "js":
case "po":
case "py":
case "md":
case "h":
case "non" when Provider.Versions.Game is EGame.GAME_RocoKingdomWorld:
@ -772,6 +799,17 @@ public class CUE4ParseViewModel : ViewModel
break;
}
case "json":
{
var data = Provider.SaveAsset(entry);
using var stream = new MemoryStream(data) { Position = 0 };
using var reader = new StreamReader(stream);
var parsedJson = JsonConvert.DeserializeObject(reader.ReadToEnd());
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(parsedJson, Formatting.Indented), saveProperties, updateUi);
break;
}
case "locmeta":
{
var archive = entry.CreateReader();
@ -828,7 +866,7 @@ public class CUE4ParseViewModel : ViewModel
case "pck":
{
var archive = entry.CreateReader();
var wwise = new WwiseReader(archive, new WwiseGameFileSource(entry));
var wwise = new WwiseReader(new FWwiseArchive(archive), new WwiseGameFileSource(entry));
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(wwise, Formatting.Indented), saveProperties, updateUi);
var medias = WwiseProvider.ExtractBankSounds(wwise);
@ -893,6 +931,13 @@ public class CUE4ParseViewModel : ViewModel
break;
}
case "ustbin" when Provider.Versions.Game is EGame.GAME_DeltaForce:
{
var archive = entry.CreateReader();
var ustbin = new FDeltaStringTable(archive);
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(ustbin, Formatting.Indented), saveProperties, updateUi);
break;
}
case "png":
case "jpg":
case "bmp":
@ -960,7 +1005,6 @@ public class CUE4ParseViewModel : ViewModel
break;
}
case "res": // just skip
case "luac": // compiled lua
case "bytes": // wuthering waves
break;
default:
@ -1066,6 +1110,53 @@ public class CUE4ParseViewModel : ViewModel
}
}
private byte[] ProcessLuaFile(byte[] data)
{
var result = EUnluacErrorCode.Ok;
byte[] output = [];
if (BitConverter.ToUInt32(data) == UnluacHelper.LuaMagic && UnluacHelper.Instance is not null)
{
// opcodemap patch
byte[] opmapData = Provider.Versions.Game switch
{
_ => [],
};
var flags = UserSettings.Default.UnluacFlags;
var opcodemap = UserSettings.Default.CurrentDir.UnluacOpCodeMap;
if (!string.IsNullOrWhiteSpace(opcodemap))
{
opmapData = Encoding.UTF8.GetBytes(opcodemap);
flags |= EUnluacFlags.OpCodeMap;
}
else if (opmapData is { Length: > 12 })
{
flags |= EUnluacFlags.OpCodeMapPatch;
}
result = UnluacHelper.Decompile(data, opmapData, (uint)flags, out output, out var log);
if (result != EUnluacErrorCode.Ok && log.Length > 0)
{
Log.Error(Encoding.UTF8.GetString(log));
}
}
else
{
result = EUnluacErrorCode.Error;
}
var decompiled = result switch
{
EUnluacErrorCode.Ok => output,
#if DEBUG
EUnluacErrorCode.PartialDecompile => output,
#endif
_ => data,
};
return decompiled;
}
public void ExtractAndScroll(CancellationToken cancellationToken, string fullPath, string objectName, string parentExportType)
{
Log.Information("User CTRL-CLICKED to extract '{FullPath}'", fullPath);
@ -1240,8 +1331,8 @@ public class CUE4ParseViewModel : ViewModel
{
var data = squareEnixObject switch
{
USQEXSEADSoundBank sqexSoundBank => sqexSoundBank.SQEXSoundBankData?.Data ?? [],
USQEXSEADSound sqexSound => sqexSound.SQEXSoundData?.Data ?? [],
USQEXSEADSoundBank sqexSoundBank => sqexSoundBank.SQEXSoundBankData?.ReadDataOnce() ?? [],
USQEXSEADSound sqexSound => sqexSound.SQEXSoundData?.ReadDataOnce() ?? [],
_ => [],
};
var sabPath = Path.Combine(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/').SubstringBeforeLast('/'), squareEnixObject.Name);

View File

@ -446,6 +446,9 @@ public class GameFileViewModel(GameFile asset) : ViewModel
case "cam" when GameVersion is EGame.GAME_RocoKingdomWorld:
AssetCategory = EAssetCategory.RocoKingdomWorld;
break;
case "ustbin" when GameVersion is EGame.GAME_DeltaForce:
AssetCategory = EAssetCategory.DeltaForce;
break;
default:
AssetCategory = EAssetCategory.All; // just so it sets resolved
break;

View File

@ -172,6 +172,13 @@ public class SettingsViewModel : ViewModel
set => SetProperty(ref _criwareDecryptionKey, value);
}
private string _unluacOpcodeMap;
public string UnluacOpcodeMap
{
get => _unluacOpcodeMap;
set => SetProperty(ref _unluacOpcodeMap, value);
}
public bool SocketSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.ActorX;
public bool CompressionSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.UEFormat;
@ -237,6 +244,7 @@ public class SettingsViewModel : ViewModel
_optionsSnapshot = UserSettings.Default.CurrentDir.Versioning.Options;
_mapStructTypesSnapshot = UserSettings.Default.CurrentDir.Versioning.MapStructTypes;
_criwareDecryptionKey = UserSettings.Default.CurrentDir.CriwareDecryptionKey;
_unluacOpcodeMap = UserSettings.Default.CurrentDir.UnluacOpCodeMap;
AesEndpoint = UserSettings.Default.CurrentDir.Endpoints[0];
MappingEndpoint = UserSettings.Default.CurrentDir.Endpoints[1];
@ -273,6 +281,7 @@ public class SettingsViewModel : ViewModel
SelectedMaterialExportFormat = _materialExportFormatSnapshot;
SelectedTextureExportFormat = _textureExportFormatSnapshot;
CriwareDecryptionKey = _criwareDecryptionKey;
UnluacOpcodeMap = _unluacOpcodeMap;
SelectedAesReload = UserSettings.Default.AesReload;
SelectedDiscordRpc = UserSettings.Default.DiscordRpc;
@ -314,6 +323,7 @@ public class SettingsViewModel : ViewModel
UserSettings.Default.CurrentDir.Versioning.Options = SelectedOptions;
UserSettings.Default.CurrentDir.Versioning.MapStructTypes = SelectedMapStructTypes;
UserSettings.Default.CurrentDir.CriwareDecryptionKey = CriwareDecryptionKey;
UserSettings.Default.CurrentDir.UnluacOpCodeMap = UnluacOpcodeMap;
UserSettings.Default.AssetLanguage = SelectedAssetLanguage;
UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio;

View File

@ -53,4 +53,5 @@
<SolidColorBrush x:Key="BorderlandsBrush" Color="Yellow"></SolidColorBrush>
<SolidColorBrush x:Key="AionBrush" Color="DeepSkyBlue"></SolidColorBrush>
<SolidColorBrush x:Key="RocoKingdomWorldBrush" Color="#fecf4d"></SolidColorBrush>
<SolidColorBrush x:Key="DeltaForceBrush" Color="LightGreen"></SolidColorBrush>
</ResourceDictionary>

View File

@ -68,7 +68,7 @@ public partial class DropOverlay : UserControl
}
else if (_dragStatus is DragStatus.File)
{
TitleText.Text = "Drop .usmap to import";
TitleText.Text = "Drop usmap/jmap to import";
DescriptionText.Text = "Mapping file will be applied immediately";
}
}
@ -125,7 +125,6 @@ public partial class DropOverlay : UserControl
if (!_applicationView.Status.IsReady || !e.Data.GetDataPresent(DataFormats.FileDrop) || e.Data.GetData(DataFormats.FileDrop) is not string[] files)
return;
bool directorySelectorIsVisible = _applicationView.Status.Kind is EStatusKind.Configuring;
if (!directorySelectorIsVisible && (Helper.IsWindowOpen<DictionaryEditor>() || Helper.IsWindowOpen<EndpointEditor>()))
{
@ -145,7 +144,9 @@ public partial class DropOverlay : UserControl
_dragStatus = DragStatus.Folder;
return;
}
else if (File.Exists(path) && Path.GetExtension(path).Equals(".usmap", StringComparison.OrdinalIgnoreCase))
else if (File.Exists(path) && path.EndsWith(".usmap", StringComparison.OrdinalIgnoreCase) ||
path.EndsWith(".jmap", StringComparison.OrdinalIgnoreCase) ||
path.EndsWith(".jmap.gz", StringComparison.OrdinalIgnoreCase))
{
_path = path;
_dragStatus = DragStatus.File;

View File

@ -0,0 +1,32 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace FModel.Views.Resources.Converters;
public sealed class EnumFlagToBoolConverter : IValueConverter
{
public static readonly EnumFlagToBoolConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is null || parameter is null) return false;
var enumType = value.GetType();
if (!enumType.IsEnum) return false;
var flag = parameter is string s
? Enum.Parse(enumType, s, ignoreCase: true)
: parameter;
var current = System.Convert.ToInt64(value);
var wanted = System.Convert.ToInt64(flag);
return (current & wanted) == wanted;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

View File

@ -92,6 +92,7 @@ public class FileToGeometryConverter : IMultiValueConverter
EAssetCategory.Borderlands => ("BorderlandsIcon", "BorderlandsBrush"),
EAssetCategory.Aion2 => ("AionIcon", "AionBrush"),
EAssetCategory.RocoKingdomWorld => ("RocoKingdomWorldIcon", "RocoKingdomWorldBrush"),
EAssetCategory.DeltaForce => ("DeltaForceIcon", "DeltaForceBrush"),
_ => ("AssetIcon", "NeutralBrush")
};

File diff suppressed because one or more lines are too long

View File

@ -44,6 +44,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
@ -237,26 +238,32 @@
IsChecked="{Binding ShowDecompileOption, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
<TextBlock Grid.Row="19"
<TextBlock Grid.Row="19" Grid.Column="0" Text="Decompile Lua" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Decompile/Disassemble compiled lua files with an unluac." />
<CheckBox Grid.Row="19" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding DecompileLua, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}"
Checked="OnDecompileLuaChanged" />
<TextBlock Grid.Row="20"
Grid.Column="0"
Text="Convert Audio During Export (.wav)"
VerticalAlignment="Center"
Margin="0 0 0 5" />
<CheckBox Grid.Row="19"
<CheckBox Grid.Row="20"
Grid.Column="2"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding ConvertAudioOnBulkExport, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Margin="0 5 0 10"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
<TextBlock Grid.Row="20"
<TextBlock Grid.Row="21"
Grid.Column="0"
Text="CRIWARE Decryption Key"
VerticalAlignment="Center"
Margin="0 0 0 10" />
<TextBox x:Name="CriwareKeyBox"
Grid.Row="20"
Grid.Row="21"
Grid.Column="2"
Grid.ColumnSpan="5"
Margin="0 5 0 10"
@ -608,6 +615,98 @@
HotKey="{Binding RemoveAudio, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="unluacTemplate">
<Grid adonisExtensions:LayerExtension.Layer="2">
<Grid.RowDefinitions>
<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" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Mode" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="10"
SelectedValuePath="Tag"
SelectedValue="{Binding UnluacMode, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Margin="0 0 0 5">
<ComboBoxItem Content="Decompile" Tag="{x:Static local:EUnluacMode.Decompile}" />
<ComboBoxItem Content="Disassemble" Tag="{x:Static local:EUnluacMode.Disassemble}" />
</ComboBox>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Raw string" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Tag="RawString"
Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="3"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"
IsChecked="{Binding UnluacFlags, Source={x:Static local:Settings.UserSettings.Default},
Converter={x:Static converters:EnumFlagToBoolConverter.Instance}, ConverterParameter=RawString, Mode=OneWay}"
Checked="OnUnluacFlagChanged"
Unchecked="OnUnluacFlagChanged"
ToolTip="Copy string bytes directly to output">
</CheckBox>
<TextBlock Grid.Row="1" Grid.Column="4" Text="No debug" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Tag="NoDebug"
Grid.Row="1" Grid.Column="6" Grid.ColumnSpan="3"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"
IsChecked="{Binding UnluacFlags, Source={x:Static local:Settings.UserSettings.Default},
Converter={x:Static converters:EnumFlagToBoolConverter.Instance}, ConverterParameter=NoDebug, Mode=OneWay}"
Checked="OnUnluacFlagChanged"
Unchecked="OnUnluacFlagChanged"
ToolTip="Ignore debugging information in input file">
</CheckBox>
<TextBlock Grid.Row="1" Grid.Column="8" Text="Luaj" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Tag="Luaj"
Grid.Row="1" Grid.Column="10" Grid.ColumnSpan="3"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"
IsChecked="{Binding UnluacFlags, Source={x:Static local:Settings.UserSettings.Default},
Converter={x:Static converters:EnumFlagToBoolConverter.Instance}, ConverterParameter=Luaj, Mode=OneWay}"
Checked="OnUnluacFlagChanged"
Unchecked="OnUnluacFlagChanged"
ToolTip="Emulate Luaj's permissive parser">
</CheckBox>
<Separator Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="11" 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"
Margin="0 0 0 5" Height="300"
FontSize="14" Padding="8"
HorizontalAlignment="Stretch" VerticalAlignment="Top"
VerticalContentAlignment="Top" TextAlignment="Left"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Text="{Binding SettingsView.UnluacOpcodeMap, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
AcceptsReturn="True"
AcceptsTab="True"
TextWrapping="NoWrap"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
ToolTip="Paste a custom opcode map." />
</Grid>
</DataTemplate>
</ResourceDictionary>
</adonisControls:AdonisWindow.Resources>
<Grid>
@ -685,6 +784,28 @@
</StackPanel>
</TreeViewItem.Header>
</TreeViewItem>
<TreeViewItem Tag="unluacTemplate">
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center" Margin="-20 4 7.5 4">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource LuaIcon}" />
</Canvas>
</Viewbox>
<TextBlock Text="unluac" HorizontalAlignment="Left" VerticalAlignment="Center" />
</StackPanel>
</TreeViewItem.Header>
<TreeViewItem.Style>
<Style TargetType="TreeViewItem" BasedOn="{StaticResource TreeViewItemStyle}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding DecompileLua, Source={x:Static local:Settings.UserSettings.Default}}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeViewItem.Style>
</TreeViewItem>
</TreeView>
<Grid Grid.Row="0" Grid.Column="1" Margin="{adonisUi:Space 1, 0.5}" HorizontalAlignment="Stretch">

View File

@ -6,6 +6,7 @@ using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using CUE4Parse.UE4.Lua.unluac;
using FModel.Services;
using FModel.Settings;
using FModel.ViewModels;
@ -274,4 +275,21 @@ public partial class SettingsView
Process.Start(new ProcessStartInfo(hyperlink.NavigateUri.AbsoluteUri) { UseShellExecute = true });
}
private async void OnDecompileLuaChanged(object sender, RoutedEventArgs e)
{
if (sender is CheckBox { IsChecked: true } && UnluacHelper.Instance is null)
await ApplicationViewModel.InitUnluac();
}
private void OnUnluacFlagChanged(object sender, RoutedEventArgs e)
{
if (sender is not CheckBox cb || cb.Tag is not string name) return;
if (!Enum.TryParse<EUnluacFlags>(name, true, out var flag)) return;
var current = UserSettings.Default.UnluacFlags;
var isChecked = cb.IsChecked == true;
UserSettings.Default.UnluacFlags = isChecked ? (current | flag) : (current & ~flag);
}
}