diff --git a/.gitmodules b/.gitmodules
index 959570f6..2221fc41 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,3 @@
[submodule "CUE4Parse"]
path = CUE4Parse
url = https://github.com/FabianFG/CUE4Parse
-[submodule "EpicManifestParser"]
- path = EpicManifestParser
- url = https://github.com/FModel/EpicManifestParser
\ No newline at end of file
diff --git a/CUE4Parse b/CUE4Parse
index 909fae9a..08a5553d 160000
--- a/CUE4Parse
+++ b/CUE4Parse
@@ -1 +1 @@
-Subproject commit 909fae9ad24f8c9b0216af824b3c021dd39f1956
+Subproject commit 08a5553ddec50aa6064278b424620e95aabf4048
diff --git a/EpicManifestParser b/EpicManifestParser
deleted file mode 160000
index 21df8a55..00000000
--- a/EpicManifestParser
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 21df8a55d474f14148a35bc943e06f3fdc20c997
diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj
index 27458409..0a192513 100644
--- a/FModel/FModel.csproj
+++ b/FModel/FModel.csproj
@@ -45,6 +45,12 @@
+
+
+
+
+
+
@@ -140,28 +146,28 @@
-
+
-
-
+
+
+
-
+
-
+
-
@@ -181,6 +187,12 @@
+
+
+
+
+
+
diff --git a/FModel/FModel.sln b/FModel/FModel.sln
index 52492681..238d1b59 100644
--- a/FModel/FModel.sln
+++ b/FModel/FModel.sln
@@ -9,8 +9,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse", "..\CUE4Parse\C
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse-Conversion", "..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj", "{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EpicManifestParser", "..\EpicManifestParser\src\EpicManifestParser\EpicManifestParser.csproj", "{D4958A8B-815B-421D-A988-2A4E8E2B582D}"
-EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -29,10 +27,6 @@ Global
{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
- {D4958A8B-815B-421D-A988-2A4E8E2B582D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D4958A8B-815B-421D-A988-2A4E8E2B582D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D4958A8B-815B-421D-A988-2A4E8E2B582D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D4958A8B-815B-421D-A988-2A4E8E2B582D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/FModel/Framework/ImGuiController.cs b/FModel/Framework/ImGuiController.cs
index 9077553b..8e1e2c91 100644
--- a/FModel/Framework/ImGuiController.cs
+++ b/FModel/Framework/ImGuiController.cs
@@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.IO;
using System.Numerics;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Forms;
+using FModel.Settings;
using ImGuiNET;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Windowing.Desktop;
@@ -34,7 +37,6 @@ public class ImGuiController : IDisposable
private int _windowWidth;
private int _windowHeight;
- // private string _iniPath;
public ImFontPtr FontNormal;
public ImFontPtr FontBold;
@@ -49,7 +51,6 @@ public class ImGuiController : IDisposable
{
_windowWidth = width;
_windowHeight = height;
- // _iniPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "imgui.ini");
int major = GL.GetInteger(GetPName.MajorVersion);
int minor = GL.GetInteger(GetPName.MinorVersion);
@@ -58,9 +59,13 @@ public class ImGuiController : IDisposable
IntPtr context = ImGui.CreateContext();
ImGui.SetCurrentContext(context);
- // ImGui.LoadIniSettingsFromDisk(_iniPath);
var io = ImGui.GetIO();
+ unsafe
+ {
+ var iniFileNamePtr = Marshal.StringToCoTaskMemUTF8(Path.Combine(UserSettings.Default.OutputDirectory, ".data", "imgui.ini"));
+ io.NativePtr->IniFilename = (byte*)iniFileNamePtr;
+ }
FontNormal = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeui.ttf", 16 * DpiScale);
FontBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeuib.ttf", 16 * DpiScale);
FontSemiBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\seguisb.ttf", 16 * DpiScale);
@@ -71,7 +76,6 @@ public class ImGuiController : IDisposable
io.Fonts.Flags |= ImFontAtlasFlags.NoBakedLines;
CreateDeviceResources();
- SetKeyMappings();
SetPerFrameImGuiData(1f / 60f);
@@ -271,8 +275,8 @@ outputColor = color * texture(in_fontTexture, texCoord);
foreach (Keys key in Enum.GetValues(typeof(Keys)))
{
- if (key == Keys.Unknown || io.KeyMap[(int) key] == -1) continue;
- io.AddKeyEvent((ImGuiKey) io.KeyMap[(int) key], kState.IsKeyDown(key));
+ if (key == Keys.Unknown) continue;
+ io.AddKeyEvent(TranslateKey(key), kState.IsKeyDown(key));
}
foreach (var c in PressedChars)
@@ -292,115 +296,6 @@ outputColor = color * texture(in_fontTexture, texCoord);
PressedChars.Add(keyChar);
}
- private static void SetKeyMappings()
- {
- ImGuiIOPtr io = ImGui.GetIO();
- io.KeyMap[(int)ImGuiKey.LeftShift] = (int)Keys.LeftShift;
- io.KeyMap[(int)ImGuiKey.RightShift] = (int)Keys.RightShift;
- io.KeyMap[(int)ImGuiKey.LeftCtrl] = (int)Keys.LeftControl;
- io.KeyMap[(int)ImGuiKey.RightCtrl] = (int)Keys.RightControl;
- io.KeyMap[(int)ImGuiKey.LeftAlt] = (int)Keys.LeftAlt;
- io.KeyMap[(int)ImGuiKey.RightAlt] = (int)Keys.RightAlt;
- io.KeyMap[(int)ImGuiKey.LeftSuper] = (int)Keys.LeftSuper;
- io.KeyMap[(int)ImGuiKey.RightSuper] = (int)Keys.RightSuper;
- io.KeyMap[(int)ImGuiKey.Menu] = (int)Keys.Menu;
- io.KeyMap[(int)ImGuiKey.UpArrow] = (int)Keys.Up;
- io.KeyMap[(int)ImGuiKey.DownArrow] = (int)Keys.Down;
- io.KeyMap[(int)ImGuiKey.LeftArrow] = (int)Keys.Left;
- io.KeyMap[(int)ImGuiKey.RightArrow] = (int)Keys.Right;
- io.KeyMap[(int)ImGuiKey.Enter] = (int)Keys.Enter;
- io.KeyMap[(int)ImGuiKey.Escape] = (int)Keys.Escape;
- io.KeyMap[(int)ImGuiKey.Space] = (int)Keys.Space;
- io.KeyMap[(int)ImGuiKey.Tab] = (int)Keys.Tab;
- io.KeyMap[(int)ImGuiKey.Backspace] = (int)Keys.Backspace;
- io.KeyMap[(int)ImGuiKey.Insert] = (int)Keys.Insert;
- io.KeyMap[(int)ImGuiKey.Delete] = (int)Keys.Delete;
- io.KeyMap[(int)ImGuiKey.PageUp] = (int)Keys.PageUp;
- io.KeyMap[(int)ImGuiKey.PageDown] = (int)Keys.PageDown;
- io.KeyMap[(int)ImGuiKey.Home] = (int)Keys.Home;
- io.KeyMap[(int)ImGuiKey.End] = (int)Keys.End;
- io.KeyMap[(int)ImGuiKey.CapsLock] = (int)Keys.CapsLock;
- io.KeyMap[(int)ImGuiKey.ScrollLock] = (int)Keys.ScrollLock;
- io.KeyMap[(int)ImGuiKey.PrintScreen] = (int)Keys.PrintScreen;
- io.KeyMap[(int)ImGuiKey.Pause] = (int)Keys.Pause;
- io.KeyMap[(int)ImGuiKey.NumLock] = (int)Keys.NumLock;
- io.KeyMap[(int)ImGuiKey.KeypadDivide] = (int)Keys.KeyPadDivide;
- io.KeyMap[(int)ImGuiKey.KeypadMultiply] = (int)Keys.KeyPadMultiply;
- io.KeyMap[(int)ImGuiKey.KeypadSubtract] = (int)Keys.KeyPadSubtract;
- io.KeyMap[(int)ImGuiKey.KeypadAdd] = (int)Keys.KeyPadAdd;
- io.KeyMap[(int)ImGuiKey.KeypadDecimal] = (int)Keys.KeyPadDecimal;
- io.KeyMap[(int)ImGuiKey.KeypadEnter] = (int)Keys.KeyPadEnter;
- io.KeyMap[(int)ImGuiKey.GraveAccent] = (int)Keys.GraveAccent;
- io.KeyMap[(int)ImGuiKey.Minus] = (int)Keys.Minus;
- io.KeyMap[(int)ImGuiKey.Equal] = (int)Keys.Equal;
- io.KeyMap[(int)ImGuiKey.LeftBracket] = (int)Keys.LeftBracket;
- io.KeyMap[(int)ImGuiKey.RightBracket] = (int)Keys.RightBracket;
- io.KeyMap[(int)ImGuiKey.Semicolon] = (int)Keys.Semicolon;
- io.KeyMap[(int)ImGuiKey.Apostrophe] = (int)Keys.Apostrophe;
- io.KeyMap[(int)ImGuiKey.Comma] = (int)Keys.Comma;
- io.KeyMap[(int)ImGuiKey.Period] = (int)Keys.Period;
- io.KeyMap[(int)ImGuiKey.Slash] = (int)Keys.Slash;
- io.KeyMap[(int)ImGuiKey.Backslash] = (int)Keys.Backslash;
- io.KeyMap[(int)ImGuiKey.F1] = (int)Keys.F1;
- io.KeyMap[(int)ImGuiKey.F2] = (int)Keys.F2;
- io.KeyMap[(int)ImGuiKey.F3] = (int)Keys.F3;
- io.KeyMap[(int)ImGuiKey.F4] = (int)Keys.F4;
- io.KeyMap[(int)ImGuiKey.F5] = (int)Keys.F5;
- io.KeyMap[(int)ImGuiKey.F6] = (int)Keys.F6;
- io.KeyMap[(int)ImGuiKey.F7] = (int)Keys.F7;
- io.KeyMap[(int)ImGuiKey.F8] = (int)Keys.F8;
- io.KeyMap[(int)ImGuiKey.F9] = (int)Keys.F9;
- io.KeyMap[(int)ImGuiKey.F10] = (int)Keys.F10;
- io.KeyMap[(int)ImGuiKey.F11] = (int)Keys.F11;
- io.KeyMap[(int)ImGuiKey.F12] = (int)Keys.F12;
- io.KeyMap[(int)ImGuiKey.Keypad0] = (int)Keys.KeyPad0;
- io.KeyMap[(int)ImGuiKey.Keypad1] = (int)Keys.KeyPad1;
- io.KeyMap[(int)ImGuiKey.Keypad2] = (int)Keys.KeyPad2;
- io.KeyMap[(int)ImGuiKey.Keypad3] = (int)Keys.KeyPad3;
- io.KeyMap[(int)ImGuiKey.Keypad4] = (int)Keys.KeyPad4;
- io.KeyMap[(int)ImGuiKey.Keypad5] = (int)Keys.KeyPad5;
- io.KeyMap[(int)ImGuiKey.Keypad6] = (int)Keys.KeyPad6;
- io.KeyMap[(int)ImGuiKey.Keypad7] = (int)Keys.KeyPad7;
- io.KeyMap[(int)ImGuiKey.Keypad8] = (int)Keys.KeyPad8;
- io.KeyMap[(int)ImGuiKey.Keypad9] = (int)Keys.KeyPad9;
- io.KeyMap[(int)ImGuiKey._0] = (int)Keys.D0;
- io.KeyMap[(int)ImGuiKey._1] = (int)Keys.D1;
- io.KeyMap[(int)ImGuiKey._2] = (int)Keys.D2;
- io.KeyMap[(int)ImGuiKey._3] = (int)Keys.D3;
- io.KeyMap[(int)ImGuiKey._4] = (int)Keys.D4;
- io.KeyMap[(int)ImGuiKey._5] = (int)Keys.D5;
- io.KeyMap[(int)ImGuiKey._6] = (int)Keys.D6;
- io.KeyMap[(int)ImGuiKey._7] = (int)Keys.D7;
- io.KeyMap[(int)ImGuiKey._8] = (int)Keys.D8;
- io.KeyMap[(int)ImGuiKey._9] = (int)Keys.D9;
- io.KeyMap[(int)ImGuiKey.A] = (int)Keys.A;
- io.KeyMap[(int)ImGuiKey.B] = (int)Keys.B;
- io.KeyMap[(int)ImGuiKey.C] = (int)Keys.C;
- io.KeyMap[(int)ImGuiKey.D] = (int)Keys.D;
- io.KeyMap[(int)ImGuiKey.E] = (int)Keys.E;
- io.KeyMap[(int)ImGuiKey.F] = (int)Keys.F;
- io.KeyMap[(int)ImGuiKey.G] = (int)Keys.G;
- io.KeyMap[(int)ImGuiKey.H] = (int)Keys.H;
- io.KeyMap[(int)ImGuiKey.I] = (int)Keys.I;
- io.KeyMap[(int)ImGuiKey.J] = (int)Keys.J;
- io.KeyMap[(int)ImGuiKey.K] = (int)Keys.K;
- io.KeyMap[(int)ImGuiKey.L] = (int)Keys.L;
- io.KeyMap[(int)ImGuiKey.M] = (int)Keys.M;
- io.KeyMap[(int)ImGuiKey.N] = (int)Keys.N;
- io.KeyMap[(int)ImGuiKey.O] = (int)Keys.O;
- io.KeyMap[(int)ImGuiKey.P] = (int)Keys.P;
- io.KeyMap[(int)ImGuiKey.Q] = (int)Keys.Q;
- io.KeyMap[(int)ImGuiKey.R] = (int)Keys.R;
- io.KeyMap[(int)ImGuiKey.S] = (int)Keys.S;
- io.KeyMap[(int)ImGuiKey.T] = (int)Keys.T;
- io.KeyMap[(int)ImGuiKey.U] = (int)Keys.U;
- io.KeyMap[(int)ImGuiKey.V] = (int)Keys.V;
- io.KeyMap[(int)ImGuiKey.W] = (int)Keys.W;
- io.KeyMap[(int)ImGuiKey.X] = (int)Keys.X;
- io.KeyMap[(int)ImGuiKey.Y] = (int)Keys.Y;
- io.KeyMap[(int)ImGuiKey.Z] = (int)Keys.Z;
- }
-
private void RenderImDrawData(ImDrawDataPtr draw_data)
{
if (draw_data.CmdListsCount == 0)
@@ -643,4 +538,71 @@ outputColor = color * texture(in_fontTexture, texCoord);
{
return Math.Max((float)(Screen.PrimaryScreen.Bounds.Width / SystemParameters.PrimaryScreenWidth), (float)(Screen.PrimaryScreen.Bounds.Height / SystemParameters.PrimaryScreenHeight));
}
+
+ public static ImGuiKey TranslateKey(Keys key)
+ {
+ if (key is >= Keys.D0 and <= Keys.D9)
+ return key - Keys.D0 + ImGuiKey._0;
+
+ if (key is >= Keys.A and <= Keys.Z)
+ return key - Keys.A + ImGuiKey.A;
+
+ if (key is >= Keys.KeyPad0 and <= Keys.KeyPad9)
+ return key - Keys.KeyPad0 + ImGuiKey.Keypad0;
+
+ if (key is >= Keys.F1 and <= Keys.F24)
+ return key - Keys.F1 + ImGuiKey.F24;
+
+ return key switch
+ {
+ Keys.Tab => ImGuiKey.Tab,
+ Keys.Left => ImGuiKey.LeftArrow,
+ Keys.Right => ImGuiKey.RightArrow,
+ Keys.Up => ImGuiKey.UpArrow,
+ Keys.Down => ImGuiKey.DownArrow,
+ Keys.PageUp => ImGuiKey.PageUp,
+ Keys.PageDown => ImGuiKey.PageDown,
+ Keys.Home => ImGuiKey.Home,
+ Keys.End => ImGuiKey.End,
+ Keys.Insert => ImGuiKey.Insert,
+ Keys.Delete => ImGuiKey.Delete,
+ Keys.Backspace => ImGuiKey.Backspace,
+ Keys.Space => ImGuiKey.Space,
+ Keys.Enter => ImGuiKey.Enter,
+ Keys.Escape => ImGuiKey.Escape,
+ Keys.Apostrophe => ImGuiKey.Apostrophe,
+ Keys.Comma => ImGuiKey.Comma,
+ Keys.Minus => ImGuiKey.Minus,
+ Keys.Period => ImGuiKey.Period,
+ Keys.Slash => ImGuiKey.Slash,
+ Keys.Semicolon => ImGuiKey.Semicolon,
+ Keys.Equal => ImGuiKey.Equal,
+ Keys.LeftBracket => ImGuiKey.LeftBracket,
+ Keys.Backslash => ImGuiKey.Backslash,
+ Keys.RightBracket => ImGuiKey.RightBracket,
+ Keys.GraveAccent => ImGuiKey.GraveAccent,
+ Keys.CapsLock => ImGuiKey.CapsLock,
+ Keys.ScrollLock => ImGuiKey.ScrollLock,
+ Keys.NumLock => ImGuiKey.NumLock,
+ Keys.PrintScreen => ImGuiKey.PrintScreen,
+ Keys.Pause => ImGuiKey.Pause,
+ Keys.KeyPadDecimal => ImGuiKey.KeypadDecimal,
+ Keys.KeyPadDivide => ImGuiKey.KeypadDivide,
+ Keys.KeyPadMultiply => ImGuiKey.KeypadMultiply,
+ Keys.KeyPadSubtract => ImGuiKey.KeypadSubtract,
+ Keys.KeyPadAdd => ImGuiKey.KeypadAdd,
+ Keys.KeyPadEnter => ImGuiKey.KeypadEnter,
+ Keys.KeyPadEqual => ImGuiKey.KeypadEqual,
+ Keys.LeftShift => ImGuiKey.LeftShift,
+ Keys.LeftControl => ImGuiKey.LeftCtrl,
+ Keys.LeftAlt => ImGuiKey.LeftAlt,
+ Keys.LeftSuper => ImGuiKey.LeftSuper,
+ Keys.RightShift => ImGuiKey.RightShift,
+ Keys.RightControl => ImGuiKey.RightCtrl,
+ Keys.RightAlt => ImGuiKey.RightAlt,
+ Keys.RightSuper => ImGuiKey.RightSuper,
+ Keys.Menu => ImGuiKey.Menu,
+ _ => ImGuiKey.None
+ };
+ }
}
diff --git a/FModel/Helper.cs b/FModel/Helper.cs
index 67cd659f..c7a43c50 100644
--- a/FModel/Helper.cs
+++ b/FModel/Helper.cs
@@ -1,30 +1,32 @@
-using System;
+using System;
using System.Linq;
-using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
using System.Windows;
namespace FModel;
public static class Helper
{
- [StructLayout(LayoutKind.Explicit)]
- private struct NanUnion
- {
- [FieldOffset(0)]
- internal double DoubleValue;
- [FieldOffset(0)]
- internal readonly ulong UlongValue;
- }
-
public static string FixKey(string key)
{
if (string.IsNullOrEmpty(key))
return string.Empty;
- if (key.StartsWith("0x"))
- key = key[2..];
+ var keySpan = key.AsSpan().Trim();
+ if (keySpan.Length > sizeof(char) * (2 /* 0x */ + 32 /* FAES = 256 bit */)) // maybe strictly check for length?
+ return string.Empty; // bullshit key
- return "0x" + key.ToUpper().Trim();
+ Span resultSpan = stackalloc char[keySpan.Length + 2 /* pad for 0x */];
+ keySpan.ToUpperInvariant(resultSpan[2..]);
+
+ if (resultSpan[2..].StartsWith("0X"))
+ resultSpan = resultSpan[2..];
+ else
+ resultSpan[0] = '0';
+
+ resultSpan[1] = 'x';
+
+ return new string(resultSpan);
}
public static void OpenWindow(string windowName, Action action) where T : Window
@@ -74,9 +76,9 @@ public static class Helper
public static bool IsNaN(double value)
{
- var t = new NanUnion { DoubleValue = value };
- var exp = t.UlongValue & 0xfff0000000000000;
- var man = t.UlongValue & 0x000fffffffffffff;
+ var ulongValue = Unsafe.As(ref value);
+ var exp = ulongValue & 0xfff0000000000000;
+ var man = ulongValue & 0x000fffffffffffff;
return exp is 0x7ff0000000000000 or 0xfff0000000000000 && man != 0;
}
@@ -96,13 +98,17 @@ public static class Helper
return -d < n && d > n;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DegreesToRadians(float degrees)
{
- return MathF.PI / 180f * degrees;
+ const float ratio = MathF.PI / 180f;
+ return ratio * degrees;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float RadiansToDegrees(float radians)
{
- return radians* 180f / MathF.PI;
+ const float ratio = 180f / MathF.PI;
+ return radians * ratio;
}
}
diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs
index 86212096..3c69be80 100644
--- a/FModel/MainWindow.xaml.cs
+++ b/FModel/MainWindow.xaml.cs
@@ -61,6 +61,8 @@ public partial class MainWindow
break;
}
+ await ApplicationViewModel.InitOodle();
+ await ApplicationViewModel.InitZlib();
await _applicationView.CUE4Parse.Initialize();
await _applicationView.AesManager.InitAes();
await _applicationView.UpdateProvider(true);
@@ -71,8 +73,8 @@ public partial class MainWindow
_applicationView.CUE4Parse.VerifyConsoleVariables(),
_applicationView.CUE4Parse.VerifyOnDemandArchives(),
_applicationView.CUE4Parse.InitMappings(),
- _applicationView.InitVgmStream(),
- _applicationView.InitImGuiSettings(newOrUpdated),
+ ApplicationViewModel.InitVgmStream(),
+ ApplicationViewModel.InitImGuiSettings(newOrUpdated),
Task.Run(() =>
{
if (UserSettings.Default.DiscordRpc == EDiscordRpc.Always)
@@ -83,10 +85,7 @@ public partial class MainWindow
#if DEBUG
await _threadWorkerView.Begin(cancellationToken =>
_applicationView.CUE4Parse.Extract(cancellationToken,
- "fortnitegame/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_Med_Soldier_01.uasset"));
- await _threadWorkerView.Begin(cancellationToken =>
- _applicationView.CUE4Parse.Extract(cancellationToken,
- "fortnitegame/Content/Animation/Game/MainPlayer/Emotes/Cowbell/Cowbell_CMM_Loop_M.uasset"));
+ "ShooterGame/Content/Characters/Smonk/S0/3P/Models/TP_Smonk_S0_Skelmesh.uasset"));
#endif
}
diff --git a/FModel/Resources/cube.png b/FModel/Resources/cube.png
new file mode 100644
index 00000000..00766874
Binary files /dev/null and b/FModel/Resources/cube.png differ
diff --git a/FModel/Resources/cube_off.png b/FModel/Resources/cube_off.png
new file mode 100644
index 00000000..eae850fa
Binary files /dev/null and b/FModel/Resources/cube_off.png differ
diff --git a/FModel/Resources/light.png b/FModel/Resources/light.png
new file mode 100644
index 00000000..cdce6934
Binary files /dev/null and b/FModel/Resources/light.png differ
diff --git a/FModel/Resources/light_off.png b/FModel/Resources/light_off.png
new file mode 100644
index 00000000..5e2d5890
Binary files /dev/null and b/FModel/Resources/light_off.png differ
diff --git a/FModel/Resources/square.png b/FModel/Resources/square.png
new file mode 100644
index 00000000..1edb68e8
Binary files /dev/null and b/FModel/Resources/square.png differ
diff --git a/FModel/Resources/square_off.png b/FModel/Resources/square_off.png
new file mode 100644
index 00000000..cd743702
Binary files /dev/null and b/FModel/Resources/square_off.png differ
diff --git a/FModel/ViewModels/ApiEndpoints/EpicApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/EpicApiEndpoint.cs
index 386762ce..6b1eeea2 100644
--- a/FModel/ViewModels/ApiEndpoints/EpicApiEndpoint.cs
+++ b/FModel/ViewModels/ApiEndpoints/EpicApiEndpoint.cs
@@ -1,10 +1,14 @@
using System.Threading;
using System.Threading.Tasks;
-using EpicManifestParser.Objects;
+
+using EpicManifestParser.Api;
+
using FModel.Framework;
using FModel.Settings;
using FModel.ViewModels.ApiEndpoints.Models;
+
using RestSharp;
+
using Serilog;
namespace FModel.ViewModels.ApiEndpoints;
@@ -25,7 +29,7 @@ public class EpicApiEndpoint : AbstractApiProvider
request.AddHeader("Authorization", $"bearer {UserSettings.Default.LastAuthResponse.AccessToken}");
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString);
- return response.IsSuccessful ? new ManifestInfo(response.Content) : null;
+ return response.IsSuccessful ? ManifestInfo.Deserialize(response.RawBytes) : null;
}
public ManifestInfo GetManifest(CancellationToken token)
diff --git a/FModel/ViewModels/ApiEndpoints/ValorantApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/ValorantApiEndpoint.cs
index 67c51f96..8c827867 100644
--- a/FModel/ViewModels/ApiEndpoints/ValorantApiEndpoint.cs
+++ b/FModel/ViewModels/ApiEndpoints/ValorantApiEndpoint.cs
@@ -1,8 +1,3 @@
-using CUE4Parse.UE4.Exceptions;
-using CUE4Parse.UE4.Readers;
-using FModel.Settings;
-using Ionic.Zlib;
-using RestSharp;
using System;
using System.Collections.Generic;
using System.IO;
@@ -13,7 +8,15 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+
+using CUE4Parse.Compression;
+using CUE4Parse.UE4.Exceptions;
+using CUE4Parse.UE4.Readers;
+
using FModel.Framework;
+using FModel.Settings;
+
+using RestSharp;
namespace FModel.ViewModels.ApiEndpoints;
@@ -40,26 +43,22 @@ public class VManifest
public readonly VChunk[] Chunks;
public readonly VPak[] Paks;
- public VManifest(byte[] data) : this(new FByteArchive("CompressedValorantManifest", data))
- {
- }
-
+ public VManifest(byte[] data) : this(new FByteArchive("CompressedValorantManifest", data)) { }
private VManifest(FArchive Ar)
{
using (Ar)
{
Header = new VHeader(Ar);
var compressedBuffer = Ar.ReadBytes((int) Header.CompressedSize);
- var uncompressedBuffer = ZlibStream.UncompressBuffer(compressedBuffer);
- if (uncompressedBuffer.Length != Header.UncompressedSize)
- throw new ParserException(Ar, $"Decompression failed, {uncompressedBuffer.Length} != {Header.UncompressedSize}");
+ var uncompressedBuffer = new byte[(int)Header.UncompressedSize];
+ ZlibHelper.Decompress(compressedBuffer, 0, compressedBuffer.Length, uncompressedBuffer, 0, uncompressedBuffer.Length);
- using var manifest = new FByteArchive("UncompressedValorantManifest", uncompressedBuffer);
- Chunks = manifest.ReadArray((int) Header.ChunkCount);
- Paks = manifest.ReadArray((int) Header.PakCount, () => new VPak(manifest));
+ var manifestAr = new FByteArchive("UncompressedValorantManifest", uncompressedBuffer);
+ Chunks = manifestAr.ReadArray((int) Header.ChunkCount);
+ Paks = manifestAr.ReadArray((int) Header.PakCount, () => new VPak(manifestAr));
- if (manifest.Position != manifest.Length)
- throw new ParserException(manifest, $"Parsing failed, {manifest.Position} != {manifest.Length}");
+ if (manifestAr.Position != manifestAr.Length)
+ throw new ParserException(manifestAr, $"Parsing failed, {manifestAr.Position} != {manifestAr.Length}");
}
_client = new HttpClient(new HttpClientHandler
diff --git a/FModel/ViewModels/ApplicationViewModel.cs b/FModel/ViewModels/ApplicationViewModel.cs
index 6398f37a..86929360 100644
--- a/FModel/ViewModels/ApplicationViewModel.cs
+++ b/FModel/ViewModels/ApplicationViewModel.cs
@@ -1,3 +1,10 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.IO.Compression;
+using System.Threading.Tasks;
+using System.Windows;
+using CUE4Parse.Compression;
using FModel.Extensions;
using FModel.Framework;
using FModel.Services;
@@ -5,12 +12,7 @@ using FModel.Settings;
using FModel.ViewModels.Commands;
using FModel.Views;
using FModel.Views.Resources.Controls;
-using Ionic.Zip;
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Threading.Tasks;
-using System.Windows;
+
using MessageBox = AdonisUI.Controls.MessageBox;
using MessageBoxButton = AdonisUI.Controls.MessageBoxButton;
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
@@ -160,7 +162,7 @@ public class ApplicationViewModel : ViewModel
RaisePropertyChanged(nameof(GameDisplayName));
}
- public async Task InitVgmStream()
+ public static async Task InitVgmStream()
{
var vgmZipFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-win.zip");
if (File.Exists(vgmZipFilePath)) return;
@@ -168,9 +170,17 @@ public class ApplicationViewModel : ViewModel
await ApplicationService.ApiEndpointView.DownloadFileAsync("https://github.com/vgmstream/vgmstream/releases/latest/download/vgmstream-win.zip", vgmZipFilePath);
if (new FileInfo(vgmZipFilePath).Length > 0)
{
- var zip = ZipFile.Read(vgmZipFilePath);
- var zipDir = vgmZipFilePath.SubstringBeforeLast("\\");
- foreach (var e in zip) e.Extract(zipDir, ExtractExistingFileAction.OverwriteSilently);
+ 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.OpenRead(entryPath);
+ await using var entryStream = entry.Open();
+ await entryStream.CopyToAsync(entryFs);
+ }
}
else
{
@@ -178,15 +188,44 @@ public class ApplicationViewModel : ViewModel
}
}
- public async Task InitImGuiSettings(bool forceDownload)
+ public static async Task InitImGuiSettings(bool forceDownload)
{
- var imgui = Path.Combine(/*UserSettings.Default.OutputDirectory, ".data", */"imgui.ini");
- if (File.Exists(imgui) && !forceDownload) return;
+ var imgui = "imgui.ini";
+ var imguiPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", imgui);
- await ApplicationService.ApiEndpointView.DownloadFileAsync("https://cdn.fmodel.app/d/configurations/imgui.ini", imgui);
- if (new FileInfo(imgui).Length == 0)
+ if (File.Exists(imgui)) File.Move(imgui, imguiPath, true);
+ if (File.Exists(imguiPath) && !forceDownload) return;
+
+ await ApplicationService.ApiEndpointView.DownloadFileAsync($"https://cdn.fmodel.app/d/configurations/{imgui}", imguiPath);
+ if (new FileInfo(imguiPath).Length == 0)
{
FLogger.Append(ELog.Error, () => FLogger.Text("Could not download ImGui settings", Constants.WHITE, true));
}
}
+
+ public static async ValueTask InitOodle()
+ {
+ var oodlePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", OodleHelper.OODLE_DLL_NAME);
+ if (File.Exists(OodleHelper.OODLE_DLL_NAME))
+ {
+ File.Move(OodleHelper.OODLE_DLL_NAME, oodlePath, true);
+ }
+ else if (!File.Exists(oodlePath))
+ {
+ await OodleHelper.DownloadOodleDllAsync(oodlePath);
+ }
+
+ OodleHelper.Initialize(oodlePath);
+ }
+
+ public static async ValueTask InitZlib()
+ {
+ var zlibPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", ZlibHelper.DLL_NAME);
+ if (!File.Exists(zlibPath))
+ {
+ await ZlibHelper.DownloadDllAsync(zlibPath);
+ }
+
+ ZlibHelper.Initialize(zlibPath);
+ }
}
diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs
index 977e83ba..409b0d12 100644
--- a/FModel/ViewModels/CUE4ParseViewModel.cs
+++ b/FModel/ViewModels/CUE4ParseViewModel.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http.Headers;
@@ -7,7 +8,9 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
+
using AdonisUI.Controls;
+using CUE4Parse.Compression;
using CUE4Parse.Encryption.Aes;
using CUE4Parse.FileProvider;
using CUE4Parse.FileProvider.Vfs;
@@ -17,23 +20,26 @@ using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Animation;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
-using CUE4Parse.UE4.Assets.Exports.Verse;
using CUE4Parse.UE4.Assets.Exports.Sound;
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.IO;
using CUE4Parse.UE4.Localization;
+using CUE4Parse.UE4.Objects.Core.Serialization;
using CUE4Parse.UE4.Objects.Engine;
using CUE4Parse.UE4.Oodle.Objects;
using CUE4Parse.UE4.Readers;
using CUE4Parse.UE4.Shaders;
using CUE4Parse.UE4.Versions;
using CUE4Parse.UE4.Wwise;
+
using CUE4Parse_Conversion;
using CUE4Parse_Conversion.Sounds;
-using CUE4Parse.UE4.Objects.Core.Serialization;
-using EpicManifestParser.Objects;
+
+using EpicManifestParser;
+
using FModel.Creator;
using FModel.Extensions;
using FModel.Framework;
@@ -42,13 +48,20 @@ using FModel.Settings;
using FModel.Views;
using FModel.Views.Resources.Controls;
using FModel.Views.Snooper;
+
using Newtonsoft.Json;
+
using Ookii.Dialogs.Wpf;
+
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
+
using Serilog;
+
using SkiaSharp;
+
using UE4Config.Parsing;
+
using Application = System.Windows.Application;
namespace FModel.ViewModels;
@@ -190,45 +203,41 @@ public class CUE4ParseViewModel : ViewModel
case "FortniteLive":
{
var manifestInfo = _apiEndpointView.EpicApi.GetManifest(cancellationToken);
- if (manifestInfo == null)
+ if (manifestInfo is null)
{
- throw new Exception("Could not load latest Fortnite manifest, you may have to switch to your local installation.");
+ throw new FileLoadException("Could not load latest Fortnite manifest, you may have to switch to your local installation.");
}
- byte[] manifestData;
- var chunksDir = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
- var manifestPath = Path.Combine(chunksDir.FullName, manifestInfo.FileName);
- if (File.Exists(manifestPath))
+ var cacheDir = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data")).FullName;
+ var manifestOptions = new ManifestParseOptions
{
- manifestData = File.ReadAllBytes(manifestPath);
- }
- else
- {
- manifestData = manifestInfo.DownloadManifestData();
- File.WriteAllBytes(manifestPath, manifestData);
- }
+ ChunkCacheDirectory = cacheDir,
+ ManifestCacheDirectory = cacheDir,
+ ChunkBaseUrl = "http://epicgames-download1.akamaized.net/Builds/Fortnite/CloudDir/",
+ Zlibng = ZlibHelper.Instance
+ };
- var manifest = new Manifest(manifestData, new ManifestOptions
- {
- ChunkBaseUri = new Uri("http://epicgames-download1.akamaized.net/Builds/Fortnite/CloudDir/ChunksV4/", UriKind.Absolute),
- ChunkCacheDirectory = chunksDir
- });
+ var startTs = Stopwatch.GetTimestamp();
+ var (manifest, _) = manifestInfo.DownloadAndParseAsync(manifestOptions,
+ cancellationToken: cancellationToken).GetAwaiter().GetResult();
+ var parseTime = Stopwatch.GetElapsedTime(startTs);
+ const bool cacheChunksAsIs = false;
- foreach (var fileManifest in manifest.FileManifests)
+ foreach (var fileManifest in manifest.FileManifestList)
{
- if (fileManifest.Name.Equals("Cloud/IoStoreOnDemand.ini", StringComparison.OrdinalIgnoreCase))
+ if (fileManifest.FileName.Equals("Cloud/IoStoreOnDemand.ini", StringComparison.OrdinalIgnoreCase))
{
- IoStoreOnDemand.Read(new StreamReader(fileManifest.GetStream()));
+ IoStoreOnDemand.Read(new StreamReader(fileManifest.GetStream(cacheChunksAsIs)));
continue;
}
- if (!_fnLive.IsMatch(fileManifest.Name)) continue;
+ if (!_fnLive.IsMatch(fileManifest.FileName)) continue;
- p.RegisterVfs(fileManifest.Name, new Stream[] { fileManifest.GetStream() }
- , it => new FStreamArchive(it, manifest.FileManifests.First(x => x.Name.Equals(it)).GetStream(), p.Versions));
+ p.RegisterVfs(fileManifest.FileName, [fileManifest.GetStream(cacheChunksAsIs)]
+ , it => new FStreamArchive(it, manifest.FileManifestList.First(x => x.FileName.Equals(it)).GetStream(cacheChunksAsIs), p.Versions));
}
FLogger.Append(ELog.Information, () =>
- FLogger.Text($"Fortnite has been loaded successfully in {manifest.ParseTime.TotalMilliseconds}ms", Constants.WHITE, true));
+ FLogger.Text($"Fortnite [LIVE] has been loaded successfully in {parseTime.TotalMilliseconds}ms", Constants.WHITE, true));
break;
}
case "ValorantLive":
@@ -241,7 +250,7 @@ public class CUE4ParseViewModel : ViewModel
for (var i = 0; i < manifestInfo.Paks.Length; i++)
{
- p.RegisterVfs(manifestInfo.Paks[i].GetFullName(), new[] { manifestInfo.GetPakStream(i) });
+ p.RegisterVfs(manifestInfo.Paks[i].GetFullName(), [manifestInfo.GetPakStream(i)]);
}
FLogger.Append(ELog.Information, () =>
@@ -252,12 +261,19 @@ public class CUE4ParseViewModel : ViewModel
break;
case DefaultFileProvider:
+ {
var ioStoreOnDemandPath = Path.Combine(UserSettings.Default.GameDirectory, "..\\..\\..\\Cloud\\IoStoreOnDemand.ini");
- if (File.Exists(ioStoreOnDemandPath)) IoStoreOnDemand.Read(new StringReader(File.ReadAllText(ioStoreOnDemandPath)));
+ if (File.Exists(ioStoreOnDemandPath))
+ {
+ using var s = new StreamReader(ioStoreOnDemandPath);
+ IoStoreOnDemand.Read(s);
+ }
break;
+ }
}
Provider.Initialize();
+ Log.Information($"{Provider.Versions.Game} ({Provider.Versions.Platform}) | Archives: x{Provider.UnloadedVfs.Count} | AES: x{Provider.RequiredKeys.Count}");
foreach (var vfs in Provider.UnloadedVfs) // push files from the provider to the ui
{
@@ -305,6 +321,10 @@ public class CUE4ParseViewModel : ViewModel
}
InternalGameName = Provider.InternalGameName;
+
+ var aesMax = Provider.RequiredKeys.Count + Provider.Keys.Count;
+ var archiveMax = Provider.UnloadedVfs.Count + Provider.MountedVfs.Count;
+ Log.Information($"Project: {InternalGameName} | Mounted: {Provider.MountedVfs.Count}/{archiveMax} | AES: {Provider.Keys.Count}/{aesMax}");
}
public void ClearProvider()
diff --git a/FModel/Views/Snooper/Options.cs b/FModel/Views/Snooper/Options.cs
index aab519e0..0493fcbd 100644
--- a/FModel/Views/Snooper/Options.cs
+++ b/FModel/Views/Snooper/Options.cs
@@ -42,6 +42,12 @@ public class Options
Icons = new Dictionary
{
["material"] = new ("materialicon"),
+ ["square"] = new ("square"),
+ ["square_off"] = new ("square_off"),
+ ["cube"] = new ("cube"),
+ ["cube_off"] = new ("cube_off"),
+ ["light"] = new ("light"),
+ ["light_off"] = new ("light_off"),
["noimage"] = new ("T_Placeholder_Item_Image"),
["checker"] = new ("checker"),
["pointlight"] = new ("pointlight"),
diff --git a/FModel/Views/Snooper/SnimGui.cs b/FModel/Views/Snooper/SnimGui.cs
index e774c83f..ff28a65a 100644
--- a/FModel/Views/Snooper/SnimGui.cs
+++ b/FModel/Views/Snooper/SnimGui.cs
@@ -215,15 +215,9 @@ public class SnimGui
ImGui.SeparatorText("Editor");
if (ImGui.BeginTable("world_editor", 2))
{
- Layout("Skybox");ImGui.PushID(1);
- ImGui.Checkbox("", ref s.Renderer.ShowSkybox);
- ImGui.PopID();Layout("Grid");ImGui.PushID(2);
- ImGui.Checkbox("", ref s.Renderer.ShowGrid);
- ImGui.PopID();Layout("Lights");ImGui.PushID(3);
- ImGui.Checkbox("", ref s.Renderer.ShowLights);
- ImGui.PopID();Layout("Animate With Rotation Only");ImGui.PushID(4);
+ Layout("Animate With Rotation Only");ImGui.PushID(1);
ImGui.Checkbox("", ref s.Renderer.AnimateWithRotationOnly);
- ImGui.PopID();Layout("Vertex Colors");ImGui.PushID(5);
+ ImGui.PopID();Layout("Vertex Colors");ImGui.PushID(2);
var c = (int) s.Renderer.Color;
ImGui.Combo("vertex_colors", ref c,
"Default\0Sections\0Colors\0Normals\0Texture Coordinates\0");
@@ -783,13 +777,36 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
s.CursorState = CursorState.Normal;
}
+ const float margin = 7.5f;
+ var buttonWidth = 14.0f * ImGui.GetWindowDpiScale();
+ var basePos = new Vector2( size.X - buttonWidth - margin * 2, ImGui.GetFrameHeight() + margin);
+ ImGui.SetCursorPos(basePos);
+ ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero);
+ ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0.2f));
+ ImGui.ImageButton("skybox_btn", s.Renderer.Options.Icons[s.Renderer.ShowSkybox ? "cube" : "cube_off"].GetPointer(), new Vector2(buttonWidth));
+ TooltipCheckbox("Skybox", ref s.Renderer.ShowSkybox);
+
+ basePos.X -= buttonWidth + margin;
+ ImGui.SetCursorPos(basePos);
+ ImGui.ImageButton("grid_btn", s.Renderer.Options.Icons[s.Renderer.ShowGrid ? "square" : "square_off"].GetPointer(), new Vector2(buttonWidth));
+ TooltipCheckbox("Grid", ref s.Renderer.ShowGrid);
+
+ basePos.X -= buttonWidth + margin;
+ ImGui.SetCursorPos(basePos);
+ ImGui.ImageButton("lights_btn", s.Renderer.Options.Icons[s.Renderer.ShowLights ? "light" : "light_off"].GetPointer(), new Vector2(buttonWidth));
+ TooltipCheckbox("Lights", ref s.Renderer.ShowLights);
+
+ ImGui.PopStyleColor();
+
+
float framerate = ImGui.GetIO().Framerate;
- ImGui.SetCursorPos(size with { X = 7.5f });
+ ImGui.SetCursorPos(size with { X = margin });
ImGui.Text($"FPS: {framerate:0} ({1000.0f / framerate:0.##} ms)");
const string label = "Previewed content may differ from final version saved or used in-game.";
- ImGui.SetCursorPos(size with { X = size.X - ImGui.CalcTextSize(label).X - 7.5f });
+ ImGui.SetCursorPos(size with { X = size.X - ImGui.CalcTextSize(label).X - margin });
ImGui.TextColored(new Vector4(0.50f, 0.50f, 0.50f, 1.00f), label);
+
}, false);
ImGui.PopStyleVar();
}
@@ -906,6 +923,17 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
if (ImGui.IsItemClicked()) ImGui.SetClipboardText(text ?? label);
}
+ private static void TooltipCheckbox(string tooltip, ref bool value)
+ {
+ if (ImGui.IsItemHovered())
+ {
+ ImGui.BeginTooltip();
+ ImGui.Text($"{tooltip}: {value}");
+ ImGui.EndTooltip();
+ }
+ if (ImGui.IsItemClicked()) value = !value;
+ }
+
private void Theme()
{
var style = ImGui.GetStyle();