Merge branch '4sval:dev' into dev

This commit is contained in:
wenlong li 2026-06-09 11:11:22 +08:00 committed by GitHub
commit 21f3d47ccc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 368 additions and 336 deletions

View File

@ -21,10 +21,10 @@ jobs:
- name: Fetch Submodules Recursively
run: git submodule update --init --recursive
- name: .NET 8 Setup
- name: .NET 10 Setup
uses: actions/setup-dotnet@v5
with:
dotnet-version: '8.0.x'
dotnet-version: '10.0.x'
- name: .NET Restore
run: dotnet restore FModel

View File

@ -14,16 +14,16 @@ jobs:
with:
submodules: 'recursive'
- name: .NET 8 Setup
- name: .NET 10 Setup
uses: actions/setup-dotnet@v5
with:
dotnet-version: '8.0.x'
dotnet-version: '10.0.x'
- name: .NET Restore
run: dotnet restore "./FModel/FModel.slnx" -r win-x64
- name: .NET Publish
run: dotnet publish "./FModel/FModel.csproj" -c Release --no-restore --no-self-contained -r win-x64 -f net8.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false
run: dotnet publish "./FModel/FModel.csproj" -c Release --no-restore --no-self-contained -r win-x64 -f net10.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false
- name: ZIP File
uses: thedoctor0/zip-release@0.7.6

@ -1 +1 @@
Subproject commit 81458ae77d3f3230d7582a77ffd938510430a4bf
Subproject commit bf3c09087c2a4d7d812582e268ec0d275a0827fd

View File

@ -45,6 +45,7 @@ public class CreatorPackage : IDisposable
case "AthenaMusicPackItemDefinition":
case "AthenaBattleBusItemDefinition":
case "AthenaCharacterItemDefinition":
case "ExtractableItemDefinition":
case "AthenaMapMarkerItemDefinition":
case "AthenaBackpackItemDefinition":
case "CosmeticShoesItemDefinition":

View File

@ -163,6 +163,7 @@ public enum EAssetCategory : uint
Aion2 = GameSpecific + 2,
RocoKingdomWorld = GameSpecific + 3,
DeltaForce = GameSpecific + 4,
LegoBatman = GameSpecific + 5,
}
public enum EUnluacMode

View File

@ -49,6 +49,7 @@ public static class AvalonExtensions
case "bat":
case "txt":
case "pem":
case "js":
case "po":
return null;
default:

View File

@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<TargetFramework>net10.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<ApplicationIcon>FModel.ico</ApplicationIcon>
<Version>4.4.4.0</Version>
@ -152,27 +152,25 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="AdonisUI" Version="1.17.1" />
<PackageReference Include="AdonisUI.ClassicTheme" Version="1.17.1" />
<PackageReference Include="Autoupdater.NET.Official" Version="1.9.2" />
<PackageReference Include="AvalonEdit" Version="6.3.1.120" />
<PackageReference Include="CSCore" Version="1.2.1.2" />
<PackageReference Include="DiscordRichPresence" Version="1.6.1.70" />
<PackageReference Include="EpicManifestParser" Version="2.4.1" />
<PackageReference Include="EpicManifestParser.ZlibngDotNetDecompressor" Version="1.0.1" />
<PackageReference Include="EpicManifestParser" Version="3.0.0-preview.1" />
<PackageReference Include="FModel.AdonisUI" Version="1.18.0" />
<PackageReference Include="FModel.AdonisUI.ClassicTheme" Version="1.18.0" />
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="NVorbis" Version="0.10.5" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
<PackageReference Include="OpenTK" Version="4.9.4" />
<PackageReference Include="RestSharp" Version="113.0.0" />
<PackageReference Include="Serilog" Version="4.3.0" />
<PackageReference Include="RestSharp" Version="114.0.0" />
<PackageReference Include="Serilog" Version="4.3.1" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.9" />
<PackageReference Include="Svg.Skia" Version="3.2.1" />
<PackageReference Include="SkiaSharp.HarfBuzz" Version="[2.88.9]" />
<PackageReference Include="Svg.Skia" Version="[4.8.0]" /> <!--because of skia 3.0 in newer versions-->
<PackageReference Include="Twizzle.ImGui-Bundle.NET" Version="1.91.5.2" />
<PackageReference Include="VirtualizingWrapPanel" Version="2.3.2" />
<PackageReference Include="VirtualizingWrapPanel" Version="2.5.1" />
</ItemGroup>
<ItemGroup>

View File

@ -1,37 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31912.275
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FModel", "FModel.csproj", "{B1F494EA-90A6-4C24-800E-2F724A1884CA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse", "..\CUE4Parse\CUE4Parse\CUE4Parse.csproj", "{C4620341-BBB7-4384-AC7D-5082D3E0386E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse-Conversion", "..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj", "{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B1F494EA-90A6-4C24-800E-2F724A1884CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B1F494EA-90A6-4C24-800E-2F724A1884CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B1F494EA-90A6-4C24-800E-2F724A1884CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B1F494EA-90A6-4C24-800E-2F724A1884CA}.Release|Any CPU.Build.0 = Release|Any CPU
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Release|Any CPU.Build.0 = Release|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {53DB7A15-4E15-4575-9402-0110BDF2794E}
EndGlobalSection
EndGlobal

View File

@ -1,58 +1,76 @@
<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" />
<Color name="Keyword1" foreground="#d8a0df" fontWeight="bold" />
<Color name="Keyword2" foreground="#569cd6" fontWeight="bold" />
<Color name="Self" foreground="#569cd6" fontStyle="italic" />
<Color name="Function" foreground="#dcdcaa" />
<Color name="ObjectName" foreground="#4ec9b0" />
<Color name="Property" foreground="#abd4a2" />
<Color name="String" foreground="#f4ab6b" />
<Color name="Number" foreground="#ff9e64" />
<Color name="Comment" foreground="#565f89" fontStyle="italic" />
<Color name="Punctuation" foreground="#89ddff" />
<Color name="Constant" foreground="#e0af68" />
<Color name="Metamethod" foreground="#bb9af7" fontStyle="italic" />
<Color name="GotoLabel" foreground="#7dcfff" />
<Color name="Discard" foreground="#3b4261" />
<RuleSet>
<Rule color="Comment">--\[\[(.|\n)*?\]\]</Rule>
<Rule color="Comment">--.*$</Rule>
<Rule color="String">\[\[(.|\n)*?\]\]</Rule>
<Rule color="String">&quot;([^&quot;\\]|\\.)*&quot;</Rule>
<Rule color="String">&apos;([^&apos;\\]|\\.)*&apos;</Rule>
<Rule color="Number">\b0[xX][0-9A-Fa-f]+\b</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>and</Word>
<Word>break</Word>
<Word>do</Word>
<Word>else</Word>
<Word>elseif</Word>
<Word>then</Word>
<Word>end</Word>
<Word>for</Word>
<Word>function</Word>
<Word>goto</Word>
<Word>if</Word>
<Word>in</Word>
<Word>or</Word>
<Word>repeat</Word>
<Word>return</Word>
<Word>then</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>false</Word>
<Word>local</Word>
<Word>nil</Word>
<Word>not</Word>
<Word>true</Word>
<Word>false</Word>
</Keywords>
<Keywords color="Self">
<Word>self</Word>
<Word>_G</Word>
<Word>_ENV</Word>
</Keywords>
<Keywords color="Function">
<!-- Core functions -->
<Word>assert</Word>
<Word>collectgarbage</Word>
<Word>dofile</Word>
<Word>error</Word>
<Word>getmetatable</Word>
<Word>ipairs</Word>
<Word>load</Word>
<Word>loadfile</Word>
<Word>module</Word>
<Word>next</Word>
<Word>pairs</Word>
<Word>pcall</Word>
@ -61,128 +79,24 @@
<Word>rawget</Word>
<Word>rawlen</Word>
<Word>rawset</Word>
<Word>require</Word>
<Word>select</Word>
<Word>setmetatable</Word>
<Word>tonumber</Word>
<Word>tostring</Word>
<Word>type</Word>
<Word>unpack</Word>
<Word>xpcall</Word>
<Word>getmetatable</Word>
<Word>require</Word>
<Word>module</Word>
<!-- Modules / tables -->
<Word>bit32</Word>
<Word>coroutine</Word>
<Word>debug</Word>
<Word>io</Word>
<Word>math</Word>
<Word>os</Word>
<Word>package</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>
@ -195,36 +109,140 @@
<Word>exp</Word>
<Word>floor</Word>
<Word>fmod</Word>
<Word>ult</Word>
<Word>frexp</Word>
<Word>huge</Word>
<Word>ldexp</Word>
<Word>log</Word>
<Word>log10</Word>
<Word>max</Word>
<Word>maxinteger</Word>
<Word>min</Word>
<Word>mininteger</Word>
<Word>modf</Word>
<Word>pi</Word>
<Word>pow</Word>
<Word>rad</Word>
<Word>random</Word>
<Word>randomseed</Word>
<Word>sin</Word>
<Word>sinh</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>
<Word>tointeger</Word>
<Word>ult</Word>
<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>pack</Word>
<Word>packsize</Word>
<Word>rep</Word>
<Word>reverse</Word>
<Word>sub</Word>
<Word>upper</Word>
<Word>concat</Word>
<Word>insert</Word>
<Word>maxn</Word>
<Word>move</Word>
<Word>remove</Word>
<Word>sort</Word>
<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>seek</Word>
<Word>setvbuf</Word>
<Word>tmpfile</Word>
<Word>write</Word>
<Word>clock</Word>
<Word>date</Word>
<Word>difftime</Word>
<Word>execute</Word>
<Word>exit</Word>
<Word>getenv</Word>
<Word>rename</Word>
<Word>setlocale</Word>
<Word>time</Word>
<Word>create</Word>
<Word>isyieldable</Word>
<Word>resume</Word>
<Word>running</Word>
<Word>status</Word>
<Word>wrap</Word>
<Word>yield</Word>
<Word>arshift</Word>
<Word>band</Word>
<Word>bnot</Word>
<Word>bor</Word>
<Word>btest</Word>
<Word>bxor</Word>
<Word>extract</Word>
<Word>lrotate</Word>
<Word>lshift</Word>
<Word>replace</Word>
<Word>rrotate</Word>
<Word>rshift</Word>
<Word>charpattern</Word>
<Word>codepoint</Word>
<Word>codes</Word>
<Word>offset</Word>
<Word>gethook</Word>
<Word>getinfo</Word>
<Word>getlocal</Word>
<Word>getregistry</Word>
<Word>getupvalue</Word>
<Word>getuservalue</Word>
<Word>sethook</Word>
<Word>setlocal</Word>
<Word>setupvalue</Word>
<Word>setuservalue</Word>
<Word>traceback</Word>
<Word>upvalueid</Word>
<Word>upvaluejoin</Word>
<Word>cpath</Word>
<Word>loaded</Word>
<Word>loadlib</Word>
<Word>path</Word>
<Word>preload</Word>
<Word>searchers</Word>
<Word>searchpath</Word>
<Word>seeall</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="Metamethod">__[A-Za-z_][A-Za-z0-9_]*__</Rule>
<Rule color="Constant">\b[A-Z_][A-Z0-9_]*\b</Rule>
<Rule color="GotoLabel">(?&lt;=::)[A-Za-z_][A-Za-z0-9_]*(?=::)</Rule>
<Rule color="Discard">(?&lt;![A-Za-z0-9_])_(?![A-Za-z0-9_])</Rule>
<Rule color="ObjectName">(?&lt;=function\s)[A-Za-z_][A-Za-z0-9_]*(?=\.)</Rule>
<Rule color="ObjectName">(?&lt;=function\s)[A-Za-z_][A-Za-z0-9_]*(?=:)</Rule>
<Rule color="Function">(?&lt;=function\s)[A-Za-z_][A-Za-z0-9_]*(?=\s*\()</Rule>
<Rule color="ObjectName">[A-Za-z_][A-Za-z0-9_]*(?=\.)</Rule>
<Rule color="ObjectName">[A-Za-z_][A-Za-z0-9_]*(?=:)</Rule>
<Rule color="Function">(?&lt;=:)[A-Za-z_][A-Za-z0-9_]*(?=\s*\()</Rule>
<Rule color="Function">(?&lt;=\.)[A-Za-z_][A-Za-z0-9_]*(?=\s*\()</Rule>
<Rule color="Function">\b[A-Za-z_][A-Za-z0-9_]*(?=\s*\()</Rule>
<Rule color="Property">(?&lt;=\.)[A-Za-z_][A-Za-z0-9_]*</Rule>
<Rule color="Constant">\b[A-Z][A-Z0-9]*_[A-Z0-9_]*\b</Rule>
<Rule color="Constant">\b[A-Z]{2}[A-Z0-9_]*\b</Rule>
</RuleSet>
</SyntaxDefinition>

View File

@ -300,7 +300,7 @@ namespace FModel.Settings
}
}
private EUnluacFlags _unluacFlags;
private EUnluacFlags _unluacFlags = EUnluacFlags.Decompile;
public EUnluacFlags UnluacFlags
{
get => _unluacFlags;

View File

@ -53,7 +53,7 @@ public class VManifest
Header = new VHeader(Ar);
var compressedBuffer = Ar.ReadBytes((int) Header.CompressedSize);
var uncompressedBuffer = new byte[(int)Header.UncompressedSize];
ZlibHelper.Decompress(compressedBuffer, 0, compressedBuffer.Length, uncompressedBuffer, 0, uncompressedBuffer.Length);
Compression.Decompress(compressedBuffer, 0, compressedBuffer.Length, uncompressedBuffer, 0, uncompressedBuffer.Length, CompressionMethod.Zlib, Ar);
var manifestAr = new FByteArchive("UncompressedValorantManifest", uncompressedBuffer);
Chunks = manifestAr.ReadArray<VChunk>((int) Header.ChunkCount);

View File

@ -265,15 +265,8 @@ public class ApplicationViewModel : ViewModel
{
var zipDir = Path.GetDirectoryName(vgmZipFilePath)!;
await using var zipFs = File.OpenRead(vgmZipFilePath);
using var zip = new ZipArchive(zipFs, ZipArchiveMode.Read);
foreach (var entry in zip.Entries)
{
var entryPath = Path.Combine(zipDir, entry.FullName);
await using var entryFs = File.Create(entryPath);
await using var entryStream = entry.Open();
await entryStream.CopyToAsync(entryFs);
}
await using var zip = await ZipArchive.CreateAsync(zipFs, ZipArchiveMode.Read, true, null);
await zip.ExtractToDirectoryAsync(zipDir, true);
}
else
{
@ -305,9 +298,7 @@ public class ApplicationViewModel : ViewModel
oodlePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", OodleHelper.OODLE_NAME_CURRENT);
}
OodleHelper.Initialize(oodlePath);
if (OodleHelper.Instance is null)
FLogger.Append(ELog.Error, () => FLogger.Text("Failed to download Oodle", Constants.WHITE, true));
await OodleHelper.InitializeAsync(oodlePath);
}
public static async Task InitZlib()
@ -319,12 +310,12 @@ public class ApplicationViewModel : ViewModel
{
if (!await ZlibHelper.DownloadDllAsync(zlibPath))
{
FLogger.Append(ELog.Error, () => FLogger.Text("Failed to download Zlib-ng", Constants.WHITE, true));
zlibFileInfo.Refresh();
if (!zlibFileInfo.Exists) return;
}
}
ZlibHelper.Initialize(zlibPath);
await ZlibHelper.InitializeAsync(zlibPath);
}
public static async Task InitDetex()

View File

@ -570,139 +570,126 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
{
if (SelectedAudioFile?.Data == null)
return false;
if (SelectedAudioFile.Extension == "wav")
return true;
switch (SelectedAudioFile.Extension)
{
case "binka":
case "adpcm":
case "xvag":
case "opus":
case "wem":
case "at9":
case "raw":
{
if (TryConvert(out var wavFilePath))
{
var newAudio = new AudioFile(SelectedAudioFile.Id, new FileInfo(wavFilePath));
Replace(newAudio);
return true;
}
if (!TryConvert(SelectedAudioFile.FilePath, SelectedAudioFile.Data, SelectedAudioFile.Extension, out var convertedFilePath, true))
return false;
return false;
}
case "adx":
case "hca":
return TryConvertCriware();
case "rada":
{
if (TryDecode(SelectedAudioFile.Extension, out var rawFilePath))
{
var newAudio = new AudioFile(SelectedAudioFile.Id, new FileInfo(rawFilePath));
Replace(newAudio);
return true;
}
return false;
}
}
var newAudio = new AudioFile(SelectedAudioFile.Id, new FileInfo(convertedFilePath));
Replace(newAudio);
return true;
}
private bool TryConvertCriware()
public static bool TryConvert(string inputFilePath, byte[] inputFileData, string extension, out string wavFilePath, bool updateUi = false)
{
wavFilePath = string.Empty;
switch (extension.ToLowerInvariant())
{
case "hca":
case "adx":
return TryConvertCriware(inputFilePath, inputFileData, extension, out wavFilePath);
case "rada":
return TryConvertRada(inputFilePath, inputFileData, extension, out wavFilePath, updateUi);
default:
{
var vgmStreamPath = TryGetVgmstreamPath();
if (string.IsNullOrEmpty(vgmStreamPath))
return false;
var success = TryConvertToWav(inputFilePath, inputFileData, vgmStreamPath, true, out wavFilePath);
if (!success)
{
Log.Error("Failed to convert {InputFilePath} to .wav format", Path.GetFileName(inputFilePath));
if (updateUi)
{
FLogger.Append(ELog.Error, () =>
{
FLogger.Text("Failed to convert audio to .wav format. See: ", Constants.WHITE);
FLogger.Link("→ link ←", Constants.AUDIO_ISSUE_LINK, true);
});
}
}
return success;
}
}
}
private static bool TryConvertCriware(string inputFilePath, byte[] inputFileData, string extension, out string wavFilePath)
{
wavFilePath = string.Empty;
try
{
byte[] wavData = SelectedAudioFile.Extension switch
byte[] wavData = extension switch
{
"hca" => HcaWaveStream.ConvertHcaToWav(
SelectedAudioFile.Data,
UserSettings.Default.CurrentDir.CriwareDecryptionKey),
"adx" => AdxDecoder.ConvertAdxToWav(
SelectedAudioFile.Data,
UserSettings.Default.CurrentDir.CriwareDecryptionKey),
"hca" => HcaWaveStream.ConvertHcaToWav(inputFileData, UserSettings.Default.CurrentDir.CriwareDecryptionKey),
"adx" => AdxDecoder.ConvertAdxToWav(inputFileData, UserSettings.Default.CurrentDir.CriwareDecryptionKey),
_ => throw new NotSupportedException()
};
if (wavData.Length is 0)
// Fallback for ADX
if (wavData.Length == 0)
{
if (TryConvert(out var wavFilePathFallback))
{
var newAudioFallback = new AudioFile(SelectedAudioFile.Id, new FileInfo(wavFilePathFallback));
Replace(newAudioFallback);
return true;
}
var vgmStreamPath = TryGetVgmstreamPath();
if (string.IsNullOrEmpty(vgmStreamPath))
return false;
return TryConvertToWav(inputFilePath, inputFileData, vgmStreamPath, true, out wavFilePath);
}
string wavFilePath = Path.Combine(
UserSettings.Default.AudioDirectory,
SelectedAudioFile.FilePath.TrimStart('/'));
wavFilePath = Path.ChangeExtension(wavFilePath, ".wav");
wavFilePath = Path.ChangeExtension(inputFilePath, ".wav");
var directory = Path.GetDirectoryName(wavFilePath);
if (!string.IsNullOrEmpty(directory))
Directory.CreateDirectory(directory);
Directory.CreateDirectory(Path.GetDirectoryName(wavFilePath)!);
File.WriteAllBytes(wavFilePath, wavData);
var newAudio = new AudioFile(SelectedAudioFile.Id, new FileInfo(wavFilePath));
Replace(newAudio);
return true;
}
catch (CriwareDecryptionException ex)
{
FLogger.Append(ELog.Error, () => FLogger.Text($"Encrypted {SelectedAudioFile.Extension.ToUpper()}: {ex.Message}", Constants.WHITE, true));
Log.Error($"Encrypted {SelectedAudioFile.Extension.ToUpper()}: {ex.Message}");
FLogger.Append(ELog.Error, () => FLogger.Text($"Encrypted {extension.ToUpper()}: {ex.Message}", Constants.WHITE, true));
Log.Error($"Encrypted {extension.ToUpper()}: {ex.Message}");
return false;
}
catch (Exception ex)
{
FLogger.Append(ELog.Error, () => FLogger.Text($"Failed to convert {SelectedAudioFile.Extension.ToUpper()}: {ex.Message}", Constants.WHITE, true));
Log.Error($"Failed to convert {SelectedAudioFile.Extension.ToUpper()}: {ex.Message}");
FLogger.Append(ELog.Error, () => FLogger.Text($"Failed to convert {extension.ToUpper()}: {ex.Message}", Constants.WHITE, true));
Log.Error($"Failed to convert {extension.ToUpper()}: {ex.Message}");
return false;
}
}
private bool TryConvert(out string wavFilePath) => TryConvert(SelectedAudioFile.FilePath, SelectedAudioFile.Data, out wavFilePath, true);
public static bool TryConvert(string inputFilePath, byte[] inputFileData, out string wavFilePath, bool updateUi = false)
{
wavFilePath = string.Empty;
var vgmStreamPath = TryGetVgmstreamPath();
if (string.IsNullOrEmpty(vgmStreamPath))
return false;
var success = TryConvertToWav(inputFilePath, inputFileData, vgmStreamPath, true, out wavFilePath);
if (!success)
{
Log.Error("Failed to convert {InputFilePath} to .wav format", Path.GetFileName(inputFilePath));
if (updateUi)
{
FLogger.Append(ELog.Error, () =>
{
FLogger.Text("Failed to convert audio to .wav format. See: ", Constants.WHITE);
FLogger.Link("→ link ←", Constants.AUDIO_ISSUE_LINK, true);
});
}
}
return success;
}
private bool TryDecode(string extension, out string rawFilePath)
private static bool TryConvertRada(string inputFilePath, byte[] inputFileData, string extension, out string rawFilePath, bool updateUi = false)
{
rawFilePath = string.Empty;
var decoderPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{extension}dec.exe");
if (!File.Exists(decoderPath))
{
Log.Error("Failed to convert {FilePath}, rada decoder is missing", SelectedAudioFile.FilePath);
FLogger.Append(ELog.Error, () =>
Log.Error("Failed to convert {FilePath}, {Extension} decoder is missing", inputFilePath, extension);
if (updateUi)
{
FLogger.Text("Failed to convert audio because rada decoder is missing. See: ", Constants.WHITE);
FLogger.Link("→ link ←", Constants.RADA_ISSUE_LINK, true);
});
FLogger.Append(ELog.Error, () =>
{
FLogger.Text($"Failed to convert audio because {extension} decoder is missing. See: ", Constants.WHITE);
FLogger.Link("→ link ←", Constants.RADA_ISSUE_LINK, true);
});
}
return false;
}
return TryConvertToWav(SelectedAudioFile.FilePath, SelectedAudioFile.Data, decoderPath, false, out rawFilePath);
return TryConvertToWav(inputFilePath, inputFileData, decoderPath, false, out rawFilePath);
}
private static bool TryConvertToWav(string inputFilePath, byte[] inputFileData, string converterPath, bool usevgmstream, out string wavFilePath)

View File

@ -26,6 +26,7 @@ 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.LegoBatman.Assets;
using CUE4Parse.GameTypes.RocoKingdomWorld.Assets.Objects;
using CUE4Parse.GameTypes.SMG.UE4.Assets.Exports.Wwise;
using CUE4Parse.GameTypes.SquareEnix.UE4.Assets.Exports;
@ -65,7 +66,6 @@ using CUE4Parse_Conversion;
using CUE4Parse_Conversion.Sounds;
using EpicManifestParser;
using EpicManifestParser.UE;
using EpicManifestParser.ZlibngDotNetDecompressor;
using FModel.Creator;
using FModel.Extensions;
using FModel.Framework;
@ -94,6 +94,8 @@ public class CUE4ParseViewModel : ViewModel
private readonly Regex _fnLiveRegex = new(@"^FortniteGame[/\\]Content[/\\]Paks[/\\]",
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
private static readonly HttpClient _chunkClient = ManifestParseOptions.CreateDefaultClient();
private bool _modelIsOverwritingMaterial;
public bool ModelIsOverwritingMaterial
{
@ -224,9 +226,9 @@ public class CUE4ParseViewModel : ViewModel
{
Provider.OnDemandOptions = new IoStoreOnDemandOptions
{
ChunkHostUri = new Uri("https://download.epicgames.com/", UriKind.Absolute),
ChunkHostUri = new Uri("https://egdownload.fastly-edge.com/", UriKind.Absolute),
ChunkCacheDirectory = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data")),
Timeout = TimeSpan.FromSeconds(30)
DownloaderClient = _chunkClient
};
switch (Provider)
@ -247,9 +249,9 @@ public class CUE4ParseViewModel : ViewModel
{
ChunkCacheDirectory = cacheDir,
ManifestCacheDirectory = cacheDir,
ChunkBaseUrl = "http://download.epicgames.com/Builds/Fortnite/CloudDir/",
Decompressor = ManifestZlibngDotNetDecompressor.Decompress,
DecompressorState = ZlibHelper.Instance,
ChunkBaseUrl = "https://egdownload.fastly-edge.com/Builds/Fortnite/CloudDir/",
Decompressor = Compression.Decompressor,
Client = _chunkClient,
CacheChunksAsIs = false
};
@ -260,7 +262,7 @@ public class CUE4ParseViewModel : ViewModel
{
(manifest, _) = manifestInfo.DownloadAndParseAsync(manifestOptions,
cancellationToken: cancellationToken,
elementManifestPredicate: static x => x.Uri.Host == "download.epicgames.com"
elementDownloadPredicate: static x => x.Uri.Host is "egdownload.fastly-edge.com" or "epicgames-download1.akamaized.net" or "download.epicgames.com"
).GetAwaiter().GetResult();
}
catch (HttpRequestException ex)
@ -400,6 +402,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))
@ -413,7 +425,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)
{
@ -439,7 +451,7 @@ public class CUE4ParseViewModel : ViewModel
_apiEndpointView.DownloadFile(mapping.Url, mappingPath);
}
Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(mappingPath);
Provider.MappingsContainer = SelectMappingsProvider(mappingPath);
break;
}
}
@ -722,6 +734,7 @@ public class CUE4ParseViewModel : ViewModel
case "archive":
case "dnearchive": // Banishers: Ghosts of New Eden
case "gitignore":
case "gitattributes":
case "LICENSE":
case "playstats": // Dispatch
case "template":
@ -780,6 +793,8 @@ public class CUE4ParseViewModel : ViewModel
case "bl":
case "bm":
case "br":
case "sql":
case "cs":
{
var data = Provider.SaveAsset(entry);
using var stream = new MemoryStream(data) { Position = 0 };
@ -789,6 +804,7 @@ public class CUE4ParseViewModel : ViewModel
break;
}
case "ebd" when Provider.Versions.Game is EGame.GAME_ArcRaiders:
case "json":
{
var data = Provider.SaveAsset(entry);
@ -1018,9 +1034,25 @@ public class CUE4ParseViewModel : ViewModel
var nonPath = Provider.Files.Keys.FirstOrDefault(k => k.EndsWith(nonFileName, StringComparison.OrdinalIgnoreCase));
// I will only get one localization file because they did not translate any languages, lol
var locPathKey = entry.Path.Replace("/BinData/", "/BinLocalize/en_US/").Replace("/BinDataCompressed/", "/BinLocalize/en_US/");
var locPathKey = entry.Path.Replace("/BinData/", "/BinLocalize/zh_Hans/").Replace("/BinDataCompressed/", "/BinLocalize/zh_Hans/");
var locFileFound = Provider.Files.TryGetValue(locPathKey, out var locEntry);
if (entry.Path is "NRC/Content/ScriptC/Data/Audio/dataconfig_audio.bytes")
{
var descFound = Provider.Files.TryGetValue("NRC/Content/ScriptC/Data/Audio/dataconfig_audiodesc.bytes", out var descEntry);
var typeDescFound = Provider.Files.TryGetValue("NRC/Content/ScriptC/Data/Audio/typeDesc.bytes", out var typeDescEntry);
if (!descFound || !typeDescFound)
{
Log.Warning("Could not find associated dataconfig_audiodesc.bytes or typeDesc.bytes, cannot parse audio config");
return;
}
var data = new FRocoAudioConfig(entry.CreateReader(), descEntry.CreateReader(), typeDescEntry.CreateReader());
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(data, Formatting.Indented), saveProperties, updateUi);
return;
}
if (!string.IsNullOrEmpty(nonPath) && Provider.Files.TryGetValue(nonPath, out var nonEntry))
{
string json = Encoding.UTF8.GetString(nonEntry.Read());
@ -1309,8 +1341,7 @@ public class CUE4ParseViewModel : ViewModel
_ => []
};
var directory = Path.GetDirectoryName(atomObject.Owner?.Name) ?? "/Criware/";
directory = Path.GetDirectoryName(atomObject.Owner.Provider.FixPath(directory));
var directory = Path.GetDirectoryName(Provider.FixPath(atomObject.Owner?.Name ?? "/Criware/"));
foreach (var sound in extractedSounds)
{
SaveAndPlaySound(cancellationToken, Path.Combine(directory, sound.Name).Replace("\\", "/"), sound.Extension, sound.Data, saveAudio, updateUi);
@ -1321,8 +1352,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);
@ -1427,6 +1458,27 @@ public class CUE4ParseViewModel : ViewModel
return false;
}
// LEGO® Batman™: Legacy of the Dark Knight
case UWubAudioEvent when (isNone || saveAudio) && pointer.Object.Value is UWubAudioEvent wubAudioEvent:
{
var extractedSounds = WwiseProvider.ExtractWubAudioEventSounds(wubAudioEvent);
foreach (var sound in extractedSounds)
{
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data?.GetData() ?? [], saveAudio, updateUi);
}
return false;
}
case UWubDialogueEvent when (isNone || saveAudio) && pointer.Object.Value is UWubDialogueEvent wubDialogueEvent:
{
var files = wubDialogueEvent.Wems
.SelectMany(wem => Provider.Files.Values.Where(file => file.Path.EndsWith(wem.Text + ".wem", StringComparison.OrdinalIgnoreCase)))
.ToList();
foreach (var entry in files)
{
SaveAndPlaySound(cancellationToken, entry.PathWithoutExtension, entry.Extension, entry.Read(), saveAudio, updateUi);
}
return false;
}
case UWorld when isNone && UserSettings.Default.PreviewWorlds:
case UBlueprintGeneratedClass when isNone && UserSettings.Default.PreviewWorlds && TabControl.SelectedTab.ParentExportType switch
{
@ -1583,7 +1635,7 @@ public class CUE4ParseViewModel : ViewModel
bool conversionSuccess = true;
if (UserSettings.Default.ConvertAudioOnBulkExport && extLower is not "wav")
{
if (AudioPlayerViewModel.TryConvert(savedAudioPath, data, out string wavFilePath))
if (AudioPlayerViewModel.TryConvert(savedAudioPath, data, extLower, out string wavFilePath))
savedAudioPath = wavFilePath;
else
{

View File

@ -10,6 +10,7 @@ using CUE4Parse.FileProvider.Objects;
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.SMG.UE4.Assets.Exports.Wwise;
using CUE4Parse.GameTypes.SMG.UE4.Assets.Objects;
using CUE4Parse.GameTypes.SquareEnix.UE4.Assets.Exports;
@ -263,6 +264,7 @@ public class GameFileViewModel(GameFile asset) : ViewModel
UBorderlandsDialogObject when GameVersion is EGame.GAME_Borderlands3 => (EAssetCategory.Borderlands, EBulkType.None), // Borderlands 3;
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;
_ => (EAssetCategory.All, EBulkType.None),
};
@ -350,11 +352,15 @@ public class GameFileViewModel(GameFile asset) : ViewModel
case "pem":
case "xml":
case "gitignore":
case "gitattributes":
case "html":
case "css":
case "js":
case "data":
case "csv":
case "sql":
case "py":
case "cs":
AssetCategory = EAssetCategory.Data;
break;
case "stinfo":

View File

@ -48,10 +48,14 @@
<SolidColorBrush x:Key="GitBrush" Color="Coral" />
<SolidColorBrush x:Key="CsvBrush" Color="ForestGreen" />
<SolidColorBrush x:Key="AIBrush" Color="LightGray" />
<SolidColorBrush x:Key="SQLBrush" Color="#00758f" />
<SolidColorBrush x:Key="PythonBrush" Color="#3d77a8" />
<SolidColorBrush x:Key="CSharpBrush" Color="#9e559a" />
<!-- For specific games -->
<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>
<SolidColorBrush x:Key="BatmanBrush" Color="Gold"></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

@ -80,10 +80,14 @@ public class FileToGeometryConverter : IMultiValueConverter
"bin" => ("DataTableIcon", "BinaryBrush"),
"xml" => ("XmlIcon", "JsonXmlBrush"),
"gitignore" => ("GitIcon", "GitBrush"),
"gitattributes" => ("GitIcon", "GitBrush"),
"html" => ("HtmlIcon", "HtmlBrush"),
"js" => ("JavaScriptIcon", "JavaScriptBrush"),
"css" => ("CssIcon", "CssBrush"),
"csv" => ("CsvIcon", "CsvBrush"),
"sql" => ("SQLIcon", "SQLBrush"),
"py" => ("PythonIcon", "PythonBrush"),
"cs" => ("CSharpIcon", "CSharpBrush"),
_ => ("DataTableIcon", "NeutralBrush")
},
@ -93,6 +97,7 @@ public class FileToGeometryConverter : IMultiValueConverter
EAssetCategory.Aion2 => ("AionIcon", "AionBrush"),
EAssetCategory.RocoKingdomWorld => ("RocoKingdomWorldIcon", "RocoKingdomWorldBrush"),
EAssetCategory.DeltaForce => ("DeltaForceIcon", "DeltaForceBrush"),
EAssetCategory.LegoBatman => ("BatmanIcon", "BatmanBrush"),
_ => ("AssetIcon", "NeutralBrush")
};

File diff suppressed because one or more lines are too long

View File

@ -61,13 +61,13 @@ public class Morph : IDisposable
}
}
public Morph(float[] vertices, Dictionary<uint, int> dict, UMorphTarget morphTarget)
public Morph(float[] vertices, Dictionary<uint, int> dict, UMorphTarget morphTarget, int index = 0)
{
Name = morphTarget.Name;
Vertices = new float[vertices.Length];
Array.Copy(vertices, Vertices, vertices.Length);
foreach (var vert in morphTarget.MorphLODModels[0].Vertices)
foreach (var vert in morphTarget.MorphLODModels[index].Vertices)
{
var count = 0;
if (dict.TryGetValue(vert.SourceIdx, out var baseIndex))

View File

@ -100,11 +100,11 @@ public class SkeletalModel : UModel
foreach (var morph in export.MorphTargets)
{
if (!morph.TryLoad(out UMorphTarget morphTarget) || morphTarget.MorphLODModels.Length < 1 ||
morphTarget.MorphLODModels[0].Vertices.Length < 1)
if (!morph.TryLoad(out UMorphTarget morphTarget) || morphTarget.MorphLODModels.Length <= skeletalMesh.LODs[LodLevel].LODIndex ||
morphTarget.MorphLODModels[skeletalMesh.LODs[LodLevel].LODIndex].Vertices.Length < 1)
continue;
Morphs.Add(new Morph(cachedVertices, vertexLookup, morphTarget));
Morphs.Add(new Morph(cachedVertices, vertexLookup, morphTarget, skeletalMesh.LODs[LodLevel].LODIndex));
}
}

View File

@ -247,7 +247,7 @@ public class Options
{
return _game switch
{
"LIESOFP" or "CODEVEIN2" or "HIGHONLIFE2" => true,
"LIESOFP" or "CODEVEIN2" or "HIGHONLIFE2" or "MORTALSHELL2" => true,
_ => false,
};
}