From b5c1fdccc6a178a34d89403235b2147c5a9c9624 Mon Sep 17 00:00:00 2001 From: Asval Date: Fri, 14 Feb 2025 19:23:10 +0100 Subject: [PATCH 1/3] SoftObjectPath notify animation support Marvel Rivals emotes will display notified models --- CUE4Parse | 2 +- FModel/Views/Snooper/Animations/Animation.cs | 4 +- .../Views/Snooper/Animations/TimeTracker.cs | 4 +- FModel/Views/Snooper/Renderer.cs | 156 ++++++++---------- FModel/Views/Snooper/SnimGui.cs | 2 +- 5 files changed, 73 insertions(+), 95 deletions(-) diff --git a/CUE4Parse b/CUE4Parse index 2aed4da1..7c76fdc0 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 2aed4da1ee440cd421222526bdc501031f685be6 +Subproject commit 7c76fdc0d3053170121bd5b6f0bb3b5fa28afbf6 diff --git a/FModel/Views/Snooper/Animations/Animation.cs b/FModel/Views/Snooper/Animations/Animation.cs index 87936494..2b1e4921 100644 --- a/FModel/Views/Snooper/Animations/Animation.cs +++ b/FModel/Views/Snooper/Animations/Animation.cs @@ -35,9 +35,9 @@ public class Animation : IDisposable _export = export; Path = _export.GetPathName(); Name = _export.Name; - Sequences = Array.Empty(); + Sequences = []; Framing = new Dictionary(); - AttachedModels = new List(); + AttachedModels = []; } public Animation(UObject export, CAnimSet animSet) : this(export) diff --git a/FModel/Views/Snooper/Animations/TimeTracker.cs b/FModel/Views/Snooper/Animations/TimeTracker.cs index 5417f39c..28cc9538 100644 --- a/FModel/Views/Snooper/Animations/TimeTracker.cs +++ b/FModel/Views/Snooper/Animations/TimeTracker.cs @@ -20,7 +20,7 @@ public class TimeTracker : IDisposable public bool IsActive; public float ElapsedTime; public float MaxElapsedTime; - public int TimeMultiplier; + public float TimeMultiplier; public TimeTracker() { @@ -51,7 +51,7 @@ public class TimeTracker : IDisposable if (doMet) { MaxElapsedTime = 0.01f; - TimeMultiplier = 1; + TimeMultiplier = 1f; } } diff --git a/FModel/Views/Snooper/Renderer.cs b/FModel/Views/Snooper/Renderer.cs index b3ea7dd6..0dfceee2 100644 --- a/FModel/Views/Snooper/Renderer.cs +++ b/FModel/Views/Snooper/Renderer.cs @@ -122,111 +122,89 @@ public class Renderer : IDisposable public void Animate(Lazy anim) => Animate(anim.Value, Options.SelectedModel); private void Animate(UObject anim, FGuid guid) { - if (!Options.TryGetModel(guid, out var m) || m is not SkeletalModel model) + if (anim is not UAnimSequenceBase animBase || !Options.TryGetModel(guid, out var m) || m is not SkeletalModel model) return; - float maxElapsedTime; - switch (anim) + var animSet = animBase switch { - case UAnimSequence animSequence when animSequence.Skeleton.TryLoad(out USkeleton skeleton): - { - var animSet = skeleton.ConvertAnims(animSequence); - var animation = new Animation(animSequence, animSet, guid); - maxElapsedTime = animation.TotalElapsedTime; - model.Skeleton.Animate(animSet); - Options.AddAnimation(animation); - break; - } - case UAnimMontage animMontage when animMontage.Skeleton.TryLoad(out USkeleton skeleton): - { - var animSet = skeleton.ConvertAnims(animMontage); - var animation = new Animation(animMontage, animSet, guid); - maxElapsedTime = animation.TotalElapsedTime; - model.Skeleton.Animate(animSet); - Options.AddAnimation(animation); + UAnimSequence animSequence when animSequence.Skeleton.TryLoad(out USkeleton skeleton) => skeleton.ConvertAnims(animSequence), + UAnimMontage animMontage when animMontage.Skeleton.TryLoad(out USkeleton skeleton) => skeleton.ConvertAnims(animMontage), + UAnimComposite animComposite when animComposite.Skeleton.TryLoad(out USkeleton skeleton) => skeleton.ConvertAnims(animComposite), + _ => throw new ArgumentException("Unknown animation type") + }; - foreach (var notifyEvent in animMontage.Notifies) + var animation = new Animation(anim, animSet, guid); + model.Skeleton.Animate(animSet); + Options.AddAnimation(animation); + + foreach (var notifyEvent in animBase.Notifies) + { + if (!notifyEvent.NotifyStateClass.TryLoad(out UObject notifyClass) || + !notifyClass.TryGetValue(out UObject export, "SkeletalMeshProp", "StaticMeshProp", "Mesh", "SkeletalMeshTemplate")) + continue; + + var t = Transform.Identity; + if (notifyClass.TryGetValue(out FTransform offset, "Offset")) + { + t.Rotation = offset.Rotation; + t.Position = offset.Translation * Constants.SCALE_DOWN_RATIO; + t.Scale = offset.Scale3D; + } + + UModel addedModel = null; + switch (export) + { + case UStaticMesh st: { - if (!notifyEvent.NotifyStateClass.TryLoad(out UObject notifyClass) || - !notifyClass.TryGetValue(out FPackageIndex meshProp, "SkeletalMeshProp", "StaticMeshProp", "Mesh") || - !meshProp.TryLoad(out UObject export)) continue; - - var t = Transform.Identity; - if (notifyClass.TryGetValue(out FTransform offset, "Offset")) + guid = st.LightingGuid; + if (Options.TryGetModel(guid, out addedModel)) { - t.Rotation = offset.Rotation; - t.Position = offset.Translation * Constants.SCALE_DOWN_RATIO; - t.Scale = offset.Scale3D; + addedModel.AddInstance(t); } - - UModel addedModel = null; - switch (export) + else if (st.TryConvert(out var mesh)) { - case UStaticMesh st: - { - guid = st.LightingGuid; - if (Options.TryGetModel(guid, out addedModel)) - { - addedModel.AddInstance(t); - } - else if (st.TryConvert(out var mesh)) - { - addedModel = new StaticModel(st, mesh, t); - Options.Models[guid] = addedModel; - } - break; - } - case USkeletalMesh sk: - { - guid = Guid.NewGuid(); - if (!Options.Models.ContainsKey(guid) && sk.TryConvert(out var mesh)) - { - addedModel = new SkeletalModel(sk, mesh, t); - Options.Models[guid] = addedModel; - } - break; - } - } - - if (addedModel == null) - throw new ArgumentException("Unknown model type"); - - addedModel.IsProp = true; - if (notifyClass.TryGetValue(out UObject skeletalMeshPropAnimation, "SkeletalMeshPropAnimation", "Animation")) - Animate(skeletalMeshPropAnimation, guid); - if (notifyClass.TryGetValue(out FName socketName, "SocketName")) - { - t = Transform.Identity; - if (notifyClass.TryGetValue(out FVector location, "LocationOffset", "Location")) - t.Position = location * Constants.SCALE_DOWN_RATIO; - if (notifyClass.TryGetValue(out FRotator rotation, "RotationOffset", "Rotation")) - t.Rotation = rotation.Quaternion(); - if (notifyClass.TryGetValue(out FVector scale, "Scale")) - t.Scale = scale; - - var s = new Socket($"ANIM_{addedModel.Name}", socketName, t, true); - model.Sockets.Add(s); - addedModel.Attachments.Attach(model, addedModel.GetTransform(), s, - new SocketAttachementInfo { Guid = guid, Instance = addedModel.SelectedInstance }); + addedModel = new StaticModel(st, mesh, t); + Options.Models[guid] = addedModel; } + break; + } + case USkeletalMesh sk: + { + guid = Guid.NewGuid(); + if (!Options.Models.ContainsKey(guid) && sk.TryConvert(out var mesh)) + { + addedModel = new SkeletalModel(sk, mesh, t); + Options.Models[guid] = addedModel; + } + break; } - break; } - case UAnimComposite animComposite when animComposite.Skeleton.TryLoad(out USkeleton skeleton): + + if (addedModel == null) + throw new ArgumentException("Unknown model type"); + + addedModel.IsProp = true; + if (notifyClass.TryGetValue(out UObject skeletalMeshPropAnimation, "SkeletalMeshPropAnimation", "Animation", "AnimToPlay")) + Animate(skeletalMeshPropAnimation, guid); + if (notifyClass.TryGetValue(out FName socketName, "SocketName")) { - var animSet = skeleton.ConvertAnims(animComposite); - var animation = new Animation(animComposite, animSet, guid); - maxElapsedTime = animation.TotalElapsedTime; - model.Skeleton.Animate(animSet); - Options.AddAnimation(animation); - break; + t = Transform.Identity; + if (notifyClass.TryGetValue(out FVector location, "LocationOffset", "Location")) + t.Position = location * Constants.SCALE_DOWN_RATIO; + if (notifyClass.TryGetValue(out FRotator rotation, "RotationOffset", "Rotation")) + t.Rotation = rotation.Quaternion(); + if (notifyClass.TryGetValue(out FVector scale, "Scale")) + t.Scale = scale; + + var s = new Socket($"ANIM_{addedModel.Name}", socketName, t, true); + model.Sockets.Add(s); + addedModel.Attachments.Attach(model, addedModel.GetTransform(), s, + new SocketAttachementInfo { Guid = guid, Instance = addedModel.SelectedInstance }); } - default: - throw new ArgumentException(); } Options.Tracker.IsPaused = false; - Options.Tracker.SafeSetMaxElapsedTime(maxElapsedTime); + Options.Tracker.SafeSetMaxElapsedTime(animation.TotalElapsedTime); } public void Setup() diff --git a/FModel/Views/Snooper/SnimGui.cs b/FModel/Views/Snooper/SnimGui.cs index 4b182f79..b07cfb13 100644 --- a/FModel/Views/Snooper/SnimGui.cs +++ b/FModel/Views/Snooper/SnimGui.cs @@ -218,7 +218,7 @@ public class SnimGui Layout("Animate With Rotation Only");ImGui.PushID(1); ImGui.Checkbox("", ref s.Renderer.AnimateWithRotationOnly); ImGui.PopID();Layout("Time Multiplier");ImGui.PushID(2); - ImGui.DragInt("", ref s.Renderer.Options.Tracker.TimeMultiplier, 0.1f, 1, 8, "x%i", ImGuiSliderFlags.NoInput); + ImGui.DragFloat("", ref s.Renderer.Options.Tracker.TimeMultiplier, 0.01f, 0.25f, 8f, "x%.2f", ImGuiSliderFlags.NoInput); ImGui.PopID();Layout("Vertex Colors");ImGui.PushID(3); var c = (int) s.Renderer.Color; ImGui.Combo("vertex_colors", ref c, From c92103857e90917634b29dbc0ded21bce3260ede Mon Sep 17 00:00:00 2001 From: Asval Date: Sat, 15 Feb 2025 23:47:33 +0100 Subject: [PATCH 2/3] fixed backups being case sensitive --- CUE4Parse | 2 +- FModel/ViewModels/BackupManagerViewModel.cs | 5 +++-- FModel/ViewModels/Commands/LoadCommand.cs | 14 +++++++++----- FModel/Views/Snooper/Renderer.cs | 9 +++++---- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/CUE4Parse b/CUE4Parse index 7c76fdc0..c36748f2 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 7c76fdc0d3053170121bd5b6f0bb3b5fa28afbf6 +Subproject commit c36748f206d19eea45a6f2ea4cc82e56617d87c9 diff --git a/FModel/ViewModels/BackupManagerViewModel.cs b/FModel/ViewModels/BackupManagerViewModel.cs index 92a15561..0010b761 100644 --- a/FModel/ViewModels/BackupManagerViewModel.cs +++ b/FModel/ViewModels/BackupManagerViewModel.cs @@ -66,7 +66,7 @@ public class BackupManagerViewModel : ViewModel var backupFolder = Path.Combine(UserSettings.Default.OutputDirectory, "Backups"); var fileName = $"{_gameName}_{DateTime.Now:MM'_'dd'_'yyyy}.fbkp"; var fullPath = Path.Combine(backupFolder, fileName); - var func = new Func(x => !x.Path.EndsWith(".uexp") && !x.Path.EndsWith(".ubulk") && !x.Path.EndsWith(".uptnl")); + var func = new Func(x => !x.IsUePackagePayload); using var fileStream = new FileStream(fullPath, FileMode.Create); using var compressedStream = LZ4Stream.Encode(fileStream, LZ4Level.L00_FAST); @@ -80,7 +80,7 @@ public class BackupManagerViewModel : ViewModel if (!func(asset)) continue; writer.Write(asset.Size); writer.Write(asset.IsEncrypted); - writer.Write($"/{asset.Path.ToLower()}"); + writer.Write(asset.Path); } SaveCheck(fullPath, fileName, "created", "create"); @@ -121,6 +121,7 @@ public enum EBackupVersion : byte { BeforeVersionWasAdded = 0, Initial, + PerfectPath, // no more leading slash and ToLower LatestPlusOne, Latest = LatestPlusOne - 1 diff --git a/FModel/ViewModels/Commands/LoadCommand.cs b/FModel/ViewModels/Commands/LoadCommand.cs index 3e58b4e8..0a2683bb 100644 --- a/FModel/ViewModels/Commands/LoadCommand.cs +++ b/FModel/ViewModels/Commands/LoadCommand.cs @@ -182,7 +182,7 @@ public class LoadCommand : ViewModelCommand { case ELoadingMode.AllButNew: { - var paths = new HashSet(); + var paths = new HashSet(StringComparer.OrdinalIgnoreCase); var magic = archive.Read(); if (magic != BackupManagerViewModel.FBKP_MAGIC) { @@ -192,7 +192,7 @@ public class LoadCommand : ViewModelCommand cancellationToken.ThrowIfCancellationRequested(); archive.Position += 29; - paths.Add(archive.ReadString().ToLower()[1..]); + paths.Add(archive.ReadString()[1..]); archive.Position += 4; } } @@ -205,7 +205,10 @@ public class LoadCommand : ViewModelCommand cancellationToken.ThrowIfCancellationRequested(); archive.Position += sizeof(long) + sizeof(byte); - paths.Add(archive.ReadString().ToLower()[1..]); + var fullPath = archive.ReadString(); + if (version < EBackupVersion.PerfectPath) fullPath = fullPath[1..]; + + paths.Add(fullPath); } } @@ -233,7 +236,7 @@ public class LoadCommand : ViewModelCommand var uncompressedSize = archive.Read(); var isEncrypted = archive.ReadFlag(); archive.Position += 4; - var fullPath = archive.ReadString().ToLower()[1..]; + var fullPath = archive.ReadString()[1..]; archive.Position += 4; AddEntry(fullPath, uncompressedSize, isEncrypted, entries); @@ -249,7 +252,8 @@ public class LoadCommand : ViewModelCommand var uncompressedSize = archive.Read(); var isEncrypted = archive.ReadFlag(); - var fullPath = archive.ReadString().ToLower()[1..]; + var fullPath = archive.ReadString(); + if (version < EBackupVersion.PerfectPath) fullPath = fullPath[1..]; AddEntry(fullPath, uncompressedSize, isEncrypted, entries); } diff --git a/FModel/Views/Snooper/Renderer.cs b/FModel/Views/Snooper/Renderer.cs index 0dfceee2..f067336e 100644 --- a/FModel/Views/Snooper/Renderer.cs +++ b/FModel/Views/Snooper/Renderer.cs @@ -122,14 +122,15 @@ public class Renderer : IDisposable public void Animate(Lazy anim) => Animate(anim.Value, Options.SelectedModel); private void Animate(UObject anim, FGuid guid) { - if (anim is not UAnimSequenceBase animBase || !Options.TryGetModel(guid, out var m) || m is not SkeletalModel model) + if (anim is not UAnimSequenceBase animBase || !animBase.Skeleton.TryLoad(out USkeleton skeleton) || + !Options.TryGetModel(guid, out var m) || m is not SkeletalModel model) return; var animSet = animBase switch { - UAnimSequence animSequence when animSequence.Skeleton.TryLoad(out USkeleton skeleton) => skeleton.ConvertAnims(animSequence), - UAnimMontage animMontage when animMontage.Skeleton.TryLoad(out USkeleton skeleton) => skeleton.ConvertAnims(animMontage), - UAnimComposite animComposite when animComposite.Skeleton.TryLoad(out USkeleton skeleton) => skeleton.ConvertAnims(animComposite), + UAnimSequence animSequence => skeleton.ConvertAnims(animSequence), + UAnimMontage animMontage => skeleton.ConvertAnims(animMontage), + UAnimComposite animComposite => skeleton.ConvertAnims(animComposite), _ => throw new ArgumentException("Unknown animation type") }; From 4c790af232175bb4288f5654f7d82f6fe44cce4c Mon Sep 17 00:00:00 2001 From: Asval Date: Sun, 16 Feb 2025 00:20:28 +0100 Subject: [PATCH 3/3] fixed use of obsolete methods --- FModel/ViewModels/ApiEndpoints/AbstractApiProvider.cs | 6 ++++++ FModel/ViewModels/ApiEndpoints/DynamicApiEndpoint.cs | 2 +- .../ViewModels/ApiEndpoints/FortniteCentralApiEndpoint.cs | 2 +- FModel/Views/Snooper/Models/SkeletalModel.cs | 4 ++-- FModel/Views/Snooper/Models/StaticModel.cs | 4 ++-- FModel/Views/Snooper/Models/UModel.cs | 4 ++-- 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/FModel/ViewModels/ApiEndpoints/AbstractApiProvider.cs b/FModel/ViewModels/ApiEndpoints/AbstractApiProvider.cs index ea0f4c04..400cafe7 100644 --- a/FModel/ViewModels/ApiEndpoints/AbstractApiProvider.cs +++ b/FModel/ViewModels/ApiEndpoints/AbstractApiProvider.cs @@ -1,13 +1,19 @@ using RestSharp; +using RestSharp.Interceptors; namespace FModel.ViewModels.ApiEndpoints; public abstract class AbstractApiProvider { protected readonly RestClient _client; + protected readonly Interceptor _interceptor; protected AbstractApiProvider(RestClient client) { _client = client; + _interceptor = new CompatibilityInterceptor + { + OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; } + }; } } diff --git a/FModel/ViewModels/ApiEndpoints/DynamicApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/DynamicApiEndpoint.cs index 4b151c15..3e2fa9fb 100644 --- a/FModel/ViewModels/ApiEndpoints/DynamicApiEndpoint.cs +++ b/FModel/ViewModels/ApiEndpoints/DynamicApiEndpoint.cs @@ -66,7 +66,7 @@ public class DynamicApiEndpoint : AbstractApiProvider { var request = new FRestRequest(url) { - OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; } + Interceptors = [_interceptor] }; 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); diff --git a/FModel/ViewModels/ApiEndpoints/FortniteCentralApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/FortniteCentralApiEndpoint.cs index af7fe1e8..82af932f 100644 --- a/FModel/ViewModels/ApiEndpoints/FortniteCentralApiEndpoint.cs +++ b/FModel/ViewModels/ApiEndpoints/FortniteCentralApiEndpoint.cs @@ -15,7 +15,7 @@ public class FortniteCentralApiEndpoint : AbstractApiProvider { var request = new FRestRequest("https://fortnitecentral.genxgames.gg/api/v1/hotfixes") { - OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; } + Interceptors = [_interceptor] }; request.AddParameter("lang", language); var response = await _client.ExecuteAsync>>(request, token).ConfigureAwait(false); diff --git a/FModel/Views/Snooper/Models/SkeletalModel.cs b/FModel/Views/Snooper/Models/SkeletalModel.cs index 7d40778c..17592492 100644 --- a/FModel/Views/Snooper/Models/SkeletalModel.cs +++ b/FModel/Views/Snooper/Models/SkeletalModel.cs @@ -153,7 +153,7 @@ public class SkeletalModel : UModel GL.Disable(EnableCap.DepthTest); GL.Disable(EnableCap.CullFace); - GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line); + GL.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Line); foreach (var collision in Collisions) { var boneMatrix = Matrix4x4.Identity; @@ -162,7 +162,7 @@ public class SkeletalModel : UModel collision.Render(shader, boneMatrix); } - GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill); + GL.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill); GL.Enable(EnableCap.CullFace); GL.Enable(EnableCap.DepthTest); } diff --git a/FModel/Views/Snooper/Models/StaticModel.cs b/FModel/Views/Snooper/Models/StaticModel.cs index 0d669a83..e0464f42 100644 --- a/FModel/Views/Snooper/Models/StaticModel.cs +++ b/FModel/Views/Snooper/Models/StaticModel.cs @@ -148,12 +148,12 @@ public class StaticModel : UModel base.RenderCollision(shader); GL.Disable(EnableCap.CullFace); - GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line); + GL.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Line); foreach (var collision in Collisions) { collision.Render(shader, Matrix4x4.Identity); } - GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill); + GL.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill); GL.Enable(EnableCap.CullFace); } } diff --git a/FModel/Views/Snooper/Models/UModel.cs b/FModel/Views/Snooper/Models/UModel.cs index 4f398e19..62b95084 100644 --- a/FModel/Views/Snooper/Models/UModel.cs +++ b/FModel/Views/Snooper/Models/UModel.cs @@ -255,7 +255,7 @@ public abstract class UModel : IRenderableModel } Vao.Bind(); - GL.PolygonMode(MaterialFace.FrontAndBack, ShowWireframe ? PolygonMode.Line : PolygonMode.Fill); + GL.PolygonMode(TriangleFace.FrontAndBack, ShowWireframe ? PolygonMode.Line : PolygonMode.Fill); foreach (var section in Sections) { if (!section.Show) continue; @@ -275,7 +275,7 @@ public abstract class UModel : IRenderableModel GL.DrawElementsInstanced(PrimitiveType.Triangles, section.FacesCount, DrawElementsType.UnsignedInt, section.FirstFaceIndexPtr, TransformsCount); } - GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill); + GL.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill); Vao.Unbind(); if (IsSelected)