From 046677b875bbb84b48f2b1c665a772b8d156905d Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Tue, 8 Jul 2025 20:42:58 +1000 Subject: [PATCH] Improvements --- FModel/Extensions/KismetExtensions.cs | 88 +++++++++++++++---------- FModel/ViewModels/CUE4ParseViewModel.cs | 63 ++++++++++-------- 2 files changed, 87 insertions(+), 64 deletions(-) diff --git a/FModel/Extensions/KismetExtensions.cs b/FModel/Extensions/KismetExtensions.cs index c84f06ed..76562497 100644 --- a/FModel/Extensions/KismetExtensions.cs +++ b/FModel/Extensions/KismetExtensions.cs @@ -17,16 +17,43 @@ namespace FModel.Extensions; public static class KismetExtensions { - public static string GetPrefix(string type, string extra = "") // todo: implement better handling + public static string GetPrefix(this UStruct val) { - return type switch + var ret = string.Empty; + var super = val; + + while (super is not null) { - "FNameProperty" or "FPackageIndex" or "FTextProperty" or "FStructProperty" => "F", - "UBlueprintGeneratedClass" or "FActorProperty" => "A", - "FObjectProperty" when extra.Contains("Actor") => "A", - "ResolvedScriptObject" or "ResolvedLoadedObject" or "FSoftObjectProperty" or "FObjectProperty" => "U", - _ => "" - }; + if (super.Name == "Actor") + { + ret += 'A'; + break; + } + + if (super.Name == "Interface") + { + ret += 'I'; + break; + } + + if (super.Name == "Object") + { + ret += 'U'; + break; + } + + super = super?.SuperStruct?.Load(); + } + + if (string.IsNullOrEmpty(ret)) + ret += 'U'; + + return ret; + } + + public static string GetPrefix(this string val) // todo: val.Contains("Struct") ? "F" findout + { + return val.Contains(".Actor") ? "A" : val.Contains("Interface") ? "I" : "U"; } // GetUnknownFieldType and GetPropertyType from @@ -77,36 +104,25 @@ public static class KismetExtensions _ => GetUnknownFieldType(property) }; } - public static string GetPropertyType(FProperty? property) + public static bool isPointer(FProperty p) => + p.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || + p.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || + p.PropertyFlags.HasFlag(EPropertyFlags.ContainsInstancedReference) || + p.GetType() == typeof(FObjectProperty); + + public static string GetPropertyType(FProperty property) { if (property is null) return "None"; - bool isPointer(FProperty p) => - p.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || - property.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || - p.PropertyFlags.HasFlag(EPropertyFlags.ContainsInstancedReference); - return property switch { - FSetProperty s => $"TSet<{GetPrefix(s.ElementProp.GetType().Name)}{GetPropertyType(s.ElementProp)}{(isPointer(s) ? "*" : "")}>", - FMapProperty m => $"TMap<{GetPrefix(m.KeyProp.GetType().Name)}{GetPropertyType(m.KeyProp)}, {GetPrefix(m.ValueProp.GetType().Name)}{GetPropertyType(m.ValueProp)}{(isPointer(m) ? "*" : "")}>", - FArrayProperty a => $"TArray<{GetPrefix(a.Inner.GetType().Name)}{GetPropertyType(a.Inner)}{(isPointer(a) || GetPropertyProperty(a.Inner.GetType().Name) ? "*" : "")}>", + FSetProperty s => $"TSet<{s?.ElementProp.GetType().Name.GetPrefix()}{GetPropertyType(s.ElementProp)}{(isPointer(s) ? "*" : "")}>", + FMapProperty m => $"TMap<{m?.KeyProp.GetType().Name.GetPrefix()}{GetPropertyType(m.KeyProp)}, {m.ValueProp.GetType().Name.GetPrefix()}{GetPropertyType(m.ValueProp)}{(isPointer(m) ? "*" : "")}>", + FArrayProperty a => $"TArray<{a?.Inner.GetType().Name.GetPrefix()}{GetPropertyType(a.Inner)}{(isPointer(a) || a.Inner.GetType().Name == nameof(FObjectProperty) ? "*" : "")}>", _ => GetPropertyType((object)property) }; } - public static bool GetPropertyProperty(object? property) - { - if (property is null) - return false; - - return property switch - { - FObjectProperty => true, - _ => false - }; - } - public static string FormatStructFallback(FStructFallback fallback) { if (fallback.Properties.Count == 0) @@ -245,7 +261,7 @@ public static class KismetExtensions } else { - outputBuilder.Append($"\t\t{GetPrefix(op?.StackNode?.ResolvedObject?.Outer?.GetType()?.Name ?? string.Empty)}{op?.StackNode?.Name.Replace(" ", "")}("); + outputBuilder.Append($"\t\t{op?.StackNode?.ResolvedObject?.Outer?.GetType()?.Name.GetPrefix()}{op?.StackNode?.Name.Replace(" ", "")}("); } for (int i = 0; i < opp.Length; i++) @@ -275,7 +291,7 @@ public static class KismetExtensions } else { - outputBuilder.Append($"\t\t{op?.StackNode?.Name.Replace(" ", "")}(");//{GetPrefix(op?.StackNode?.ResolvedObject?.Outer?.GetType()?.Name)} + outputBuilder.Append($"\t\t{op?.StackNode?.Name.Replace(" ", "")}(");// maybe use GetPrefix(op?.StackNode?.ResolvedObject?.Outer?.GetType()?.Name)} } for (int i = 0; i < opp.Length; i++) @@ -296,7 +312,7 @@ public static class KismetExtensions EX_FinalFunction op = (EX_FinalFunction) expression; KismetExpression[] opp = op.Parameters; outputBuilder.Append(isParameter ? string.Empty : "\t\t"); - outputBuilder.Append($"{GetPrefix(op.StackNode.ResolvedObject.Outer.GetType().Name)}{op.StackNode.ResolvedObject.Outer.Name.ToString().Replace(" ", "")}::{op.StackNode.Name}("); + outputBuilder.Append($"{op.StackNode.ResolvedObject.Outer.GetType().Name.GetPrefix()}{op.StackNode.ResolvedObject.Outer.Name.ToString().Replace(" ", "")}::{op.StackNode.Name}("); for (int i = 0; i < opp.Length; i++) { @@ -606,7 +622,7 @@ public static class KismetExtensions case EExprToken.EX_StructConst: { EX_StructConst op = (EX_StructConst) expression; - outputBuilder.Append($"{GetPrefix(op.Struct.GetType().Name)}{op.Struct.Name}"); + outputBuilder.Append($"{op.Struct.GetType().Name.GetPrefix()}{op.Struct.Name}"); outputBuilder.Append('('); for (int i = 0; i < op.Properties.Length; i++) { @@ -627,11 +643,11 @@ public static class KismetExtensions if (classString?.Contains('.') == true) { - outputBuilder.Append(GetPrefix(op?.Value?.ResolvedObject?.Class?.GetType().Name) + classString.Split('.')[1]); + outputBuilder.Append(op?.Value?.ResolvedObject?.Class?.GetType().Name.GetPrefix() + classString.Split('.')[1]); } else { - outputBuilder.Append(GetPrefix(op?.Value?.ResolvedObject?.Class?.GetType().Name) + classString); + outputBuilder.Append(op?.Value?.ResolvedObject?.Class?.GetType().Name.GetPrefix() + classString); } outputBuilder.Append(">(\""); var resolvedObject = op?.Value?.ResolvedObject; @@ -883,7 +899,7 @@ public static class KismetExtensions if (!check) outputBuilder.Append(' '); ProcessExpression(op.ReturnExpression.Token, op.ReturnExpression, outputBuilder, jumpCodeOffsets, true); - outputBuilder.AppendLine(";\n\n"); + outputBuilder.AppendLine(";"); break; } case EExprToken.EX_RotationConst: diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 5d495b9b..1b87ad79 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -60,12 +60,12 @@ using SkiaSharp; using UE4Config.Parsing; using Application = System.Windows.Application; using FGuid = CUE4Parse.UE4.Objects.Core.Misc.FGuid; -using CUE4Parse.UE4.Assets.Objects.Properties; using CUE4Parse.UE4.Assets.Objects; using CUE4Parse.UE4.Kismet; using CUE4Parse.UE4.Objects.Core.Math; using CUE4Parse.UE4.Objects.GameplayTags; using System.Text; +using AssetRipper.TextureDecoder.Rgb; namespace FModel.ViewModels; @@ -353,7 +353,7 @@ public class CUE4ParseViewModel : ViewModel { var mappingsFolder = Path.Combine(UserSettings.Default.OutputDirectory, ".data"); if (endpoint.Path == "$.[?(@.meta.compressionMethod=='Oodle')].['url','fileName']") endpoint.Path = "$.[0].['url','fileName']"; - var mappings = _apiEndpointView.DynamicApi.GetMappings(default, endpoint.Url, endpoint.Path); + var mappings = _apiEndpointView.DynamicApi.GetMappings(CancellationToken.None, endpoint.Url, endpoint.Path); if (mappings is { Length: > 0 }) { foreach (var mapping in mappings) @@ -428,7 +428,7 @@ public class CUE4ParseViewModel : ViewModel var ioStoreOnDemandPath = Path.Combine(UserSettings.Default.GameDirectory, "..\\..\\..\\Cloud", inst[0].Value.SubstringAfterLast("/").SubstringBefore("\"")); if (!File.Exists(ioStoreOnDemandPath)) return; - await _apiEndpointView.EpicApi.VerifyAuth(default); + await _apiEndpointView.EpicApi.VerifyAuth(CancellationToken.None); await Provider.RegisterVfs(new IoChunkToc(ioStoreOnDemandPath), new IoStoreOnDemandOptions { ChunkBaseUri = new Uri("https://download.epicgames.com/ias/fortnite/", UriKind.Absolute), @@ -471,7 +471,7 @@ public class CUE4ParseViewModel : ViewModel if (!Provider.ProjectName.Equals("fortnitegame", StringComparison.OrdinalIgnoreCase) || HotfixedResourcesDone) return Task.CompletedTask; return Task.Run(() => { - var hotfixes = ApplicationService.ApiEndpointView.CentralApi.GetHotfixes(default, Provider.GetLanguageCode(UserSettings.Default.AssetLanguage)); + var hotfixes = ApplicationService.ApiEndpointView.CentralApi.GetHotfixes(CancellationToken.None, Provider.GetLanguageCode(UserSettings.Default.AssetLanguage)); if (hotfixes == null) return; Provider.Internationalization.Override(hotfixes); @@ -796,7 +796,7 @@ public class CUE4ParseViewModel : ViewModel } } - private bool CheckExport(CancellationToken cancellationToken, IPackage pkg, int index, EBulkType bulk = EBulkType.None) // return true once you wanna stop searching for exports + private bool CheckExport(CancellationToken cancellationToken, IPackage pkg, int index, EBulkType bulk = EBulkType.None) // return true once you want to stop searching for exports { var isNone = bulk == EBulkType.None; var updateUi = !HasFlag(bulk, EBulkType.Auto); @@ -999,8 +999,9 @@ public class CUE4ParseViewModel : ViewModel if (dummy is not UClass || pointer.Object.Value is not UClass blueprint) continue; - var typePrefix = KismetExtensions.GetPrefix(blueprint.GetType().Name); - outputBuilder.AppendLine($"class {typePrefix}{blueprint.Name} : public {typePrefix}{blueprint?.SuperStruct?.Name ?? string.Empty}\n{{\npublic:"); + var typePrefix = blueprint?.SuperStruct.Load().GetPrefix(); + var modifierStr = blueprint.Flags.HasAnyFlags(EObjectFlags.RF_Public) ? "public" : "private"; + outputBuilder.AppendLine($"class {typePrefix}{blueprint.Name} : {modifierStr} {typePrefix}{blueprint?.SuperStruct?.Name ?? string.Empty}\n{{\n{modifierStr}:"); if (!blueprint.ClassDefaultObject.TryLoad(out var bpObject)) continue; @@ -1110,21 +1111,19 @@ public class CUE4ParseViewModel : ViewModel var propertyName = property.Name.ToString().Replace(" ", ""); var type = KismetExtensions.GetPropertyType(property); - var prefix = KismetExtensions.GetPrefix(property.GetType().Name); - string pointerIdentifier; - if (property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || - property.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || - KismetExtensions.GetPropertyProperty(property)) + var prefix = ""; + switch (property) { - pointerIdentifier = "*"; - } - else - { - pointerIdentifier = string.Empty; + case FFieldPathProperty pathProp: + prefix = pathProp.PropertyClass.ToString().GetPrefix(); + break; + case FObjectProperty objectProp: + prefix = objectProp.PropertyClass.ToString().GetPrefix(); + break; } - outputBuilder.AppendLine($"\t{prefix}{type}{pointerIdentifier} {propertyName} = {propertyName}fmodelholder;"); + outputBuilder.AppendLine($"\t{prefix}{type}{(KismetExtensions.isPointer(property) ? '*' : "")} {propertyName} = {propertyName}fmodelholder;"); } { @@ -1155,8 +1154,8 @@ public class CUE4ParseViewModel : ViewModel foreach (var property in function.ScriptBytecode) { - string? label = null; - int? offset = null; + string label = string.Empty; + int offset = 0; switch (property.Token) { @@ -1173,20 +1172,20 @@ public class CUE4ParseViewModel : ViewModel case EExprToken.EX_LocalFinalFunction: { EX_FinalFunction op = (EX_FinalFunction) property; - label = op.StackNode?.Name?.ToString()?.Split('.').Last().Split('[')[0]; + label = op.StackNode?.Name?.Split('.').Last().Split('[')[0]; - if (op.Parameters.Length == 1 && op.Parameters[0] is EX_IntConst intConst) + if (op is { Parameters: [EX_IntConst intConst] }) offset = intConst.Value; break; } } - if (!string.IsNullOrEmpty(label) && offset.HasValue) + if (!string.IsNullOrEmpty(label)) { if (!jumpCodeOffsetsMap.TryGetValue(label, out var list)) jumpCodeOffsetsMap[label] = list = new List(); - list.Add(offset.Value); + list.Add(offset); } } } @@ -1201,16 +1200,24 @@ public class CUE4ParseViewModel : ViewModel { var name = property.Name.ToString(); var plainName = property.Name.PlainText; - var prefix = KismetExtensions.GetPrefix(property.GetType().Name); + var prefix = ""; + switch (property) + { + case FFieldPathProperty pathProp: + prefix = pathProp.PropertyClass.ToString().GetPrefix(); + break; + case FObjectProperty objectProp: + prefix = objectProp.PropertyClass.ToString().GetPrefix(); + break; + } var type = KismetExtensions.GetPropertyType(property); var isConst = property.PropertyFlags.HasFlag(EPropertyFlags.ConstParm); var isOut = property.PropertyFlags.HasFlag(EPropertyFlags.OutParm); - var isInstanced = property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference); var isEdit = property.PropertyFlags.HasFlag(EPropertyFlags.Edit); if (plainName == "ReturnValue") { - returnFunc = $"{(isConst ? "const " : "")}{prefix}{type}{(isInstanced || prefix == "U" ? "*" : "")}"; + returnFunc = $"{(isConst ? "const " : "")}{prefix}{type}{(KismetExtensions.isPointer(property) ? '*' : "")}"; continue; } @@ -1220,7 +1227,7 @@ public class CUE4ParseViewModel : ViewModel continue; var strippedVerseName = Regex.Replace(name, @"^__verse_0x[0-9A-Fa-f]+_", ""); - argsList += $"{(isConst ? "const " : "")}{prefix}{type}{(isInstanced || prefix == "U" ? "*" : "")}{(isOut ? "&" : "")} {strippedVerseName}, "; + argsList += $"{(isConst ? "const " : "")}{prefix}{type}{(KismetExtensions.isPointer(property) ? '*' : "")}{(isOut ? '&' : "")} {strippedVerseName}, "; } } argsList = argsList.TrimEnd(',', ' ');