From f64a333847355e3d7b6cb0a74eafb2791cd75f4d Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Sat, 5 Jul 2025 09:01:29 +1000 Subject: [PATCH 01/25] Blueprint "Decompile" option turns blueprint logic into readable c++ code, option shows only when setting is on --- FModel/Extensions/KismetExtensions.cs | 1096 +++++++++++++++++ FModel/Extensions/StringExtensions.cs | 14 + FModel/MainWindow.xaml | 17 +- FModel/MainWindow.xaml.cs | 12 + FModel/Settings/UserSettings.cs | 7 + FModel/ViewModels/CUE4ParseViewModel.cs | 496 +++++++- .../Commands/RightClickMenuCommand.cs | 10 +- .../Controls/Aed/JumpElementGenerator.cs | 35 + .../Controls/Aed/JumpVisualLineText.cs | 80 ++ .../Resources/Controls/AvalonEditor.xaml.cs | 1 + .../Controls/PropertiesPopout.xaml.cs | 3 +- FModel/Views/SettingsView.xaml | 5 + 12 files changed, 1772 insertions(+), 4 deletions(-) create mode 100644 FModel/Extensions/KismetExtensions.cs create mode 100644 FModel/Views/Resources/Controls/Aed/JumpElementGenerator.cs create mode 100644 FModel/Views/Resources/Controls/Aed/JumpVisualLineText.cs diff --git a/FModel/Extensions/KismetExtensions.cs b/FModel/Extensions/KismetExtensions.cs new file mode 100644 index 00000000..0ac16af9 --- /dev/null +++ b/FModel/Extensions/KismetExtensions.cs @@ -0,0 +1,1096 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using CUE4Parse.FileProvider; +using CUE4Parse.FileProvider.Objects; +using CUE4Parse.UE4.Assets; +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.Core.Misc; +using CUE4Parse.UE4.Objects.Engine.Ai; +using CUE4Parse.UE4.Objects.Engine.GameFramework; +using CUE4Parse.UE4.Objects.GameplayTags; +using CUE4Parse.UE4.Objects.UObject; +using FModel.Settings; +using Microsoft.VisualBasic.Logging; + +namespace FModel.Extensions; + +public static class KismetExtensions +{ + public static string GetPrefix(string? type, string? extra = "") + { + return type switch + { + "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", + _ => "" + }; + } + + // GetUnknownFieldType and GetPropertyType from + // https://github.com/CrystalFerrai/UeBlueprintDumper/blob/main/UeBlueprintDumper/BlueprintDumper.cs#L352 + // nothing else is from UeBlueprintDumper + + public static string GetUnknownFieldType(object field) + { + string typeName = field.GetType().Name; + int suffixIndex = typeName.IndexOf("Property", StringComparison.Ordinal); + if (suffixIndex < 0) + return typeName; + return typeName.Substring(1, suffixIndex - 1); + } + public static string GetUnknownFieldType(FField field) + { + string typeName = field.GetType().Name; + int suffixIndex = typeName.IndexOf("Property", StringComparison.Ordinal); + if (suffixIndex < 0) + return typeName; + return typeName.Substring(1, suffixIndex - 1); + } + public static string GetPropertyType(object? property) + { + if (property is null) + return "None"; + + return property switch + { + FIntProperty => "int", + FInt8Property => "int8", + FInt16Property => "int16", + FInt64Property => "int64", + FUInt16Property => "uint16", + FUInt32Property => "uint32", + FUInt64Property => "uint64", + FBoolProperty or Boolean => "bool", + FStrProperty => "FString", + FFloatProperty or Single => "float", + FDoubleProperty or Double => "double", + FObjectProperty objct => property switch + { + FClassProperty clss => $"{clss.MetaClass?.Name ?? "UNKNOWN"}", + FSoftClassProperty softClass => $"{softClass.MetaClass?.Name ?? "UNKNOWN"}", + _ => objct.PropertyClass?.Name ?? "UNKNOWN" + }, + FPackageIndex pkg => pkg?.ResolvedObject?.Class?.Name.ToString() ?? "Package", + FName fme => fme.PlainText.Contains("::") ? fme.PlainText.Split("::")[0] : fme.PlainText ?? "FName", + FEnumProperty enm => enm.Enum?.Name.ToString() ?? "Enum", + FByteProperty bt => bt.Enum.ResolvedObject?.Name.Text ?? "Byte", + FInterfaceProperty intrfc => $"{intrfc.InterfaceClass.Name} interface", + FStructProperty strct => strct.Struct.ResolvedObject?.Name.Text ?? "Struct", + FFieldPathProperty fieldPath => $"{fieldPath.PropertyClass.Text} field path", + FDelegateProperty dlgt => $"{dlgt.SignatureFunction?.Name ?? "UNKNOWN"} (Delegate)", + FMulticastDelegateProperty mdlgt => $"{mdlgt.SignatureFunction?.Name ?? "UNKNOWN"} (MulticastDelegateProperty)", + FMulticastInlineDelegateProperty midlgt => $"{midlgt.SignatureFunction?.Name ?? "UNKNOWN"} (MulticastInlineDelegateProperty)", + _ => GetUnknownFieldType(property) + }; + } + public static string GetPropertyType(FProperty? property) + { + if (property is null) + return "None"; + + return property switch + { + FIntProperty => "int", + FBoolProperty => "bool", + FStrProperty => "FString", + FFloatProperty => "float", + FDoubleProperty => "double", + FObjectProperty objct => property switch + { + FClassProperty clss => $"{clss.MetaClass?.Name ?? "UNKNOWN"} Class", + FSoftClassProperty softClass => $"{softClass.MetaClass?.Name ?? "UNKNOWN"} Class (soft)", + _ => objct.PropertyClass?.Name ?? "UNKNOWN" + }, + FEnumProperty enm => enm.Enum?.Name.ToString() ?? "Enum", + FSetProperty set => $"TSet<{GetPrefix(set.ElementProp.GetType().Name)}{GetPropertyType(set.ElementProp)}{(set.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || property.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || set.PropertyFlags.HasFlag(EPropertyFlags.ContainsInstancedReference) ? "*" : string.Empty)}>", + FByteProperty bt => bt.Enum.ResolvedObject?.Name.Text ?? "Byte", + FInterfaceProperty intrfc => $"{intrfc.InterfaceClass.Name} interface", + FStructProperty strct => strct.Struct.ResolvedObject?.Name.Text ?? "Struct", + FFieldPathProperty fieldPath => $"{fieldPath.PropertyClass.Text} field path", + FDelegateProperty dlgt => $"{dlgt.SignatureFunction?.Name ?? "UNKNOWN"} (Delegate)", + FMapProperty map => $"TMap<{GetPrefix(map.ValueProp.GetType().Name)}{GetPropertyType(map.KeyProp)}, {GetPrefix(map.ValueProp.GetType().Name)}{GetPropertyType(map.ValueProp)}{(map.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || property.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || map.PropertyFlags.HasFlag(EPropertyFlags.ContainsInstancedReference) ? "*" : string.Empty)}>", + FMulticastDelegateProperty mdlgt => $"{mdlgt.SignatureFunction?.Name ?? "UNKNOWN"} (MulticastDelegateProperty)", + FMulticastInlineDelegateProperty midlgt => $"{midlgt.SignatureFunction?.Name ?? "UNKNOWN"} (MulticastInlineDelegateProperty)", + FArrayProperty array => $"TArray<{GetPrefix(array.Inner.GetType().Name)}{GetPropertyType(array.Inner)}{(array.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || property.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || array.PropertyFlags.HasFlag(EPropertyFlags.ContainsInstancedReference) || GetPropertyProperty(array.Inner.GetType().Name) ? "*" : string.Empty)}>", + _ => GetUnknownFieldType(property) + }; + } + public static bool GetPropertyProperty(object? property) + { + if (property is null) + return false; + + return property switch + { + FObjectProperty objct => true, + _ => false + }; + } + public static bool GetPropertyProperty(FProperty? property) + { + if (property is null) + return false; + + return property switch + { + FObjectProperty objct => true, + _ => false + }; + } + public static string FormatStructFallback(FStructFallback fallback) + { + if (fallback.Properties.Count == 0) + return "[]"; + + var tags = fallback.Properties.Select(tag => + { + string tagDataFormatted; + + if (tag.Tag is TextProperty text) + { + tagDataFormatted = $"\"{text.Value.Text}\""; + } + else if (tag.Tag is NameProperty name) + { + tagDataFormatted = $"\"{name.Value.Text}\""; + } + else if (tag.Tag is ObjectProperty obj) + { + tagDataFormatted = $"\"{obj.Value}\""; + } + else if (tag.Tag.GenericValue is FScriptStruct innerStruct && innerStruct.StructType is FStructFallback nestedFallback) + { + if (nestedFallback.Properties.Count > 0) + { + tagDataFormatted = "{ " + string.Join(", ", + nestedFallback.Properties.Select(nested => + { + string nestedVal; + if (nested.Tag is TextProperty textProp) + nestedVal = $"\"{textProp.Value.Text}\""; + else if (nested.Tag is NameProperty nameProp) + nestedVal = $"\"{nameProp.Value.Text}\""; + else + nestedVal = $"\"{nested.Tag.GenericValue}\""; + + return $"\"{nested.Name}\": {nestedVal}"; + })) + " }"; + } + else + { + tagDataFormatted = "{}"; + } + } + else + { + tagDataFormatted = tag.Tag.GenericValue != null ? $"\"{tag.Tag.GenericValue}\"" : "{}"; + } + + return $"\t\t{{ \"{tag.Name}\": {tagDataFormatted} }}"; + }); + + return "[\n" + string.Join(",\n", tags) + "\n\t]"; + } + public static string FormatGameplayTagContainer(FGameplayTagContainer container) + { + var tags = container.GameplayTags.ToList(); + return tags.Count switch + { + 0 => "[]", + 1 => $"\"{tags[0].TagName}\"", + _ => "[\n" + string.Join(",\n", tags.Select(tag => $"\t\t\"{tag.TagName}\"")) + "\n\t]" + }; + } + public static string FormatStructType(object structType) + { + switch (structType) + { + case FVector vector: + return $"FVector({vector.X}, {vector.Y}, {vector.Z})"; + case FVector2D vector2D: + return $"FVector2D({vector2D.X}, {vector2D.Y})"; + case FRotator rotator: + return $"FRotator({rotator.Pitch}, {rotator.Yaw}, {rotator.Roll})"; + case FQuat quat: + return $"FQuat({quat.X}, {quat.Y}, {quat.Z}, {quat.W})"; + case FGuid guid: + return $"FGuid({guid.A}, {guid.B}, {guid.C}, {guid.D})"; + case FColor color: + return $"FColor({color.R}, {color.G}, {color.B}, {color.A})"; + case FLinearColor linearColor: + return $"FLinearColor({linearColor.R}, {linearColor.G}, {linearColor.B}, {linearColor.A})"; + case FSoftObjectPath path: + return $"FSoftObjectPath({path.AssetPathName})"; + case FUniqueNetIdRepl netId: + return $"FUniqueNetIdRepl({netId.UniqueNetId})"; + case FNavAgentSelector agent: + return $"FNavAgentSelector({agent.PackedBits})"; + case FBox box: + return $"FBox(FVector({box.Max.X}, {box.Max.Y}, {box.Max.Z}), FVector({box.Min.X}, {box.Min.Y}, {box.Min.Z}))"; + case FBox2D box2D: + return $"FBox2D(FVector2D({box2D.Max.X}, {box2D.Max.Y}), FVector2D({box2D.Min.X}, {box2D.Min.Y}))"; + case TIntVector3 intVec: + return $"FVector({intVec.X}, {intVec.Y}, {intVec.Z})"; + case TIntVector3 floatVec: + return $"FVector({floatVec.X}, {floatVec.Y}, {floatVec.Z})"; + case TIntVector2 floatVec2: + return $"FVector2D({floatVec2.X}, {floatVec2.Y})"; + case FDateTime dateTime: + return $"FDateTime({dateTime})"; + case FStructFallback fallback: + return FormatStructFallback(fallback); + case FGameplayTagContainer tagContainer: + return FormatGameplayTagContainer(tagContainer); + default: + return structType?.ToString() ?? "Issue here"; + } + } + + private static string ProcessTextProperty(FKismetPropertyPointer property, bool temp) + { + if (property.New is null) + { + return property.Old?.Name ?? string.Empty; + } + return string.Join('.', property.New.Path.Select(n => n.Text)).Replace(" ", ""); + } + public static void ProcessExpression(EExprToken token, KismetExpression expression, StringBuilder outputBuilder, List jumpCodeOffsets, bool isParameter = false) + { + if (jumpCodeOffsets.Contains(expression.StatementIndex)) + { + outputBuilder.Append("\t\tLabel_" + expression.StatementIndex + ":\n"); + } + switch (token) + { + case EExprToken.EX_LetValueOnPersistentFrame: + { + EX_LetValueOnPersistentFrame op = (EX_LetValueOnPersistentFrame) expression; + EX_VariableBase opp = (EX_VariableBase) op.AssignmentExpression; + var destination = ProcessTextProperty(op.DestinationProperty, false); + var variable = ProcessTextProperty(opp.Variable, false); + + if (!isParameter) + { + outputBuilder.Append($"\t\t{(destination.Contains("K2Node_") ? $"UberGraphFrame->{destination}" : destination)} = {variable};\n\n"); // hardcoded but works + } + else + { + outputBuilder.Append($"\t\t{(destination.Contains("K2Node_") ? $"UberGraphFrame->{destination}" : destination)} = {variable}"); + } + break; + } + case EExprToken.EX_LocalFinalFunction: + { + EX_FinalFunction op = (EX_FinalFunction) expression; + KismetExpression[] opp = op.Parameters; + if (isParameter) + { + outputBuilder.Append($"{op.StackNode.Name.Replace(" ", "")}("); + } + else if (opp.Length < 1) + { + outputBuilder.Append($"\t\t{op?.StackNode?.Name.Replace(" ", "")}("); + } + else + { + outputBuilder.Append($"\t\t{GetPrefix(op?.StackNode?.ResolvedObject?.Outer?.GetType()?.Name ?? string.Empty)}{op?.StackNode?.Name.Replace(" ", "")}("); + } + + for (int i = 0; i < opp.Length; i++) + { + if (opp.Length > 4) + outputBuilder.Append("\n\t\t"); + ProcessExpression(opp[i].Token, opp[i], outputBuilder, jumpCodeOffsets, true); + if (i < opp.Length - 1) + { + outputBuilder.Append(", "); + } + } + outputBuilder.Append(isParameter ? ")" : ");\n"); + break; + } + case EExprToken.EX_FinalFunction: + { + EX_FinalFunction op = (EX_FinalFunction) expression; + KismetExpression[] opp = op.Parameters; + if (isParameter) + { + outputBuilder.Append($"{op.StackNode.Name.Replace(" ", "")}("); + } + else if (opp.Length < 1) + { + outputBuilder.Append($"\t\t{op?.StackNode?.Name.Replace(" ", "")}("); + } + else + { + outputBuilder.Append($"\t\t{op?.StackNode?.Name.Replace(" ", "")}(");//{GetPrefix(op?.StackNode?.ResolvedObject?.Outer?.GetType()?.Name)} + } + + for (int i = 0; i < opp.Length; i++) + { + if (opp.Length > 4) + outputBuilder.Append("\n\t\t"); + ProcessExpression(opp[i].Token, opp[i], outputBuilder, jumpCodeOffsets, true); + if (i < opp.Length - 1) + { + outputBuilder.Append(", "); + } + } + outputBuilder.Append(isParameter ? ")" : ");\n\n"); + break; + } + case EExprToken.EX_CallMath: + { + 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}("); + + for (int i = 0; i < opp.Length; i++) + { + if (opp.Length > 4) + outputBuilder.Append("\n\t\t\t"); + ProcessExpression(opp[i].Token, opp[i], outputBuilder, jumpCodeOffsets, true); + if (i < opp.Length - 1) + { + outputBuilder.Append(", "); + } + } + outputBuilder.Append(isParameter ? ")" : ");\n\n"); + break; + } + case EExprToken.EX_LocalVirtualFunction: + case EExprToken.EX_VirtualFunction: + { + EX_VirtualFunction op = (EX_VirtualFunction) expression; + KismetExpression[] opp = op.Parameters; + if (isParameter) + { + outputBuilder.Append($"{op.VirtualFunctionName.PlainText.Replace(" ", "")}("); + } + else + { + outputBuilder.Append($"\t\t{op.VirtualFunctionName.PlainText.Replace(" ", "")}("); + } + for (int i = 0; i < opp.Length; i++) + { + if (opp.Length > 4) + outputBuilder.Append("\n\t\t"); + + ProcessExpression(opp[i].Token, opp[i], outputBuilder, jumpCodeOffsets, true); + if (i < opp.Length - 1) + { + outputBuilder.Append(", "); + } + } + outputBuilder.Append(isParameter ? ")" : ");\n\n"); + break; + } + case EExprToken.EX_ComputedJump: + { + EX_ComputedJump op = (EX_ComputedJump) expression; + if (op.CodeOffsetExpression is EX_VariableBase opp) + { + outputBuilder.AppendLine($"\t\tgoto {ProcessTextProperty(opp.Variable, false)};\n"); + } + else if (op.CodeOffsetExpression is EX_CallMath oppMath) + { + ProcessExpression(oppMath.Token, oppMath, outputBuilder, jumpCodeOffsets, true); + } + else + {} + break; + } + case EExprToken.EX_PopExecutionFlowIfNot: + { + EX_PopExecutionFlowIfNot op = (EX_PopExecutionFlowIfNot) expression; + outputBuilder.Append("\t\tif (!"); + ProcessExpression(op.BooleanExpression.Token, op.BooleanExpression, outputBuilder, jumpCodeOffsets, true); + outputBuilder.Append(") \r\n"); + outputBuilder.Append($"\t\t FlowStack.Pop();\n\n"); + break; + } + case EExprToken.EX_Cast: + { + EX_Cast op = (EX_Cast) expression;// support CST_ObjectToInterface when I have an example of how it works + + if (ECastToken.CST_ObjectToBool == op.ConversionType || ECastToken.CST_InterfaceToBool == op.ConversionType) + { + outputBuilder.Append("(bool)"); + } + if (ECastToken.CST_DoubleToFloat == op.ConversionType) + { + outputBuilder.Append("(float)"); + } + if (ECastToken.CST_FloatToDouble == op.ConversionType) + { + outputBuilder.Append("(double)"); + } + ProcessExpression(op.Target.Token, op.Target, outputBuilder, jumpCodeOffsets); + break; + } + case EExprToken.EX_InterfaceContext: + { + EX_InterfaceContext op = (EX_InterfaceContext) expression; + ProcessExpression(op.InterfaceValue.Token, op.InterfaceValue, outputBuilder, jumpCodeOffsets); + break; + } + case EExprToken.EX_ArrayConst: + { + EX_ArrayConst op = (EX_ArrayConst) expression; + outputBuilder.Append("TArray {"); + foreach (KismetExpression element in op.Elements) + { + outputBuilder.Append(' '); + ProcessExpression(element.Token, element, outputBuilder, jumpCodeOffsets); + } + outputBuilder.Append(op.Elements.Length < 1 ? " " : ' '); + + outputBuilder.Append("}"); + break; + } + case EExprToken.EX_SetArray: + { + EX_SetArray op = (EX_SetArray) expression; + outputBuilder.Append("\t\t"); + ProcessExpression(op.AssigningProperty.Token, op.AssigningProperty, outputBuilder, jumpCodeOffsets); + outputBuilder.Append(" = "); + outputBuilder.Append("TArray {"); + for (int i = 0; i < op.Elements.Length; i++) + { + KismetExpression element = op.Elements[i]; + outputBuilder.Append(' '); + ProcessExpression(element.Token, element, outputBuilder, jumpCodeOffsets); + + outputBuilder.Append(i < op.Elements.Length - 1 ? "," : ""); + } + + outputBuilder.Append(op.Elements.Length < 1 ? " " : ' '); + + outputBuilder.Append("};\n\n"); + break; + } + case EExprToken.EX_SetSet: + { + EX_SetSet op = (EX_SetSet) expression; + outputBuilder.Append("\t\t"); + ProcessExpression(op.SetProperty.Token, op.SetProperty, outputBuilder, jumpCodeOffsets); + outputBuilder.Append(" = "); + outputBuilder.Append("TArray {"); + for (int i = 0; i < op.Elements.Length; i++) + { + KismetExpression element = op.Elements[i]; + outputBuilder.Append(' '); + ProcessExpression(element.Token, element, outputBuilder, jumpCodeOffsets); + + outputBuilder.Append(i < op.Elements.Length - 1 ? "," : ""); + } + + outputBuilder.Append(op.Elements.Length < 1 ? " " : ' '); + + outputBuilder.Append("};\n\n"); + break; + } + case EExprToken.EX_SetConst: + { + EX_SetConst op = (EX_SetConst) expression; + outputBuilder.Append("TArray {"); + for (int i = 0; i < op.Elements.Length; i++) + { + KismetExpression element = op.Elements[i]; + outputBuilder.Append(' '); + ProcessExpression(element.Token, element, outputBuilder, jumpCodeOffsets, true); + + outputBuilder.Append(i < op.Elements.Length - 1 ? "," : ""); + } + + outputBuilder.Append(op.Elements.Length < 1 ? " " : ' '); + + outputBuilder.Append("};\n\n"); + break; + } + case EExprToken.EX_SetMap: + { + EX_SetMap op = (EX_SetMap) expression; + outputBuilder.Append("\t\t"); + ProcessExpression(op.MapProperty.Token, op.MapProperty, outputBuilder, jumpCodeOffsets); + outputBuilder.Append(" = "); + outputBuilder.Append("TMap {"); + for (int i = 0; i < op.Elements.Length; i++) + { + var element = op.Elements[i]; + outputBuilder.Append(' '); + ProcessExpression(element.Token, element, outputBuilder, jumpCodeOffsets);// sometimes the start of an array is a byte not a variable + + if (i < op.Elements.Length - 1) + { + outputBuilder.Append(element.Token == EExprToken.EX_InstanceVariable ? ": " : ", "); + } + else + { + outputBuilder.Append(' '); + } + } + + if (op.Elements.Length < 1) + outputBuilder.Append(" "); + outputBuilder.Append("}\n"); + break; + } + case EExprToken.EX_MapConst: + { + EX_MapConst op = (EX_MapConst) expression; + outputBuilder.Append("TMap {"); + for (int i = 0; i < op.Elements.Length; i++) + { + var element = op.Elements[i]; + outputBuilder.Append(' '); + ProcessExpression(element.Token, element, outputBuilder, jumpCodeOffsets, true);// sometimes the start of an array is a byte not a variable + + if (i < op.Elements.Length - 1) + { + outputBuilder.Append(element.Token == EExprToken.EX_InstanceVariable ? ": " : ", "); + } + else + { + outputBuilder.Append(' '); + } + } + + if (op.Elements.Length < 1) + outputBuilder.Append(" "); + outputBuilder.Append("}\n"); + break; + } + case EExprToken.EX_SwitchValue: + { + EX_SwitchValue op = (EX_SwitchValue) expression; + + bool useTernary = op.Cases.Length <= 2 + && op.Cases.All(c => c.CaseIndexValueTerm.Token == EExprToken.EX_True || c.CaseIndexValueTerm.Token == EExprToken.EX_False); + + if (useTernary) + { + ProcessExpression(op.IndexTerm.Token, op.IndexTerm, outputBuilder, jumpCodeOffsets); + outputBuilder.Append(" ? "); + + bool isFirst = true; + foreach (var caseItem in op.Cases.Where(c => c.CaseIndexValueTerm.Token == EExprToken.EX_True)) + { + if (!isFirst) + outputBuilder.Append(" : "); + + ProcessExpression(caseItem.CaseTerm.Token, caseItem.CaseTerm, outputBuilder, jumpCodeOffsets, true); + isFirst = false; + } + + foreach (var caseItem in op.Cases.Where(c => c.CaseIndexValueTerm.Token == EExprToken.EX_False)) + { + if (!isFirst) + outputBuilder.Append(" : "); + + ProcessExpression(caseItem.CaseTerm.Token, caseItem.CaseTerm, outputBuilder, jumpCodeOffsets, true); + } + } + else + { + outputBuilder.Append("switch ("); + ProcessExpression(op.IndexTerm.Token, op.IndexTerm, outputBuilder, jumpCodeOffsets); + outputBuilder.Append(")\n"); + outputBuilder.Append("\t\t{\n"); + + foreach (var caseItem in op.Cases) + { + if (caseItem.CaseIndexValueTerm.Token == EExprToken.EX_IntConst) + { + int caseValue = ((EX_IntConst) caseItem.CaseIndexValueTerm).Value; + outputBuilder.Append($"\t\t\tcase {caseValue}:\n"); + } + else + { + outputBuilder.Append("\t\t\tcase "); + ProcessExpression(caseItem.CaseIndexValueTerm.Token, caseItem.CaseIndexValueTerm, outputBuilder, jumpCodeOffsets); + outputBuilder.Append(":\n"); + } + + outputBuilder.Append("\t\t\t{\n"); + outputBuilder.Append("\t\t\t "); + ProcessExpression(caseItem.CaseTerm.Token, caseItem.CaseTerm, outputBuilder, jumpCodeOffsets); + outputBuilder.Append(";\n"); + outputBuilder.Append("\t\t\t break;\n"); + outputBuilder.Append("\t\t\t}\n"); + } + + outputBuilder.Append("\t\t\tdefault:\n"); + outputBuilder.Append("\t\t\t{\n"); + outputBuilder.Append("\t\t\t "); + ProcessExpression(op.DefaultTerm.Token, op.DefaultTerm, outputBuilder, jumpCodeOffsets); + outputBuilder.Append("\n\t\t\t}\n"); + + outputBuilder.Append("\t\t}"); + } + break; + } + case EExprToken.EX_ArrayGetByRef: // I assume get array with index + { + EX_ArrayGetByRef op = (EX_ArrayGetByRef) expression; // FortniteGame/Plugins/GameFeatures/FM/PilgrimCore/Content/Player/Components/BP_PilgrimPlayerControllerComponent.uasset + ProcessExpression(op.ArrayVariable.Token, op.ArrayVariable, outputBuilder, jumpCodeOffsets, true); + outputBuilder.Append("["); + ProcessExpression(op.ArrayIndex.Token, op.ArrayIndex, outputBuilder, jumpCodeOffsets); + outputBuilder.Append("]"); + break; + } + case EExprToken.EX_MetaCast: + case EExprToken.EX_DynamicCast: + case EExprToken.EX_ObjToInterfaceCast: + case EExprToken.EX_CrossInterfaceCast: + case EExprToken.EX_InterfaceToObjCast: + { + EX_CastBase op = (EX_CastBase) expression; + outputBuilder.Append($"Cast(");// m? + ProcessExpression(op.Target.Token, op.Target, outputBuilder, jumpCodeOffsets, true); + outputBuilder.Append(")"); + break; + } + case EExprToken.EX_StructConst: + { + EX_StructConst op = (EX_StructConst) expression; + outputBuilder.Append($"{GetPrefix(op.Struct.GetType().Name)}{op.Struct.Name}"); + outputBuilder.Append($"("); + for (int i = 0; i < op.Properties.Length; i++) + { + var property = op.Properties[i]; + ProcessExpression(property.Token, property, outputBuilder, jumpCodeOffsets); + if (i < op.Properties.Length - 1 && property.Token != EExprToken.EX_ArrayConst) + outputBuilder.Append(", "); + } + outputBuilder.Append($")"); + break; + } + case EExprToken.EX_ObjectConst: + { + EX_ObjectConst op = (EX_ObjectConst) expression; + outputBuilder.Append(!isParameter ? "\t\tFindObject<" : outputBuilder.ToString().EndsWith("\n") ? "\t\tFindObject<" : "FindObject<"); // please don't complain, i know this is bad but i MUST do it. + string classString = op?.Value?.ResolvedObject?.Class?.ToString()?.Replace("'", ""); + + if (classString?.Contains(".") == true) + { + + outputBuilder.Append(GetPrefix(op?.Value?.ResolvedObject?.Class?.GetType().Name) + classString.Split(".")[1]); + } + else + { + outputBuilder.Append(GetPrefix(op?.Value?.ResolvedObject?.Class?.GetType().Name) + classString); + } + outputBuilder.Append(">(\""); + var resolvedObject = op?.Value?.ResolvedObject; + var outerString = resolvedObject?.Outer?.ToString()?.Replace("'", "") ?? "UNKNOWN"; + var outerClassString = resolvedObject?.Class?.ToString()?.Replace("'", "") ?? "UNKNOWN"; + var name = op?.Value?.Name ?? string.Empty; + + outputBuilder.Append(outerString.Replace(outerClassString, "") + "." + name); + + if (isParameter) + { + outputBuilder.Append("\")"); + } + else + { + outputBuilder.Append("\")"); + } + break; + } + case EExprToken.EX_BindDelegate: + { + EX_BindDelegate op = (EX_BindDelegate) expression; + outputBuilder.Append("\t\t"); + ProcessExpression(op.Delegate.Token, op.Delegate, outputBuilder, jumpCodeOffsets); + outputBuilder.Append($".BindUFunction("); + ProcessExpression(op.ObjectTerm.Token, op.ObjectTerm, outputBuilder, jumpCodeOffsets); + outputBuilder.Append($", \"{op.FunctionName}\""); + outputBuilder.Append($");\n\n"); + break; + } + // all the delegate functions suck + case EExprToken.EX_AddMulticastDelegate: + { + EX_AddMulticastDelegate op = (EX_AddMulticastDelegate) expression; + if (op.Delegate.Token == EExprToken.EX_LocalVariable || op.Delegate.Token == EExprToken.EX_InstanceVariable) + { + outputBuilder.Append("\t\t"); + ProcessExpression(op.Delegate.Token, op.Delegate, outputBuilder, jumpCodeOffsets, true); + outputBuilder.Append(".AddDelegate("); + ProcessExpression(op.DelegateToAdd.Token, op.DelegateToAdd, outputBuilder, jumpCodeOffsets); + outputBuilder.Append($");\n\n"); + } + else if (op.Delegate.Token != EExprToken.EX_Context) + {} + else + { + //EX_Context opp = (EX_Context) op.Delegate; + outputBuilder.Append("\t\t"); + ProcessExpression(op.Delegate.Token, op.Delegate, outputBuilder, jumpCodeOffsets, true); + //outputBuilder.Append("->"); + //ProcessExpression(opp.ContextExpression.Token, opp.ContextExpression, outputBuilder, jumpCodeOffsets); + outputBuilder.Append(".AddDelegate("); + ProcessExpression(op.DelegateToAdd.Token, op.DelegateToAdd, outputBuilder, jumpCodeOffsets); + outputBuilder.Append($");\n\n"); + } + break; + } + case EExprToken.EX_RemoveMulticastDelegate: // everything here has been guessed not compared to actual UE but does work fine and displays all information + { + EX_RemoveMulticastDelegate op = (EX_RemoveMulticastDelegate) expression; + if (op.Delegate.Token == EExprToken.EX_LocalVariable || op.Delegate.Token == EExprToken.EX_InstanceVariable) + { + outputBuilder.Append("\t\t"); + ProcessExpression(op.Delegate.Token, op.Delegate, outputBuilder, jumpCodeOffsets, true); + outputBuilder.Append(".RemoveDelegate("); + ProcessExpression(op.DelegateToAdd.Token, op.DelegateToAdd, outputBuilder, jumpCodeOffsets); + outputBuilder.Append($");\n\n"); + } + else if (op.Delegate.Token != EExprToken.EX_Context) + {} + else + { + EX_Context opp = (EX_Context) op.Delegate; + outputBuilder.Append("\t\t"); + ProcessExpression(op.Delegate.Token, op.Delegate, outputBuilder, jumpCodeOffsets, true); + outputBuilder.Append("->"); + ProcessExpression(opp.ContextExpression.Token, opp.ContextExpression, outputBuilder, jumpCodeOffsets); + outputBuilder.Append(".RemoveDelegate("); + ProcessExpression(op.DelegateToAdd.Token, op.DelegateToAdd, outputBuilder, jumpCodeOffsets); + outputBuilder.Append($");\n\n"); + } + break; + } + case EExprToken.EX_ClearMulticastDelegate: // this also + { + EX_ClearMulticastDelegate op = (EX_ClearMulticastDelegate) expression; + outputBuilder.Append("\t\t"); + ProcessExpression(op.DelegateToClear.Token, op.DelegateToClear, outputBuilder, jumpCodeOffsets, true); + outputBuilder.Append(".Clear();\n\n"); + break; + } + case EExprToken.EX_CallMulticastDelegate: // this also + { + EX_CallMulticastDelegate op = (EX_CallMulticastDelegate) expression; + KismetExpression[] opp = op.Parameters; + if (op.Delegate.Token == EExprToken.EX_LocalVariable || op.Delegate.Token == EExprToken.EX_InstanceVariable) + { + outputBuilder.Append("\t\t"); + ProcessExpression(op.Delegate.Token, op.Delegate, outputBuilder, jumpCodeOffsets, true); + outputBuilder.Append(".Call("); + for (int i = 0; i < opp.Length; i++) + { + if (opp.Length > 4) + outputBuilder.Append("\n\t\t"); + ProcessExpression(opp[i].Token, opp[i], outputBuilder, jumpCodeOffsets, true); + if (i < opp.Length - 1) + { + outputBuilder.Append(", "); + } + } + outputBuilder.Append($");\n\n"); + } + else if (op.Delegate.Token != EExprToken.EX_Context) + {} + else + { + outputBuilder.Append("\t\t"); + ProcessExpression(op.Delegate.Token, op.Delegate, outputBuilder, jumpCodeOffsets, true); + outputBuilder.Append(".Call("); + for (int i = 0; i < opp.Length; i++) + { + if (opp.Length > 4) + outputBuilder.Append("\n\t\t"); + ProcessExpression(opp[i].Token, opp[i], outputBuilder, jumpCodeOffsets, true); + if (i < opp.Length - 1) + { + outputBuilder.Append(", "); + } + } + outputBuilder.Append($");\n\n"); + } + break; + } + case EExprToken.EX_ClassContext: + case EExprToken.EX_Context: + { + EX_Context op = (EX_Context) expression; + outputBuilder.Append(outputBuilder.ToString().EndsWith("\n") ? "\t\t" : ""); + ProcessExpression(op.ObjectExpression.Token, op.ObjectExpression, outputBuilder, jumpCodeOffsets, true); + + outputBuilder.Append("->"); + ProcessExpression(op.ContextExpression.Token, op.ContextExpression, outputBuilder, jumpCodeOffsets, true); + if (!isParameter) + { + outputBuilder.Append(";\n\n"); + } + break; + } + case EExprToken.EX_Context_FailSilent: + { + EX_Context op = (EX_Context) expression; + outputBuilder.Append("\t\t"); + ProcessExpression(op.ObjectExpression.Token, op.ObjectExpression, outputBuilder, jumpCodeOffsets, true); + if (!isParameter) + { + outputBuilder.Append("->"); + ProcessExpression(op.ContextExpression.Token, op.ContextExpression, outputBuilder, jumpCodeOffsets, true); + outputBuilder.Append($";\n\n"); + } + break; + } + case EExprToken.EX_Let: + { + EX_Let op = (EX_Let) expression; + if (!isParameter) + { + outputBuilder.Append("\t\t"); + } + ProcessExpression(op.Variable.Token, op.Variable, outputBuilder, jumpCodeOffsets, true); + outputBuilder.Append(" = "); + ProcessExpression(op.Assignment.Token, op.Assignment, outputBuilder, jumpCodeOffsets, true); + if (!isParameter) + { + outputBuilder.Append(";\n\n"); + } + break; + } + case EExprToken.EX_LetObj: + case EExprToken.EX_LetWeakObjPtr: + case EExprToken.EX_LetBool: + case EExprToken.EX_LetDelegate: + case EExprToken.EX_LetMulticastDelegate: + { + EX_LetBase op = (EX_LetBase) expression; + if (!isParameter) + { + outputBuilder.Append("\t\t"); + } + ProcessExpression(op.Variable.Token, op.Variable, outputBuilder, jumpCodeOffsets, true); + outputBuilder.Append(" = "); + ProcessExpression(op.Assignment.Token, op.Assignment, outputBuilder, jumpCodeOffsets, true); + if (!isParameter || op.Assignment.Token == EExprToken.EX_LocalFinalFunction || op.Assignment.Token == EExprToken.EX_FinalFunction || op.Assignment.Token == EExprToken.EX_CallMath) + { + outputBuilder.Append($";\n\n"); + } + else + { + outputBuilder.Append($";"); + } + break; + } + case EExprToken.EX_JumpIfNot: + { + EX_JumpIfNot op = (EX_JumpIfNot) expression; + outputBuilder.Append("\t\tif (!"); + ProcessExpression(op.BooleanExpression.Token, op.BooleanExpression, outputBuilder, jumpCodeOffsets, true); + outputBuilder.Append(") \r\n"); + outputBuilder.Append("\t\t goto Label_"); + outputBuilder.Append(op.CodeOffset); + outputBuilder.Append(";\n\n"); + break; + } + case EExprToken.EX_Jump: + { + EX_Jump op = (EX_Jump) expression; + outputBuilder.Append($"\t\tgoto Label_{op.CodeOffset};\n\n"); + break; + } + // Static expressions + + case EExprToken.EX_TextConst: + { + EX_TextConst op = (EX_TextConst) expression; + + if (op.Value is FScriptText scriptText) + { + if (scriptText.SourceString == null) + { + outputBuilder.Append("nullptr"); + } + else + ProcessExpression(scriptText.SourceString.Token, scriptText.SourceString, outputBuilder, jumpCodeOffsets, true); + } + else + { + outputBuilder.Append(op.Value); + } + } + break; + case EExprToken.EX_StructMemberContext: + { + EX_StructMemberContext op = (EX_StructMemberContext) expression; + ProcessExpression(op.StructExpression.Token, op.StructExpression, outputBuilder, jumpCodeOffsets); + outputBuilder.Append('.'); + outputBuilder.Append(ProcessTextProperty(op.Property, false)); + break; + } + case EExprToken.EX_Return: + { + EX_Return op = (EX_Return) expression; + bool check = op.ReturnExpression.Token == EExprToken.EX_Nothing; + outputBuilder.Append($"\t\treturn"); + if (!check) + outputBuilder.Append(' '); + ProcessExpression(op.ReturnExpression.Token, op.ReturnExpression, outputBuilder, jumpCodeOffsets, true); + outputBuilder.AppendLine(";\n\n"); + break; + } + case EExprToken.EX_RotationConst: + { + EX_RotationConst op = (EX_RotationConst) expression; + FRotator value = op.Value; + outputBuilder.Append($"FRotator({value.Pitch}, {value.Yaw}, {value.Roll})"); + break; + } + case EExprToken.EX_VectorConst: + { + EX_VectorConst op = (EX_VectorConst) expression; + FVector value = op.Value; + outputBuilder.Append($"FVector({value.X}, {value.Y}, {value.Z})"); + break; + } + case EExprToken.EX_Vector3fConst: + { + EX_Vector3fConst op = (EX_Vector3fConst) expression; + FVector value = op.Value; + outputBuilder.Append($"FVector3f({value.X}, {value.Y}, {value.Z})"); + break; + } + case EExprToken.EX_TransformConst: + { + EX_TransformConst op = (EX_TransformConst) expression; + FTransform value = op.Value; + outputBuilder.Append($"FTransform(FQuat({value.Rotation.X}, {value.Rotation.Y}, {value.Rotation.Z}, {value.Rotation.W}), FVector({value.Translation.X}, {value.Translation.Y}, {value.Translation.Z}), FVector({value.Scale3D.X}, {value.Scale3D.Y}, {value.Scale3D.Z}))"); + break; + } + + + case EExprToken.EX_LocalVariable: + case EExprToken.EX_DefaultVariable: + case EExprToken.EX_LocalOutVariable: + case EExprToken.EX_ClassSparseDataVariable: + outputBuilder.Append(ProcessTextProperty(((EX_VariableBase) expression).Variable, false)); + break; + case EExprToken.EX_InstanceVariable: + outputBuilder.Append(ProcessTextProperty(((EX_VariableBase) expression).Variable, true)); + break; + + case EExprToken.EX_ByteConst: + case EExprToken.EX_IntConstByte: + outputBuilder.Append($"0x{((KismetExpression) expression).Value.ToString("X")}"); + break; + case EExprToken.EX_SoftObjectConst: + ProcessExpression(((EX_SoftObjectConst) expression).Value.Token, ((EX_SoftObjectConst) expression).Value, outputBuilder, jumpCodeOffsets); + break; + case EExprToken.EX_DoubleConst: + { + double value = ((EX_DoubleConst) expression).Value; + outputBuilder.Append(Math.Abs(value - Math.Floor(value)) < 1e-10 ? (int) value : value.ToString("R")); + break; + } + case EExprToken.EX_NameConst: + outputBuilder.Append($"\"{((EX_NameConst) expression).Value}\""); + break; + case EExprToken.EX_IntConst: + outputBuilder.Append(((EX_IntConst) expression).Value.ToString()); + break; + case EExprToken.EX_PropertyConst: + outputBuilder.Append(ProcessTextProperty(((EX_PropertyConst) expression).Property, false)); + break; + case EExprToken.EX_StringConst: + outputBuilder.Append($"\"{((EX_StringConst) expression).Value}\""); + break; + case EExprToken.EX_FieldPathConst: + ProcessExpression(((EX_FieldPathConst) expression).Value.Token, ((EX_FieldPathConst) expression).Value, outputBuilder, jumpCodeOffsets); + break; + case EExprToken.EX_Int64Const: + outputBuilder.Append(((EX_Int64Const) expression).Value.ToString()); + break; + case EExprToken.EX_UInt64Const: + outputBuilder.Append(((EX_UInt64Const) expression).Value.ToString()); + break; + case EExprToken.EX_SkipOffsetConst: + outputBuilder.Append(((EX_SkipOffsetConst) expression).Value.ToString()); + break; + case EExprToken.EX_FloatConst: + outputBuilder.Append(((EX_FloatConst) expression).Value.ToString(CultureInfo.GetCultureInfo("en-US"))); + break; + case EExprToken.EX_BitFieldConst: + outputBuilder.Append(((EX_BitFieldConst) expression).ConstValue); + break; + case EExprToken.EX_UnicodeStringConst: + outputBuilder.Append(((EX_UnicodeStringConst) expression).Value); + break; + case EExprToken.EX_InstanceDelegate: + outputBuilder.Append($"\"{((EX_InstanceDelegate) expression).FunctionName}\""); + break; + case EExprToken.EX_EndOfScript: + case EExprToken.EX_EndParmValue: + outputBuilder.Append("\t}\n"); + break; + case EExprToken.EX_NoObject: + case EExprToken.EX_NoInterface: + outputBuilder.Append("nullptr"); + break; + case EExprToken.EX_IntOne: + outputBuilder.Append(1); + break; + case EExprToken.EX_IntZero: + outputBuilder.Append(0); + break; + case EExprToken.EX_True: + outputBuilder.Append("true"); + break; + case EExprToken.EX_False: + outputBuilder.Append("false"); + break; + case EExprToken.EX_Self: + outputBuilder.Append("this"); + break; + + case EExprToken.EX_Nothing: + case EExprToken.EX_NothingInt32: + case EExprToken.EX_EndFunctionParms: + case EExprToken.EX_EndStructConst: + case EExprToken.EX_EndArray: + case EExprToken.EX_EndArrayConst: + case EExprToken.EX_EndSet: + case EExprToken.EX_EndMap: + case EExprToken.EX_EndMapConst: + case EExprToken.EX_EndSetConst: + case EExprToken.EX_PushExecutionFlow: + case EExprToken.EX_PopExecutionFlow: + case EExprToken.EX_DeprecatedOp4A: + case EExprToken.EX_WireTracepoint: + case EExprToken.EX_Tracepoint: + case EExprToken.EX_Breakpoint: + case EExprToken.EX_AutoRtfmStopTransact: + case EExprToken.EX_AutoRtfmTransact: + case EExprToken.EX_AutoRtfmAbortIfNot: + // some here are "useful" and unsupported + break; + /* + EExprToken.EX_Assert + EExprToken.EX_Skip + EExprToken.EX_InstrumentationEvent + EExprToken.EX_FieldPathConst + */ + default: + outputBuilder.Append($"{token}"); + break; + } + } +} diff --git a/FModel/Extensions/StringExtensions.cs b/FModel/Extensions/StringExtensions.cs index cc8b523c..63cd033a 100644 --- a/FModel/Extensions/StringExtensions.cs +++ b/FModel/Extensions/StringExtensions.cs @@ -24,6 +24,20 @@ public static partial class StringExtensions return $"{size:# ###.##} {sizes[order]}".TrimStart(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetNameLineNumberText(this string s, string lineToFind) + { + using var reader = new StringReader(s); + var lineNum = 0; + while (reader.ReadLine() is { } line) + { + lineNum++; + if (line.Contains(lineToFind, StringComparison.OrdinalIgnoreCase)) + return lineNum; + } + return -1; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetNameLineNumber(this string s, string lineToFind) { diff --git a/FModel/MainWindow.xaml b/FModel/MainWindow.xaml index 198d8b59..428464e2 100644 --- a/FModel/MainWindow.xaml +++ b/FModel/MainWindow.xaml @@ -468,7 +468,7 @@ - + @@ -501,6 +501,21 @@ + + + + + + + + + + + + + + + diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index e1d71bc3..a678bc44 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -165,6 +165,18 @@ public partial class MainWindow await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); }); } + private void AssetsListName_ContextMenuOpening(object sender, ContextMenuEventArgs e) + { + var listBox = sender as ListBox; + var contextMenu = listBox.ContextMenu; + + var decompItem = contextMenu.Items + .OfType() + .FirstOrDefault(mi => mi.Name == "Decompile"); + + decompItem.Visibility = UserSettings.Default.ShowDecompileOption ? Visibility.Visible : Visibility.Collapsed; + } + private async void OnFolderExtractClick(object sender, RoutedEventArgs e) { if (AssetsFolderName.SelectedItem is TreeItem folder) diff --git a/FModel/Settings/UserSettings.cs b/FModel/Settings/UserSettings.cs index 66c126bf..c812d4ce 100644 --- a/FModel/Settings/UserSettings.cs +++ b/FModel/Settings/UserSettings.cs @@ -202,6 +202,13 @@ namespace FModel.Settings set => SetProperty(ref _keepDirectoryStructure, value); } + private bool _showDecompileOption = false; + public bool ShowDecompileOption + { + get => _showDecompileOption; + set => SetProperty(ref _showDecompileOption, value); + } + private ECompressedAudio _compressedAudioMode = ECompressedAudio.PlayDecompressed; public ECompressedAudio CompressedAudioMode { diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index c99133ba..ca67b451 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -60,6 +60,13 @@ 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; + namespace FModel.ViewModels; @@ -946,8 +953,495 @@ public class CUE4ParseViewModel : ViewModel TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(""); TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(package, Formatting.Indented), false, false); - } + } + + public void Decompile(GameFile entry) + { + var package = Provider.LoadPackage(entry); + if (TabControl.CanAddTabs) + TabControl.AddTab(entry); + else + TabControl.SelectedTab.SoftReset(entry); + + TabControl.SelectedTab.TitleExtra = "Decompiled"; + TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("txt"); + + var pkg = Provider.LoadPackage(entry); + + var outputBuilder = new StringBuilder(); + for (var i = 0; i < pkg.ExportMapLength; i++) + { + var pointer = new FPackageIndex(pkg, i + 1).ResolvedObject; + if (pointer?.Object is null) + continue; + + var dummy = ((AbstractUePackage) pkg).ConstructObject( + pointer.Class?.Object?.Value as UStruct, pkg); + switch (dummy) + { + case UBlueprintGeneratedClass _: + case UVerseClass _: + { + var blueprintGeneratedClass = pkg.ExportsLazy.FirstOrDefault(e => e.Value is UBlueprintGeneratedClass)?.Value as UBlueprintGeneratedClass; + var verseClass = pkg.ExportsLazy.Where(export => export.Value is UVerseClass).Select(export => (UVerseClass) export.Value).FirstOrDefault(); + + var stringsarray = new List(); + if (blueprintGeneratedClass != null) + { + { + var mainClass = blueprintGeneratedClass?.Name ?? verseClass?.Name; + var superStructName = blueprintGeneratedClass?.SuperStruct?.Name ?? verseClass?.SuperStruct?.Name ?? string.Empty; + outputBuilder.AppendLine( + $"class {KismetExtensions.GetPrefix(blueprintGeneratedClass?.GetType().Name ?? verseClass?.GetType().Name)}{mainClass} : public {KismetExtensions.GetPrefix(blueprintGeneratedClass?.GetType().Name ?? verseClass?.GetType().Name)}{superStructName}\n{{\npublic:"); + + foreach (var export in pkg.ExportsLazy) + { + if (export.Value is not UBlueprintGeneratedClass) + + if (export.Value.Name.StartsWith("Default__") && + export.Value.Name.EndsWith(mainClass ?? string.Empty)) + { + var exportObject = export.Value; + foreach (var key in exportObject.Properties) + { + stringsarray.Add(key.Name.PlainText); + string placeholder = $"{key.Name}placenolder"; + string result = key.Tag.GenericValue.ToString(); + string keyName = key.Name.PlainText.Replace(" ", ""); + + var propertyTag = key.Tag.GetValue(typeof(object)); + + void ShouldAppend(string? value) + { + if (value == null) + return; + if (outputBuilder.ToString().Contains(placeholder)) + { + outputBuilder.Replace(placeholder, value); + } + else + { + outputBuilder.AppendLine( + $"\t{KismetExtensions.GetPropertyType(propertyTag)} {keyName} = {value};"); + } + } + + if (key.Tag.GenericValue is FScriptStruct structTag) + { + if (structTag.StructType is FVector vector) + { + ShouldAppend( + $"FVector({vector.X}, {vector.Y}, {vector.Z})"); + } + else if (structTag.StructType is FGuid guid) + { + ShouldAppend( + $"FGuid({guid.A}, {guid.B}, {guid.C}, {guid.D})"); + } + else if (structTag.StructType is TIntVector3 vector3) + { + ShouldAppend( + $"FVector({vector3.X}, {vector3.Y}, {vector3.Z})"); + } + else if (structTag.StructType is TIntVector3 + floatVector3) + { + ShouldAppend( + $"FVector({floatVector3.X}, {floatVector3.Y}, {floatVector3.Z})"); + } + else if (structTag.StructType is TIntVector2 + floatVector2) + { + ShouldAppend( + $"FVector2D({floatVector2.X}, {floatVector2.Y})"); + } + else if (structTag.StructType is FVector2D vector2d) + { + ShouldAppend($"FVector2D({vector2d.X}, {vector2d.Y})"); + } + else if (structTag.StructType is FRotator rotator) + { + ShouldAppend( + $"FRotator({rotator.Pitch}, {rotator.Yaw}, {rotator.Roll})"); + } + else if (structTag.StructType is FStructFallback fallback) + { + string formattedTags; + if (fallback.Properties.Count > 0) + { + formattedTags = "[\n" + string.Join(",\n", + fallback.Properties.Select(tag => + { + string tagDataFormatted; + if (tag.Tag is TextProperty text) + { + tagDataFormatted = $"\"{text.Value.Text}\""; + } + else if (tag.Tag is NameProperty name) + { + tagDataFormatted = $"\"{name.Value.Text}\""; + } + else if (tag.Tag is ObjectProperty + objectproperty) + { + tagDataFormatted = + $"\"{objectproperty.Value}\""; + } + else if (tag.Tag.GenericValue is FScriptStruct innerStruct && innerStruct.StructType is FStructFallback nestedFallback) + { + if (nestedFallback.Properties.Count > 0) + { + tagDataFormatted = "{ " + string.Join(", ", + nestedFallback.Properties.Select(nested => + { + string nestedVal; + if (nested.Tag is TextProperty textProp) + nestedVal = $"\"{textProp.Value.Text}\""; + else if (nested.Tag is NameProperty nameProp) + nestedVal = $"\"{nameProp.Value.Text}\""; + else + nestedVal = $"\"{nested.Tag.GenericValue}\""; + + return $"\"{nested.Name}\": {nestedVal}"; + })) + " }"; + } + else + { + tagDataFormatted = "{}"; + } + } + else + { + tagDataFormatted = tag.Tag.GenericValue != null ? tag.Tag.GenericValue.ToString() : "{}"; + } + ; + + return $"\t\t{{ \"{tag.Name}\": {tagDataFormatted} }}"; + })) + "\n\t]"; + } + else + { + formattedTags = "[]"; + } + + ShouldAppend(formattedTags); + } + else if (structTag.StructType is FGameplayTagContainer + gameplayTag) + { + var tags = gameplayTag.GameplayTags.ToList(); + if (tags.Count > 1) + { + var formattedTags = "[\n" + string.Join(",\n", + tags.Select(tag => + $"\t\t\"{tag.TagName}\"")) + + "\n\t]"; + ShouldAppend(formattedTags); + } + else if (tags.Any()) + { + ShouldAppend($"\"{tags.First().TagName}\""); + } + else + { + ShouldAppend("[]"); + } + } + else if (structTag.StructType is FLinearColor color) + { + ShouldAppend( + $"FLinearColor({color.R}, {color.G}, {color.B}, {color.A})"); + } + else + { + ShouldAppend($"\"{result}\""); + } + } + else if (key.Tag.GetType().Name == "ObjectProperty" || + key.Tag.GetType().Name == "TextProperty" || + key.PropertyType == "StrProperty" || + key.PropertyType == "NameProperty" || + key.PropertyType == "ClassProperty") + { + ShouldAppend($"\"{result}\""); + } + else if (key.Tag.GenericValue is UScriptSet set) + { + var formattedSet = "[\n" + string.Join(",\n", + set.Properties.Select(p => + $"\t\"{p.GenericValue}\"")) + + "\n\t]"; + ShouldAppend(formattedSet); + } + else if (key.Tag.GenericValue is UScriptMap map) + { + var formattedMap = "[\n" + string.Join(",\n", + map.Properties.Select(kvp => + $"\t{{\n\t\t\"{kvp.Key}\": \"{kvp.Value}\"\n\t}}")) + + "\n\t]"; + ShouldAppend(formattedMap); + } + else if (key.Tag.GenericValue is UScriptArray array) + { + var formattedArray = "[\n" + string.Join(",\n", + array.Properties.Select(p => + { + if (p.GenericValue is FScriptStruct vectorInArray && + vectorInArray.StructType is FVector vector) + { + return + $"FVector({vector.X}, {vector.Y}, {vector.Z})"; + } + + if (p.GenericValue is FScriptStruct + vector2dInArray && + vector2dInArray + .StructType is FVector2D vector2d) + { + return $"FVector2D({vector2d.X}, {vector2d.Y})"; + } + + if (p.GenericValue is FScriptStruct structInArray && + structInArray.StructType is FRotator rotator) + { + return + $"FRotator({rotator.Pitch}, {rotator.Yaw}, {rotator.Roll})"; + } + else if + (p.GenericValue is FScriptStruct + fallbacksInArray && + fallbacksInArray.StructType is FStructFallback + fallback) + { + string formattedTags; + if (fallback.Properties.Count > 0) + { + formattedTags = "\t[\n" + string.Join(",\n", + fallback.Properties.Select(tag => + { + string tagDataFormatted; + if (tag.Tag is TextProperty text) + { + tagDataFormatted = + $"\"{text.Value.Text}\""; + } + else if (tag.Tag is NameProperty + name) + { + tagDataFormatted = + $"\"{name.Value.Text}\""; + } + else if (tag.Tag is ObjectProperty + objectproperty) + { + tagDataFormatted = + $"\"{objectproperty.Value}\""; + } + else + { + tagDataFormatted = + $"\"{tag.Tag.GenericValue}\""; + } + + return + $"\t\t\"{tag.Name}\": {tagDataFormatted}"; + })) + "\n\t]"; + } + else + { + formattedTags = "{}"; + } + + return formattedTags; + } + else if + (p.GenericValue is FScriptStruct + gameplayTagsInArray && + gameplayTagsInArray.StructType is + FGameplayTagContainer gameplayTag) + { + var tags = gameplayTag.GameplayTags.ToList(); + if (tags.Count > 1) + { + var formattedTags = + "[\n" + string.Join(",\n", + tags.Select(tag => + $"\t\t\"{tag.TagName}\"")) + + "\n\t]"; + return formattedTags; + } + else + { + return $"\"{tags.First().TagName}\""; + } + } + + return $"\t\t\"{p.GenericValue}\""; + })) + "\n\t]"; + ShouldAppend(formattedArray); + } + else if (key.Tag.GenericValue is FMulticastScriptDelegate multicast) + { + var list = multicast.InvocationList; + ShouldAppend(list.Length == 0 ? "[]" : $"[{string.Join(", ", list.Select(x => $"\"{x.FunctionName}\""))}]"); + } + + else if (key.Tag.GenericValue is bool boolResult) + { + ShouldAppend(boolResult.ToString().ToLower()); + } + else + { + ShouldAppend(result); + } + } + } + else + { + //outputBuilder.Append($"\nType: {export.Value.Name}"); + } + } + } + + var childProperties = blueprintGeneratedClass?.ChildProperties ?? + verseClass?.ChildProperties; + if (childProperties != null) + { + foreach (FProperty property in childProperties) + { + if (!stringsarray.Contains(property.Name.PlainText)) + outputBuilder.AppendLine( + $"\t{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || property.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || KismetExtensions.GetPropertyProperty(property) ? "*" : string.Empty)} {property.Name.PlainText.Replace(" ", "")} = {property.Name.PlainText.Replace(" ", "")}placenolder;"); + } + } + var funcMapOrder = + blueprintGeneratedClass?.FuncMap?.Keys.Select(fname => fname.ToString()) + .ToList() ?? verseClass?.FuncMap.Keys.Select(fname => fname.ToString()) + .ToList(); + var functions = pkg.ExportsLazy + .Where(e => e.Value is UFunction) + .Select(e => (UFunction) e.Value) + .OrderBy(f => + { + if (funcMapOrder != null) + { + var functionName = f.Name.ToString(); + int indexx = funcMapOrder.IndexOf(functionName); + return indexx >= 0 ? indexx : int.MaxValue; + } + + return int.MaxValue; + }) + .ThenBy(f => f.Name.ToString()) + .ToList(); + + var jumpCodeOffsetsMap = new Dictionary>(); + + foreach (var function in functions.AsEnumerable().Reverse()) + { + if (function?.ScriptBytecode == null) + continue; + + foreach (var property in function.ScriptBytecode) + { + string? label = null; + int? offset = null; + + switch (property.Token) + { + case EExprToken.EX_JumpIfNot: + label = ((EX_JumpIfNot) property).ObjectPath?.ToString()?.Split('.').Last().Split('[')[0]; + offset = (int) ((EX_JumpIfNot) property).CodeOffset; + break; + + case EExprToken.EX_Jump: + label = ((EX_Jump) property).ObjectPath?.ToString()?.Split('.').Last().Split('[')[0]; + offset = (int) ((EX_Jump) property).CodeOffset; + break; + + case EExprToken.EX_LocalFinalFunction: + { + EX_FinalFunction op = (EX_FinalFunction) property; + label = op.StackNode?.Name?.ToString()?.Split('.').Last().Split('[')[0]; + + if (op.Parameters.Length == 1 && op.Parameters[0] is EX_IntConst intConst) + offset = intConst.Value; + break; + } + } + + if (!string.IsNullOrEmpty(label) && offset.HasValue) + { + if (!jumpCodeOffsetsMap.TryGetValue(label, out var list)) + jumpCodeOffsetsMap[label] = list = new List(); + + list.Add(offset.Value); + } + } + } + + + + foreach (var function in functions) + { + string argsList = ""; + string returnFunc = "void"; + if (function?.ChildProperties != null) + { + foreach (FProperty property in function.ChildProperties) + { + if (property.Name.PlainText == "ReturnValue") + { + returnFunc = + $"{(property.PropertyFlags.HasFlag(EPropertyFlags.ConstParm) ? "const " : string.Empty)}{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || KismetExtensions.GetPrefix(property.GetType().Name) == "U" ? "*" : string.Empty)}"; + } + else if (!(property.Name.ToString().EndsWith("_ReturnValue") || + property.Name.ToString().StartsWith("CallFunc_") || + property.Name.ToString().StartsWith("K2Node_") || + property.Name.ToString() + .StartsWith("Temp_")) || // removes useless args + property.PropertyFlags.HasFlag(EPropertyFlags.Edit)) + { + argsList += + $"{(property.PropertyFlags.HasFlag(EPropertyFlags.ConstParm) ? "const " : string.Empty)}{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || KismetExtensions.GetPrefix(property.GetType().Name) == "U" ? "*" : string.Empty)}{(property.PropertyFlags.HasFlag(EPropertyFlags.OutParm) ? "&" : string.Empty)} {Regex.Replace(property.Name.ToString(), @"^__verse_0x[0-9A-Fa-f]+_", "")}, "; + } + } + } + argsList = argsList.TrimEnd(',', ' '); + + outputBuilder.AppendLine($"\n\t{returnFunc} {function.Name.Replace(" ", "")}({argsList})\n\t{{"); + if (function?.ScriptBytecode != null) + { + var jumpCodeOffsets = jumpCodeOffsetsMap.TryGetValue(function.Name, out var list) ? list : new List(); + foreach (KismetExpression property in function.ScriptBytecode) + { + KismetExtensions.ProcessExpression(property.Token, property, outputBuilder, jumpCodeOffsets); + } + } + else + { + outputBuilder.Append( + "\n\t // This function does not have Bytecode \n\n"); + outputBuilder.Append("\t}\n"); + } + } + + outputBuilder.Append("\n\n}"); + } + else + { + continue; + } + break; + } + } + } + string pattern = $@"\w+placenolder"; + string updatedOutput = Regex.Replace(outputBuilder.ToString(), pattern, "nullptr"); + + TabControl.SelectedTab.SetDocumentText(updatedOutput, false, false); + } private void SaveAndPlaySound(string fullPath, string ext, byte[] data) { if (fullPath.StartsWith("/")) fullPath = fullPath[1..]; diff --git a/FModel/ViewModels/Commands/RightClickMenuCommand.cs b/FModel/ViewModels/Commands/RightClickMenuCommand.cs index 0801e81b..fdd2fb6b 100644 --- a/FModel/ViewModels/Commands/RightClickMenuCommand.cs +++ b/FModel/ViewModels/Commands/RightClickMenuCommand.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using System.Linq; using System.Threading; using CUE4Parse.FileProvider.Objects; @@ -44,6 +44,14 @@ public class RightClickMenuCommand : ViewModelCommand contextViewModel.CUE4Parse.ShowMetadata(entry); } break; + case "Assets_Decompile": + foreach (var entry in entries) + { + Thread.Yield(); + cancellationToken.ThrowIfCancellationRequested(); + contextViewModel.CUE4Parse.Decompile(entry); + } + break; case "Assets_Export_Data": foreach (var entry in entries) { diff --git a/FModel/Views/Resources/Controls/Aed/JumpElementGenerator.cs b/FModel/Views/Resources/Controls/Aed/JumpElementGenerator.cs new file mode 100644 index 00000000..3c173f47 --- /dev/null +++ b/FModel/Views/Resources/Controls/Aed/JumpElementGenerator.cs @@ -0,0 +1,35 @@ +using System.Text.RegularExpressions; +using FModel.Extensions; +using ICSharpCode.AvalonEdit.Rendering; + +namespace FModel.Views.Resources.Controls; + +public class JumpElementGenerator : VisualLineElementGenerator +{ + private readonly Regex _JumpRegex = new( + @"\b(?:goto\s+Label_(?'target'\d+);|ExecuteUbergraph_(?'target'\d+)\(10\);)", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + private Match FindMatch(int startOffset) + { + var endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset; + var relevantText = CurrentContext.Document.GetText(startOffset, endOffset - startOffset); + return _JumpRegex.Match(relevantText); + } + + public override int GetFirstInterestedOffset(int startOffset) + { + var m = FindMatch(startOffset); + return m.Success ? startOffset + m.Index : -1; + } + + public override VisualLineElement ConstructElement(int offset) + { + var m = FindMatch(offset); + if (!m.Success || m.Index != 0 || + !m.Groups.TryGetValue("target", out var g)) + return null; + + return new JumpVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1); + } +} diff --git a/FModel/Views/Resources/Controls/Aed/JumpVisualLineText.cs b/FModel/Views/Resources/Controls/Aed/JumpVisualLineText.cs new file mode 100644 index 00000000..1d8aef57 --- /dev/null +++ b/FModel/Views/Resources/Controls/Aed/JumpVisualLineText.cs @@ -0,0 +1,80 @@ +using System; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.TextFormatting; +using FModel.Extensions; +using FModel.Services; +using FModel.ViewModels; +using ICSharpCode.AvalonEdit.Rendering; + +namespace FModel.Views.Resources.Controls; + +public class JumpVisualLineText : VisualLineText +{ + private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; + private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + + public delegate void JumpOnClick(string Jump); + + public event JumpOnClick OnJumpClicked; + private readonly string _Jump; + + public JumpVisualLineText(string Jump, VisualLine parentVisualLine, int length) : base(parentVisualLine, length) + { + _Jump = Jump; + } + + public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + + var relativeOffset = startVisualColumn - VisualColumn; + var text = context.GetText(context.VisualLine.FirstDocumentLine.Offset + RelativeTextOffset + relativeOffset, DocumentLength - relativeOffset); + + if (text.Count != 2) // ": " + TextRunProperties.SetForegroundBrush(Brushes.Plum); + + return new TextCharacters(text.Text, text.Offset, text.Count, TextRunProperties); + } + + private bool JumpIsClickable() => !string.IsNullOrEmpty(_Jump) && Keyboard.Modifiers == ModifierKeys.None; + + protected override void OnQueryCursor(QueryCursorEventArgs e) + { + if (!JumpIsClickable()) + return; + e.Handled = true; + e.Cursor = Cursors.Hand; + } + + protected override void OnMouseDown(MouseButtonEventArgs e) + { + if (e.ChangedButton != MouseButton.Left || !JumpIsClickable()) + return; + if (e.Handled || OnJumpClicked == null) + return; + + OnJumpClicked(_Jump); + e.Handled = true; + } + + protected override VisualLineText CreateInstance(int length) + { + var a = new JumpVisualLineText(_Jump, ParentVisualLine, length); + a.OnJumpClicked += async (Jump) => + { + var lineNumber = a.ParentVisualLine.Document.Text.GetNameLineNumberText($"Label_{_Jump}:"); + + if (lineNumber > -1) + { + var line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber); + AvalonEditor.YesWeEditor.Select(line.Offset, line.Length); + AvalonEditor.YesWeEditor.ScrollToLine(lineNumber); + return; + } + }; + return a; + } + +} diff --git a/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs b/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs index cdf50ae0..db81c600 100644 --- a/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs +++ b/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs @@ -43,6 +43,7 @@ public partial class AvalonEditor MyAvalonEditor.TextArea.TextView.LinkTextBackgroundBrush = null; MyAvalonEditor.TextArea.TextView.LinkTextForegroundBrush = Brushes.Cornsilk; MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new GamePathElementGenerator()); + MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new JumpElementGenerator()); MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new HexColorElementGenerator()); ApplicationService.ApplicationView.CUE4Parse.TabControl.OnTabRemove += OnTabClose; diff --git a/FModel/Views/Resources/Controls/PropertiesPopout.xaml.cs b/FModel/Views/Resources/Controls/PropertiesPopout.xaml.cs index 3bb5a275..ed4b3ff7 100644 --- a/FModel/Views/Resources/Controls/PropertiesPopout.xaml.cs +++ b/FModel/Views/Resources/Controls/PropertiesPopout.xaml.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text.RegularExpressions; using System.Windows.Input; using System.Windows.Media; @@ -32,6 +32,7 @@ public partial class PropertiesPopout MyAvalonEditor.TextArea.TextView.LinkTextBackgroundBrush = null; MyAvalonEditor.TextArea.TextView.LinkTextForegroundBrush = Brushes.Cornsilk; MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new GamePathElementGenerator()); + MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new JumpElementGenerator()); MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new HexColorElementGenerator()); _manager = new JsonFoldingStrategies(MyAvalonEditor); _manager.UpdateFoldings(MyAvalonEditor.Document); diff --git a/FModel/Views/SettingsView.xaml b/FModel/Views/SettingsView.xaml index d8a79e24..378e71c5 100644 --- a/FModel/Views/SettingsView.xaml +++ b/FModel/Views/SettingsView.xaml @@ -44,6 +44,7 @@ + @@ -227,6 +228,10 @@ + + + From b0625bbd6f6e9e22919f7b973ee426a1685e62dc Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Sat, 5 Jul 2025 09:06:21 +1000 Subject: [PATCH 02/25] gitignore and md files are now able to be read --- FModel/ViewModels/CUE4ParseViewModel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index ca67b451..46c79f52 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -593,6 +593,7 @@ public class CUE4ParseViewModel : ViewModel case "uplugin": case "archive": case "dnearchive": // Banishers: Ghosts of New Eden + case "gitignore": case "vmodule": case "uparam": // Steel Hunters case "verse": @@ -618,6 +619,8 @@ public class CUE4ParseViewModel : ViewModel case "lua": case "js": case "po": + case "md": + case "po": case "h": { var data = Provider.SaveAsset(entry); From 656d3b59ca1802811be7fabe627309c2935a361e Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Sat, 5 Jul 2025 13:09:23 +1000 Subject: [PATCH 03/25] mb --- FModel/ViewModels/CUE4ParseViewModel.cs | 959 ++++++++++++------------ 1 file changed, 479 insertions(+), 480 deletions(-) diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 46c79f52..f635458f 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -65,7 +65,7 @@ using CUE4Parse.UE4.Assets.Objects; using CUE4Parse.UE4.Kismet; using CUE4Parse.UE4.Objects.Core.Math; using CUE4Parse.UE4.Objects.GameplayTags; -using System.Text; +using System.Text; namespace FModel.ViewModels; @@ -620,7 +620,6 @@ public class CUE4ParseViewModel : ViewModel case "js": case "po": case "md": - case "po": case "h": { var data = Provider.SaveAsset(entry); @@ -956,493 +955,493 @@ public class CUE4ParseViewModel : ViewModel TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(""); TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(package, Formatting.Indented), false, false); - } - + } + public void Decompile(GameFile entry) { var package = Provider.LoadPackage(entry); - if (TabControl.CanAddTabs) + if (TabControl.CanAddTabs) TabControl.AddTab(entry); - else + else TabControl.SelectedTab.SoftReset(entry); TabControl.SelectedTab.TitleExtra = "Decompiled"; TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("txt"); - - var pkg = Provider.LoadPackage(entry); - - var outputBuilder = new StringBuilder(); - for (var i = 0; i < pkg.ExportMapLength; i++) - { - var pointer = new FPackageIndex(pkg, i + 1).ResolvedObject; - if (pointer?.Object is null) - continue; - - var dummy = ((AbstractUePackage) pkg).ConstructObject( - pointer.Class?.Object?.Value as UStruct, pkg); - switch (dummy) - { - case UBlueprintGeneratedClass _: - case UVerseClass _: - { - var blueprintGeneratedClass = pkg.ExportsLazy.FirstOrDefault(e => e.Value is UBlueprintGeneratedClass)?.Value as UBlueprintGeneratedClass; - var verseClass = pkg.ExportsLazy.Where(export => export.Value is UVerseClass).Select(export => (UVerseClass) export.Value).FirstOrDefault(); - - var stringsarray = new List(); - if (blueprintGeneratedClass != null) - { - { - var mainClass = blueprintGeneratedClass?.Name ?? verseClass?.Name; - var superStructName = blueprintGeneratedClass?.SuperStruct?.Name ?? verseClass?.SuperStruct?.Name ?? string.Empty; - outputBuilder.AppendLine( - $"class {KismetExtensions.GetPrefix(blueprintGeneratedClass?.GetType().Name ?? verseClass?.GetType().Name)}{mainClass} : public {KismetExtensions.GetPrefix(blueprintGeneratedClass?.GetType().Name ?? verseClass?.GetType().Name)}{superStructName}\n{{\npublic:"); - - foreach (var export in pkg.ExportsLazy) - { - if (export.Value is not UBlueprintGeneratedClass) - - if (export.Value.Name.StartsWith("Default__") && - export.Value.Name.EndsWith(mainClass ?? string.Empty)) - { - var exportObject = export.Value; - foreach (var key in exportObject.Properties) - { - stringsarray.Add(key.Name.PlainText); - string placeholder = $"{key.Name}placenolder"; - string result = key.Tag.GenericValue.ToString(); - string keyName = key.Name.PlainText.Replace(" ", ""); - - var propertyTag = key.Tag.GetValue(typeof(object)); - - void ShouldAppend(string? value) - { - if (value == null) - return; - if (outputBuilder.ToString().Contains(placeholder)) - { - outputBuilder.Replace(placeholder, value); - } - else - { - outputBuilder.AppendLine( - $"\t{KismetExtensions.GetPropertyType(propertyTag)} {keyName} = {value};"); - } - } - - if (key.Tag.GenericValue is FScriptStruct structTag) - { - if (structTag.StructType is FVector vector) - { - ShouldAppend( - $"FVector({vector.X}, {vector.Y}, {vector.Z})"); - } - else if (structTag.StructType is FGuid guid) - { - ShouldAppend( - $"FGuid({guid.A}, {guid.B}, {guid.C}, {guid.D})"); - } - else if (structTag.StructType is TIntVector3 vector3) - { - ShouldAppend( - $"FVector({vector3.X}, {vector3.Y}, {vector3.Z})"); - } - else if (structTag.StructType is TIntVector3 - floatVector3) - { - ShouldAppend( - $"FVector({floatVector3.X}, {floatVector3.Y}, {floatVector3.Z})"); - } - else if (structTag.StructType is TIntVector2 - floatVector2) - { - ShouldAppend( - $"FVector2D({floatVector2.X}, {floatVector2.Y})"); - } - else if (structTag.StructType is FVector2D vector2d) - { - ShouldAppend($"FVector2D({vector2d.X}, {vector2d.Y})"); - } - else if (structTag.StructType is FRotator rotator) - { - ShouldAppend( - $"FRotator({rotator.Pitch}, {rotator.Yaw}, {rotator.Roll})"); - } - else if (structTag.StructType is FStructFallback fallback) - { - string formattedTags; - if (fallback.Properties.Count > 0) - { - formattedTags = "[\n" + string.Join(",\n", - fallback.Properties.Select(tag => - { - string tagDataFormatted; - if (tag.Tag is TextProperty text) - { - tagDataFormatted = $"\"{text.Value.Text}\""; - } - else if (tag.Tag is NameProperty name) - { - tagDataFormatted = $"\"{name.Value.Text}\""; - } - else if (tag.Tag is ObjectProperty - objectproperty) - { - tagDataFormatted = - $"\"{objectproperty.Value}\""; - } - else if (tag.Tag.GenericValue is FScriptStruct innerStruct && innerStruct.StructType is FStructFallback nestedFallback) - { - if (nestedFallback.Properties.Count > 0) - { - tagDataFormatted = "{ " + string.Join(", ", - nestedFallback.Properties.Select(nested => - { - string nestedVal; - if (nested.Tag is TextProperty textProp) - nestedVal = $"\"{textProp.Value.Text}\""; - else if (nested.Tag is NameProperty nameProp) - nestedVal = $"\"{nameProp.Value.Text}\""; - else - nestedVal = $"\"{nested.Tag.GenericValue}\""; - - return $"\"{nested.Name}\": {nestedVal}"; - })) + " }"; - } - else - { - tagDataFormatted = "{}"; - } - } - else - { - tagDataFormatted = tag.Tag.GenericValue != null ? tag.Tag.GenericValue.ToString() : "{}"; - } - ; - - return $"\t\t{{ \"{tag.Name}\": {tagDataFormatted} }}"; - })) + "\n\t]"; - } - else - { - formattedTags = "[]"; - } - - ShouldAppend(formattedTags); - } - else if (structTag.StructType is FGameplayTagContainer - gameplayTag) - { - var tags = gameplayTag.GameplayTags.ToList(); - if (tags.Count > 1) - { - var formattedTags = "[\n" + string.Join(",\n", - tags.Select(tag => - $"\t\t\"{tag.TagName}\"")) + - "\n\t]"; - ShouldAppend(formattedTags); - } - else if (tags.Any()) - { - ShouldAppend($"\"{tags.First().TagName}\""); - } - else - { - ShouldAppend("[]"); - } - } - else if (structTag.StructType is FLinearColor color) - { - ShouldAppend( - $"FLinearColor({color.R}, {color.G}, {color.B}, {color.A})"); - } - else - { - ShouldAppend($"\"{result}\""); - } - } - else if (key.Tag.GetType().Name == "ObjectProperty" || - key.Tag.GetType().Name == "TextProperty" || - key.PropertyType == "StrProperty" || - key.PropertyType == "NameProperty" || - key.PropertyType == "ClassProperty") - { - ShouldAppend($"\"{result}\""); - } - else if (key.Tag.GenericValue is UScriptSet set) - { - var formattedSet = "[\n" + string.Join(",\n", - set.Properties.Select(p => - $"\t\"{p.GenericValue}\"")) + - "\n\t]"; - ShouldAppend(formattedSet); - } - else if (key.Tag.GenericValue is UScriptMap map) - { - var formattedMap = "[\n" + string.Join(",\n", - map.Properties.Select(kvp => - $"\t{{\n\t\t\"{kvp.Key}\": \"{kvp.Value}\"\n\t}}")) + - "\n\t]"; - ShouldAppend(formattedMap); - } - else if (key.Tag.GenericValue is UScriptArray array) - { - var formattedArray = "[\n" + string.Join(",\n", - array.Properties.Select(p => - { - if (p.GenericValue is FScriptStruct vectorInArray && - vectorInArray.StructType is FVector vector) - { - return - $"FVector({vector.X}, {vector.Y}, {vector.Z})"; - } - - if (p.GenericValue is FScriptStruct - vector2dInArray && - vector2dInArray - .StructType is FVector2D vector2d) - { - return $"FVector2D({vector2d.X}, {vector2d.Y})"; - } - - if (p.GenericValue is FScriptStruct structInArray && - structInArray.StructType is FRotator rotator) - { - return - $"FRotator({rotator.Pitch}, {rotator.Yaw}, {rotator.Roll})"; - } - else if - (p.GenericValue is FScriptStruct - fallbacksInArray && - fallbacksInArray.StructType is FStructFallback - fallback) - { - string formattedTags; - if (fallback.Properties.Count > 0) - { - formattedTags = "\t[\n" + string.Join(",\n", - fallback.Properties.Select(tag => - { - string tagDataFormatted; - if (tag.Tag is TextProperty text) - { - tagDataFormatted = - $"\"{text.Value.Text}\""; - } - else if (tag.Tag is NameProperty - name) - { - tagDataFormatted = - $"\"{name.Value.Text}\""; - } - else if (tag.Tag is ObjectProperty - objectproperty) - { - tagDataFormatted = - $"\"{objectproperty.Value}\""; - } - else - { - tagDataFormatted = - $"\"{tag.Tag.GenericValue}\""; - } - - return - $"\t\t\"{tag.Name}\": {tagDataFormatted}"; - })) + "\n\t]"; - } - else - { - formattedTags = "{}"; - } - - return formattedTags; - } - else if - (p.GenericValue is FScriptStruct - gameplayTagsInArray && - gameplayTagsInArray.StructType is - FGameplayTagContainer gameplayTag) - { - var tags = gameplayTag.GameplayTags.ToList(); - if (tags.Count > 1) - { - var formattedTags = - "[\n" + string.Join(",\n", - tags.Select(tag => - $"\t\t\"{tag.TagName}\"")) + - "\n\t]"; - return formattedTags; - } - else - { - return $"\"{tags.First().TagName}\""; - } - } - - return $"\t\t\"{p.GenericValue}\""; - })) + "\n\t]"; - ShouldAppend(formattedArray); - } - else if (key.Tag.GenericValue is FMulticastScriptDelegate multicast) - { - var list = multicast.InvocationList; - ShouldAppend(list.Length == 0 ? "[]" : $"[{string.Join(", ", list.Select(x => $"\"{x.FunctionName}\""))}]"); - } - - else if (key.Tag.GenericValue is bool boolResult) - { - ShouldAppend(boolResult.ToString().ToLower()); - } - else - { - ShouldAppend(result); - } - } - } - else - { - //outputBuilder.Append($"\nType: {export.Value.Name}"); - } - } - } - - var childProperties = blueprintGeneratedClass?.ChildProperties ?? - verseClass?.ChildProperties; - if (childProperties != null) - { - foreach (FProperty property in childProperties) - { - if (!stringsarray.Contains(property.Name.PlainText)) - outputBuilder.AppendLine( - $"\t{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || property.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || KismetExtensions.GetPropertyProperty(property) ? "*" : string.Empty)} {property.Name.PlainText.Replace(" ", "")} = {property.Name.PlainText.Replace(" ", "")}placenolder;"); - } - } - var funcMapOrder = - blueprintGeneratedClass?.FuncMap?.Keys.Select(fname => fname.ToString()) - .ToList() ?? verseClass?.FuncMap.Keys.Select(fname => fname.ToString()) - .ToList(); - var functions = pkg.ExportsLazy - .Where(e => e.Value is UFunction) - .Select(e => (UFunction) e.Value) - .OrderBy(f => - { - if (funcMapOrder != null) - { - var functionName = f.Name.ToString(); - int indexx = funcMapOrder.IndexOf(functionName); - return indexx >= 0 ? indexx : int.MaxValue; - } - - return int.MaxValue; - }) - .ThenBy(f => f.Name.ToString()) - .ToList(); - - var jumpCodeOffsetsMap = new Dictionary>(); - - foreach (var function in functions.AsEnumerable().Reverse()) - { - if (function?.ScriptBytecode == null) - continue; - - foreach (var property in function.ScriptBytecode) - { - string? label = null; - int? offset = null; - - switch (property.Token) - { - case EExprToken.EX_JumpIfNot: - label = ((EX_JumpIfNot) property).ObjectPath?.ToString()?.Split('.').Last().Split('[')[0]; - offset = (int) ((EX_JumpIfNot) property).CodeOffset; - break; - - case EExprToken.EX_Jump: - label = ((EX_Jump) property).ObjectPath?.ToString()?.Split('.').Last().Split('[')[0]; - offset = (int) ((EX_Jump) property).CodeOffset; - break; - - case EExprToken.EX_LocalFinalFunction: - { - EX_FinalFunction op = (EX_FinalFunction) property; - label = op.StackNode?.Name?.ToString()?.Split('.').Last().Split('[')[0]; - - if (op.Parameters.Length == 1 && op.Parameters[0] is EX_IntConst intConst) - offset = intConst.Value; - break; - } - } - - if (!string.IsNullOrEmpty(label) && offset.HasValue) - { - if (!jumpCodeOffsetsMap.TryGetValue(label, out var list)) - jumpCodeOffsetsMap[label] = list = new List(); - - list.Add(offset.Value); - } - } - } - - - - foreach (var function in functions) - { - string argsList = ""; - string returnFunc = "void"; - if (function?.ChildProperties != null) - { - foreach (FProperty property in function.ChildProperties) - { - if (property.Name.PlainText == "ReturnValue") - { - returnFunc = - $"{(property.PropertyFlags.HasFlag(EPropertyFlags.ConstParm) ? "const " : string.Empty)}{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || KismetExtensions.GetPrefix(property.GetType().Name) == "U" ? "*" : string.Empty)}"; - } - else if (!(property.Name.ToString().EndsWith("_ReturnValue") || - property.Name.ToString().StartsWith("CallFunc_") || - property.Name.ToString().StartsWith("K2Node_") || - property.Name.ToString() - .StartsWith("Temp_")) || // removes useless args - property.PropertyFlags.HasFlag(EPropertyFlags.Edit)) - { - argsList += - $"{(property.PropertyFlags.HasFlag(EPropertyFlags.ConstParm) ? "const " : string.Empty)}{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || KismetExtensions.GetPrefix(property.GetType().Name) == "U" ? "*" : string.Empty)}{(property.PropertyFlags.HasFlag(EPropertyFlags.OutParm) ? "&" : string.Empty)} {Regex.Replace(property.Name.ToString(), @"^__verse_0x[0-9A-Fa-f]+_", "")}, "; - } - } - } - argsList = argsList.TrimEnd(',', ' '); - - outputBuilder.AppendLine($"\n\t{returnFunc} {function.Name.Replace(" ", "")}({argsList})\n\t{{"); - if (function?.ScriptBytecode != null) - { - var jumpCodeOffsets = jumpCodeOffsetsMap.TryGetValue(function.Name, out var list) ? list : new List(); - foreach (KismetExpression property in function.ScriptBytecode) - { - KismetExtensions.ProcessExpression(property.Token, property, outputBuilder, jumpCodeOffsets); - } - } - else - { - outputBuilder.Append( - "\n\t // This function does not have Bytecode \n\n"); - outputBuilder.Append("\t}\n"); - } - } - - outputBuilder.Append("\n\n}"); - } - else - { - continue; - } - break; - } - } - } - string pattern = $@"\w+placenolder"; - string updatedOutput = Regex.Replace(outputBuilder.ToString(), pattern, "nullptr"); - + + var pkg = Provider.LoadPackage(entry); + + var outputBuilder = new StringBuilder(); + for (var i = 0; i < pkg.ExportMapLength; i++) + { + var pointer = new FPackageIndex(pkg, i + 1).ResolvedObject; + if (pointer?.Object is null) + continue; + + var dummy = ((AbstractUePackage) pkg).ConstructObject( + pointer.Class?.Object?.Value as UStruct, pkg); + switch (dummy) + { + case UBlueprintGeneratedClass _: + case UVerseClass _: + { + var blueprintGeneratedClass = pkg.ExportsLazy.FirstOrDefault(e => e.Value is UBlueprintGeneratedClass)?.Value as UBlueprintGeneratedClass; + var verseClass = pkg.ExportsLazy.Where(export => export.Value is UVerseClass).Select(export => (UVerseClass) export.Value).FirstOrDefault(); + + var stringsarray = new List(); + if (blueprintGeneratedClass != null) + { + { + var mainClass = blueprintGeneratedClass?.Name ?? verseClass?.Name; + var superStructName = blueprintGeneratedClass?.SuperStruct?.Name ?? verseClass?.SuperStruct?.Name ?? string.Empty; + outputBuilder.AppendLine( + $"class {KismetExtensions.GetPrefix(blueprintGeneratedClass?.GetType().Name ?? verseClass?.GetType().Name)}{mainClass} : public {KismetExtensions.GetPrefix(blueprintGeneratedClass?.GetType().Name ?? verseClass?.GetType().Name)}{superStructName}\n{{\npublic:"); + + foreach (var export in pkg.ExportsLazy) + { + if (export.Value is not UBlueprintGeneratedClass) + + if (export.Value.Name.StartsWith("Default__") && + export.Value.Name.EndsWith(mainClass ?? string.Empty)) + { + var exportObject = export.Value; + foreach (var key in exportObject.Properties) + { + stringsarray.Add(key.Name.PlainText); + string placeholder = $"{key.Name}placenolder"; + string result = key.Tag.GenericValue.ToString(); + string keyName = key.Name.PlainText.Replace(" ", ""); + + var propertyTag = key.Tag.GetValue(typeof(object)); + + void ShouldAppend(string? value) + { + if (value == null) + return; + if (outputBuilder.ToString().Contains(placeholder)) + { + outputBuilder.Replace(placeholder, value); + } + else + { + outputBuilder.AppendLine( + $"\t{KismetExtensions.GetPropertyType(propertyTag)} {keyName} = {value};"); + } + } + + if (key.Tag.GenericValue is FScriptStruct structTag) + { + if (structTag.StructType is FVector vector) + { + ShouldAppend( + $"FVector({vector.X}, {vector.Y}, {vector.Z})"); + } + else if (structTag.StructType is FGuid guid) + { + ShouldAppend( + $"FGuid({guid.A}, {guid.B}, {guid.C}, {guid.D})"); + } + else if (structTag.StructType is TIntVector3 vector3) + { + ShouldAppend( + $"FVector({vector3.X}, {vector3.Y}, {vector3.Z})"); + } + else if (structTag.StructType is TIntVector3 + floatVector3) + { + ShouldAppend( + $"FVector({floatVector3.X}, {floatVector3.Y}, {floatVector3.Z})"); + } + else if (structTag.StructType is TIntVector2 + floatVector2) + { + ShouldAppend( + $"FVector2D({floatVector2.X}, {floatVector2.Y})"); + } + else if (structTag.StructType is FVector2D vector2d) + { + ShouldAppend($"FVector2D({vector2d.X}, {vector2d.Y})"); + } + else if (structTag.StructType is FRotator rotator) + { + ShouldAppend( + $"FRotator({rotator.Pitch}, {rotator.Yaw}, {rotator.Roll})"); + } + else if (structTag.StructType is FStructFallback fallback) + { + string formattedTags; + if (fallback.Properties.Count > 0) + { + formattedTags = "[\n" + string.Join(",\n", + fallback.Properties.Select(tag => + { + string tagDataFormatted; + if (tag.Tag is TextProperty text) + { + tagDataFormatted = $"\"{text.Value.Text}\""; + } + else if (tag.Tag is NameProperty name) + { + tagDataFormatted = $"\"{name.Value.Text}\""; + } + else if (tag.Tag is ObjectProperty + objectproperty) + { + tagDataFormatted = + $"\"{objectproperty.Value}\""; + } + else if (tag.Tag.GenericValue is FScriptStruct innerStruct && innerStruct.StructType is FStructFallback nestedFallback) + { + if (nestedFallback.Properties.Count > 0) + { + tagDataFormatted = "{ " + string.Join(", ", + nestedFallback.Properties.Select(nested => + { + string nestedVal; + if (nested.Tag is TextProperty textProp) + nestedVal = $"\"{textProp.Value.Text}\""; + else if (nested.Tag is NameProperty nameProp) + nestedVal = $"\"{nameProp.Value.Text}\""; + else + nestedVal = $"\"{nested.Tag.GenericValue}\""; + + return $"\"{nested.Name}\": {nestedVal}"; + })) + " }"; + } + else + { + tagDataFormatted = "{}"; + } + } + else + { + tagDataFormatted = tag.Tag.GenericValue != null ? tag.Tag.GenericValue.ToString() : "{}"; + } + ; + + return $"\t\t{{ \"{tag.Name}\": {tagDataFormatted} }}"; + })) + "\n\t]"; + } + else + { + formattedTags = "[]"; + } + + ShouldAppend(formattedTags); + } + else if (structTag.StructType is FGameplayTagContainer + gameplayTag) + { + var tags = gameplayTag.GameplayTags.ToList(); + if (tags.Count > 1) + { + var formattedTags = "[\n" + string.Join(",\n", + tags.Select(tag => + $"\t\t\"{tag.TagName}\"")) + + "\n\t]"; + ShouldAppend(formattedTags); + } + else if (tags.Any()) + { + ShouldAppend($"\"{tags.First().TagName}\""); + } + else + { + ShouldAppend("[]"); + } + } + else if (structTag.StructType is FLinearColor color) + { + ShouldAppend( + $"FLinearColor({color.R}, {color.G}, {color.B}, {color.A})"); + } + else + { + ShouldAppend($"\"{result}\""); + } + } + else if (key.Tag.GetType().Name == "ObjectProperty" || + key.Tag.GetType().Name == "TextProperty" || + key.PropertyType == "StrProperty" || + key.PropertyType == "NameProperty" || + key.PropertyType == "ClassProperty") + { + ShouldAppend($"\"{result}\""); + } + else if (key.Tag.GenericValue is UScriptSet set) + { + var formattedSet = "[\n" + string.Join(",\n", + set.Properties.Select(p => + $"\t\"{p.GenericValue}\"")) + + "\n\t]"; + ShouldAppend(formattedSet); + } + else if (key.Tag.GenericValue is UScriptMap map) + { + var formattedMap = "[\n" + string.Join(",\n", + map.Properties.Select(kvp => + $"\t{{\n\t\t\"{kvp.Key}\": \"{kvp.Value}\"\n\t}}")) + + "\n\t]"; + ShouldAppend(formattedMap); + } + else if (key.Tag.GenericValue is UScriptArray array) + { + var formattedArray = "[\n" + string.Join(",\n", + array.Properties.Select(p => + { + if (p.GenericValue is FScriptStruct vectorInArray && + vectorInArray.StructType is FVector vector) + { + return + $"FVector({vector.X}, {vector.Y}, {vector.Z})"; + } + + if (p.GenericValue is FScriptStruct + vector2dInArray && + vector2dInArray + .StructType is FVector2D vector2d) + { + return $"FVector2D({vector2d.X}, {vector2d.Y})"; + } + + if (p.GenericValue is FScriptStruct structInArray && + structInArray.StructType is FRotator rotator) + { + return + $"FRotator({rotator.Pitch}, {rotator.Yaw}, {rotator.Roll})"; + } + else if + (p.GenericValue is FScriptStruct + fallbacksInArray && + fallbacksInArray.StructType is FStructFallback + fallback) + { + string formattedTags; + if (fallback.Properties.Count > 0) + { + formattedTags = "\t[\n" + string.Join(",\n", + fallback.Properties.Select(tag => + { + string tagDataFormatted; + if (tag.Tag is TextProperty text) + { + tagDataFormatted = + $"\"{text.Value.Text}\""; + } + else if (tag.Tag is NameProperty + name) + { + tagDataFormatted = + $"\"{name.Value.Text}\""; + } + else if (tag.Tag is ObjectProperty + objectproperty) + { + tagDataFormatted = + $"\"{objectproperty.Value}\""; + } + else + { + tagDataFormatted = + $"\"{tag.Tag.GenericValue}\""; + } + + return + $"\t\t\"{tag.Name}\": {tagDataFormatted}"; + })) + "\n\t]"; + } + else + { + formattedTags = "{}"; + } + + return formattedTags; + } + else if + (p.GenericValue is FScriptStruct + gameplayTagsInArray && + gameplayTagsInArray.StructType is + FGameplayTagContainer gameplayTag) + { + var tags = gameplayTag.GameplayTags.ToList(); + if (tags.Count > 1) + { + var formattedTags = + "[\n" + string.Join(",\n", + tags.Select(tag => + $"\t\t\"{tag.TagName}\"")) + + "\n\t]"; + return formattedTags; + } + else + { + return $"\"{tags.First().TagName}\""; + } + } + + return $"\t\t\"{p.GenericValue}\""; + })) + "\n\t]"; + ShouldAppend(formattedArray); + } + else if (key.Tag.GenericValue is FMulticastScriptDelegate multicast) + { + var list = multicast.InvocationList; + ShouldAppend(list.Length == 0 ? "[]" : $"[{string.Join(", ", list.Select(x => $"\"{x.FunctionName}\""))}]"); + } + + else if (key.Tag.GenericValue is bool boolResult) + { + ShouldAppend(boolResult.ToString().ToLower()); + } + else + { + ShouldAppend(result); + } + } + } + else + { + //outputBuilder.Append($"\nType: {export.Value.Name}"); + } + } + } + + var childProperties = blueprintGeneratedClass?.ChildProperties ?? + verseClass?.ChildProperties; + if (childProperties != null) + { + foreach (FProperty property in childProperties) + { + if (!stringsarray.Contains(property.Name.PlainText)) + outputBuilder.AppendLine( + $"\t{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || property.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || KismetExtensions.GetPropertyProperty(property) ? "*" : string.Empty)} {property.Name.PlainText.Replace(" ", "")} = {property.Name.PlainText.Replace(" ", "")}placenolder;"); + } + } + var funcMapOrder = + blueprintGeneratedClass?.FuncMap?.Keys.Select(fname => fname.ToString()) + .ToList() ?? verseClass?.FuncMap.Keys.Select(fname => fname.ToString()) + .ToList(); + var functions = pkg.ExportsLazy + .Where(e => e.Value is UFunction) + .Select(e => (UFunction) e.Value) + .OrderBy(f => + { + if (funcMapOrder != null) + { + var functionName = f.Name.ToString(); + int indexx = funcMapOrder.IndexOf(functionName); + return indexx >= 0 ? indexx : int.MaxValue; + } + + return int.MaxValue; + }) + .ThenBy(f => f.Name.ToString()) + .ToList(); + + var jumpCodeOffsetsMap = new Dictionary>(); + + foreach (var function in functions.AsEnumerable().Reverse()) + { + if (function?.ScriptBytecode == null) + continue; + + foreach (var property in function.ScriptBytecode) + { + string? label = null; + int? offset = null; + + switch (property.Token) + { + case EExprToken.EX_JumpIfNot: + label = ((EX_JumpIfNot) property).ObjectPath?.ToString()?.Split('.').Last().Split('[')[0]; + offset = (int) ((EX_JumpIfNot) property).CodeOffset; + break; + + case EExprToken.EX_Jump: + label = ((EX_Jump) property).ObjectPath?.ToString()?.Split('.').Last().Split('[')[0]; + offset = (int) ((EX_Jump) property).CodeOffset; + break; + + case EExprToken.EX_LocalFinalFunction: + { + EX_FinalFunction op = (EX_FinalFunction) property; + label = op.StackNode?.Name?.ToString()?.Split('.').Last().Split('[')[0]; + + if (op.Parameters.Length == 1 && op.Parameters[0] is EX_IntConst intConst) + offset = intConst.Value; + break; + } + } + + if (!string.IsNullOrEmpty(label) && offset.HasValue) + { + if (!jumpCodeOffsetsMap.TryGetValue(label, out var list)) + jumpCodeOffsetsMap[label] = list = new List(); + + list.Add(offset.Value); + } + } + } + + + + foreach (var function in functions) + { + string argsList = ""; + string returnFunc = "void"; + if (function?.ChildProperties != null) + { + foreach (FProperty property in function.ChildProperties) + { + if (property.Name.PlainText == "ReturnValue") + { + returnFunc = + $"{(property.PropertyFlags.HasFlag(EPropertyFlags.ConstParm) ? "const " : string.Empty)}{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || KismetExtensions.GetPrefix(property.GetType().Name) == "U" ? "*" : string.Empty)}"; + } + else if (!(property.Name.ToString().EndsWith("_ReturnValue") || + property.Name.ToString().StartsWith("CallFunc_") || + property.Name.ToString().StartsWith("K2Node_") || + property.Name.ToString() + .StartsWith("Temp_")) || // removes useless args + property.PropertyFlags.HasFlag(EPropertyFlags.Edit)) + { + argsList += + $"{(property.PropertyFlags.HasFlag(EPropertyFlags.ConstParm) ? "const " : string.Empty)}{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || KismetExtensions.GetPrefix(property.GetType().Name) == "U" ? "*" : string.Empty)}{(property.PropertyFlags.HasFlag(EPropertyFlags.OutParm) ? "&" : string.Empty)} {Regex.Replace(property.Name.ToString(), @"^__verse_0x[0-9A-Fa-f]+_", "")}, "; + } + } + } + argsList = argsList.TrimEnd(',', ' '); + + outputBuilder.AppendLine($"\n\t{returnFunc} {function.Name.Replace(" ", "")}({argsList})\n\t{{"); + if (function?.ScriptBytecode != null) + { + var jumpCodeOffsets = jumpCodeOffsetsMap.TryGetValue(function.Name, out var list) ? list : new List(); + foreach (KismetExpression property in function.ScriptBytecode) + { + KismetExtensions.ProcessExpression(property.Token, property, outputBuilder, jumpCodeOffsets); + } + } + else + { + outputBuilder.Append( + "\n\t // This function does not have Bytecode \n\n"); + outputBuilder.Append("\t}\n"); + } + } + + outputBuilder.Append("\n\n}"); + } + else + { + continue; + } + break; + } + } + } + string pattern = $@"\w+placenolder"; + string updatedOutput = Regex.Replace(outputBuilder.ToString(), pattern, "nullptr"); + TabControl.SelectedTab.SetDocumentText(updatedOutput, false, false); } private void SaveAndPlaySound(string fullPath, string ext, byte[] data) From b22c996a58dd9574311f16060181cfaccb2417fc Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Sat, 5 Jul 2025 13:12:30 +1000 Subject: [PATCH 04/25] fixed duplicated "po" extension fixed again because github pulled latest files for some reason --- FModel/ViewModels/CUE4ParseViewModel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 46c79f52..18c215f8 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -620,7 +620,6 @@ public class CUE4ParseViewModel : ViewModel case "js": case "po": case "md": - case "po": case "h": { var data = Provider.SaveAsset(entry); From e78c7a7be9e4263cb962cf0ae3cf3811c78f36a9 Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Sun, 6 Jul 2025 12:21:10 +1000 Subject: [PATCH 05/25] Cpp xshd by Masus added projectstore as it said unknown type --- FModel/Resources/Cpp.xshd | 433 +++++++++++++----------- FModel/ViewModels/CUE4ParseViewModel.cs | 1 + 2 files changed, 240 insertions(+), 194 deletions(-) diff --git a/FModel/Resources/Cpp.xshd b/FModel/Resources/Cpp.xshd index c7396ae4..5c3cc352 100644 --- a/FModel/Resources/Cpp.xshd +++ b/FModel/Resources/Cpp.xshd @@ -1,195 +1,240 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - [?,.;()\[\]{}+\-/%*<>^=~!&]+ - - - __abstract - __box - __delegate - __gc - __identifier - __nogc - __pin - __property - __sealed - __try_cast - __typeof - __value - __event - __hook - __raise - __unhook - __interface - ref class - ref struct - value class - value struct - interface class - interface struct - enum class - enum struct - delegate - event - property - abstract - override - sealed - generic - where - finally - for each - gcnew - in - initonly - literal - nullptr - - - this - - - and - and_eq - bitand - bitor - new - not - not_eq - or - or_eq - xor - xor_eq - - - using - namespace - - - friend - - - private - protected - public - const - volatile - static - - - bool - char - unsigned - union - virtual - double - float - short - signed - void - class - enum - struct - - - false - true - - - do - for - while - - - break - continue - goto - return - - - catch - throw - try - - - case - else - if - switch - default - - - asm - auto - compl - mutable - const_cast - delete - dynamic_cast - explicit - export - extern - inline - int - long - operator - register - reinterpret_cast - sizeof - static_cast - template - typedef - typeid - typename - - - \# - - - // - - - /\* - \*/ - - - " - " - - - - - - ' - ' - - - - - [\d\w_]+(?=(\s*\()) - \b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)? - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BlueprintGeneratedClass + Class + FSoftObjectPath + UScriptArray + NavmeshGeneratorComponent + EndGameComponent + USceneComponent + + + + bool + char + unsigned + union + virtual + double + float + short + signed + void + class + enum + struct + + + \b(BlueprintGeneratedClass|Class|FSoftObjectPath|UScriptArray|NavmeshGeneratorComponent|EndGameComponent|USceneComponent)\b + + ABP_[A-Za-z0-9_]+_C + + \bF[A-Z][A-Za-z0-9_]*\b + \bU[A-Z][A-Za-z0-9_]*\b + \bA[A-Z][A-Za-z0-9_]*\b + \bE[A-Z][A-Za-z0-9_]*\b + + Label_[0-9]+ + + CallFunc_[A-Za-z0-9_]+ + K2Node_[A-Za-z0-9_]+ + Temp_delegate_Variable(_[0-9]+)? + + ExecuteUbergraph_[A-Za-z0-9_]+ + UserConstructionScript + ReceiveBeginPlay + OnBeginFadeOut + .*__UpdateFunc + .*__FinishedFunc + + goto + nullptr + + FindObject<[A-Za-z0-9_]+> + + "[^"]*" + \b[0-9]+(\.[0-9]+)?\b + [\[\]\{\}] + + UKismetMathLibrary + UKismetSystemLibrary + + [?,.;()\[\]{}+\-/%*<>^=~!&]+ + + + __abstract + __box + __delegate + __gc + __identifier + __nogc + __pin + __property + __sealed + __try_cast + __typeof + __value + __event + __hook + __raise + __unhook + __interface + ref class + ref struct + value class + value struct + interface class + interface struct + enum class + enum struct + delegate + event + property + abstract + override + sealed + generic + where + finally + for each + gcnew + in + initonly + literal + nullptr + + + + this + + + and + and_eq + bitand + bitor + new + not + not_eq + or + or_eq + xor + xor_eq + + + using + namespace + + + friend + + + private + protected + public + const + volatile + static + + + true + false + NULL + + + break + case + catch + const_cast + continue + default + delete + dynamic_cast + else + explicit + export + extern + false + for + friend + goto + if + mutable + namespace + new + operator + private + protected + public + register + reinterpret_cast + return + sizeof + static_cast + template + throw + try + typedef + typeid + typename + using + virtual + volatile + while + + + do + for + while + break + continue + + + goto + return + throw + + + try + catch + throw + finally + + + if + else + switch + case + default + break + continue + return + + + diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index f635458f..598c4ce9 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -588,6 +588,7 @@ public class CUE4ParseViewModel : ViewModel break; } case "upluginmanifest": + case "projectstore": case "uproject": case "manifest": case "uplugin": From 00bcb7ca160e1297761936e6d6f4ebc1eb7ddfa7 Mon Sep 17 00:00:00 2001 From: Asval Date: Sun, 6 Jul 2025 18:52:16 +0200 Subject: [PATCH 06/25] refactored parts of this hell --- FModel/Extensions/KismetExtensions.cs | 313 +++++------ FModel/Extensions/StringExtensions.cs | 23 +- FModel/MainWindow.xaml | 13 +- FModel/MainWindow.xaml.cs | 12 - FModel/ViewModels/CUE4ParseViewModel.cs | 703 ++++++++---------------- FModel/Views/SettingsView.xaml | 2 +- 6 files changed, 383 insertions(+), 683 deletions(-) diff --git a/FModel/Extensions/KismetExtensions.cs b/FModel/Extensions/KismetExtensions.cs index 0ac16af9..0dde9710 100644 --- a/FModel/Extensions/KismetExtensions.cs +++ b/FModel/Extensions/KismetExtensions.cs @@ -3,10 +3,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; -using System.Text.RegularExpressions; -using CUE4Parse.FileProvider; -using CUE4Parse.FileProvider.Objects; -using CUE4Parse.UE4.Assets; using CUE4Parse.UE4.Assets.Objects.Properties; using CUE4Parse.UE4.Assets.Objects; using CUE4Parse.UE4.Kismet; @@ -16,14 +12,12 @@ using CUE4Parse.UE4.Objects.Engine.Ai; using CUE4Parse.UE4.Objects.Engine.GameFramework; using CUE4Parse.UE4.Objects.GameplayTags; using CUE4Parse.UE4.Objects.UObject; -using FModel.Settings; -using Microsoft.VisualBasic.Logging; namespace FModel.Extensions; public static class KismetExtensions { - public static string GetPrefix(string? type, string? extra = "") + public static string GetPrefix(string type, string extra = "") { return type switch { @@ -43,18 +37,9 @@ public static class KismetExtensions { string typeName = field.GetType().Name; int suffixIndex = typeName.IndexOf("Property", StringComparison.Ordinal); - if (suffixIndex < 0) - return typeName; - return typeName.Substring(1, suffixIndex - 1); - } - public static string GetUnknownFieldType(FField field) - { - string typeName = field.GetType().Name; - int suffixIndex = typeName.IndexOf("Property", StringComparison.Ordinal); - if (suffixIndex < 0) - return typeName; - return typeName.Substring(1, suffixIndex - 1); + return suffixIndex < 0 ? typeName : typeName.Substring(1, suffixIndex - 1); } + public static string GetPropertyType(object? property) { if (property is null) @@ -62,33 +47,33 @@ public static class KismetExtensions return property switch { - FIntProperty => "int", - FInt8Property => "int8", - FInt16Property => "int16", - FInt64Property => "int64", - FUInt16Property => "uint16", - FUInt32Property => "uint32", - FUInt64Property => "uint64", - FBoolProperty or Boolean => "bool", - FStrProperty => "FString", - FFloatProperty or Single => "float", - FDoubleProperty or Double => "double", + FIntProperty or int => "int", + FInt8Property or byte => "int8", + FInt16Property or short => "int16", + FInt64Property or long => "int64", + FUInt16Property or ushort => "uint16", + FUInt32Property or uint => "uint32", + FUInt64Property or ulong => "uint64", + FBoolProperty or bool => "bool", + FStrProperty or string => "FString", + FFloatProperty or float => "float", + FDoubleProperty or double => "double", FObjectProperty objct => property switch { - FClassProperty clss => $"{clss.MetaClass?.Name ?? "UNKNOWN"}", - FSoftClassProperty softClass => $"{softClass.MetaClass?.Name ?? "UNKNOWN"}", - _ => objct.PropertyClass?.Name ?? "UNKNOWN" + FClassProperty clss => $"{clss.MetaClass?.Name ?? "UKN_ObjectMetaClass"} Class", + FSoftClassProperty softClass => $"{softClass.MetaClass?.Name ?? "UKN_ObjectMetaClass"} Class (soft)", + _ => objct.PropertyClass?.Name ?? "UKN_ObjectPropertyClass" }, - FPackageIndex pkg => pkg?.ResolvedObject?.Class?.Name.ToString() ?? "Package", - FName fme => fme.PlainText.Contains("::") ? fme.PlainText.Split("::")[0] : fme.PlainText ?? "FName", - FEnumProperty enm => enm.Enum?.Name.ToString() ?? "Enum", - FByteProperty bt => bt.Enum.ResolvedObject?.Name.Text ?? "Byte", - FInterfaceProperty intrfc => $"{intrfc.InterfaceClass.Name} interface", - FStructProperty strct => strct.Struct.ResolvedObject?.Name.Text ?? "Struct", + FPackageIndex pkg => pkg.ResolvedObject?.Class?.Name.ToString() ?? "Package", + FName fme => fme.PlainText.Contains("::") ? fme.PlainText.Split("::")[0] : fme.PlainText, + FEnumProperty enm => enm.Enum?.Name ?? "Enum", + FByteProperty bt => bt.Enum?.ResolvedObject?.Name.Text ?? "Byte", + FInterfaceProperty intrfc => $"{intrfc.InterfaceClass?.Name ?? "UKN_InterfaceClass"} interface", + FStructProperty strct => strct.Struct?.ResolvedObject?.Name.Text ?? "Struct", FFieldPathProperty fieldPath => $"{fieldPath.PropertyClass.Text} field path", - FDelegateProperty dlgt => $"{dlgt.SignatureFunction?.Name ?? "UNKNOWN"} (Delegate)", - FMulticastDelegateProperty mdlgt => $"{mdlgt.SignatureFunction?.Name ?? "UNKNOWN"} (MulticastDelegateProperty)", - FMulticastInlineDelegateProperty midlgt => $"{midlgt.SignatureFunction?.Name ?? "UNKNOWN"} (MulticastInlineDelegateProperty)", + FDelegateProperty dlgt => $"{dlgt.SignatureFunction?.Name ?? "UKN_SignatureFunction"} (Delegate)", + FMulticastDelegateProperty mdlgt => $"{mdlgt.SignatureFunction?.Name ?? "UKN_SignatureFunction"} (MulticastDelegateProperty)", + FMulticastInlineDelegateProperty midlgt => $"{midlgt.SignatureFunction?.Name ?? "UKN_SignatureFunction"} (MulticastInlineDelegateProperty)", _ => GetUnknownFieldType(property) }; } @@ -99,31 +84,13 @@ public static class KismetExtensions return property switch { - FIntProperty => "int", - FBoolProperty => "bool", - FStrProperty => "FString", - FFloatProperty => "float", - FDoubleProperty => "double", - FObjectProperty objct => property switch - { - FClassProperty clss => $"{clss.MetaClass?.Name ?? "UNKNOWN"} Class", - FSoftClassProperty softClass => $"{softClass.MetaClass?.Name ?? "UNKNOWN"} Class (soft)", - _ => objct.PropertyClass?.Name ?? "UNKNOWN" - }, - FEnumProperty enm => enm.Enum?.Name.ToString() ?? "Enum", FSetProperty set => $"TSet<{GetPrefix(set.ElementProp.GetType().Name)}{GetPropertyType(set.ElementProp)}{(set.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || property.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || set.PropertyFlags.HasFlag(EPropertyFlags.ContainsInstancedReference) ? "*" : string.Empty)}>", - FByteProperty bt => bt.Enum.ResolvedObject?.Name.Text ?? "Byte", - FInterfaceProperty intrfc => $"{intrfc.InterfaceClass.Name} interface", - FStructProperty strct => strct.Struct.ResolvedObject?.Name.Text ?? "Struct", - FFieldPathProperty fieldPath => $"{fieldPath.PropertyClass.Text} field path", - FDelegateProperty dlgt => $"{dlgt.SignatureFunction?.Name ?? "UNKNOWN"} (Delegate)", FMapProperty map => $"TMap<{GetPrefix(map.ValueProp.GetType().Name)}{GetPropertyType(map.KeyProp)}, {GetPrefix(map.ValueProp.GetType().Name)}{GetPropertyType(map.ValueProp)}{(map.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || property.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || map.PropertyFlags.HasFlag(EPropertyFlags.ContainsInstancedReference) ? "*" : string.Empty)}>", - FMulticastDelegateProperty mdlgt => $"{mdlgt.SignatureFunction?.Name ?? "UNKNOWN"} (MulticastDelegateProperty)", - FMulticastInlineDelegateProperty midlgt => $"{midlgt.SignatureFunction?.Name ?? "UNKNOWN"} (MulticastInlineDelegateProperty)", FArrayProperty array => $"TArray<{GetPrefix(array.Inner.GetType().Name)}{GetPropertyType(array.Inner)}{(array.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || property.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || array.PropertyFlags.HasFlag(EPropertyFlags.ContainsInstancedReference) || GetPropertyProperty(array.Inner.GetType().Name) ? "*" : string.Empty)}>", - _ => GetUnknownFieldType(property) + _ => GetPropertyType((object)property) }; } + public static bool GetPropertyProperty(object? property) { if (property is null) @@ -131,21 +98,11 @@ public static class KismetExtensions return property switch { - FObjectProperty objct => true, + FObjectProperty => true, _ => false }; } - public static bool GetPropertyProperty(FProperty? property) - { - if (property is null) - return false; - return property switch - { - FObjectProperty objct => true, - _ => false - }; - } public static string FormatStructFallback(FStructFallback fallback) { if (fallback.Properties.Count == 0) @@ -155,44 +112,48 @@ public static class KismetExtensions { string tagDataFormatted; - if (tag.Tag is TextProperty text) + switch (tag.Tag) { - tagDataFormatted = $"\"{text.Value.Text}\""; - } - else if (tag.Tag is NameProperty name) - { - tagDataFormatted = $"\"{name.Value.Text}\""; - } - else if (tag.Tag is ObjectProperty obj) - { - tagDataFormatted = $"\"{obj.Value}\""; - } - else if (tag.Tag.GenericValue is FScriptStruct innerStruct && innerStruct.StructType is FStructFallback nestedFallback) - { - if (nestedFallback.Properties.Count > 0) + case TextProperty text: + tagDataFormatted = $"\"{text.Value.Text}\""; + break; + case NameProperty name: + tagDataFormatted = $"\"{name.Value.Text}\""; + break; + case ObjectProperty obj: + tagDataFormatted = $"\"{obj.Value}\""; + break; + default: { - tagDataFormatted = "{ " + string.Join(", ", - nestedFallback.Properties.Select(nested => + if (tag.Tag.GenericValue is FScriptStruct { StructType: FStructFallback nestedFallback }) + { + if (nestedFallback.Properties.Count > 0) { - string nestedVal; - if (nested.Tag is TextProperty textProp) - nestedVal = $"\"{textProp.Value.Text}\""; - else if (nested.Tag is NameProperty nameProp) - nestedVal = $"\"{nameProp.Value.Text}\""; - else - nestedVal = $"\"{nested.Tag.GenericValue}\""; + tagDataFormatted = "{ " + string.Join(", ", + nestedFallback.Properties.Select(nested => + { + string nestedVal = nested.Tag switch + { + TextProperty textProp => $"\"{textProp.Value.Text}\"", + NameProperty nameProp => $"\"{nameProp.Value.Text}\"", + _ => $"\"{nested.Tag.GenericValue}\"" + }; - return $"\"{nested.Name}\": {nestedVal}"; - })) + " }"; + return $"\"{nested.Name}\": {nestedVal}"; + })) + " }"; + } + else + { + tagDataFormatted = "{}"; + } + } + else + { + tagDataFormatted = tag.Tag.GenericValue != null ? $"\"{tag.Tag.GenericValue}\"" : "{}"; + } + + break; } - else - { - tagDataFormatted = "{}"; - } - } - else - { - tagDataFormatted = tag.Tag.GenericValue != null ? $"\"{tag.Tag.GenericValue}\"" : "{}"; } return $"\t\t{{ \"{tag.Name}\": {tagDataFormatted} }}"; @@ -200,6 +161,7 @@ public static class KismetExtensions return "[\n" + string.Join(",\n", tags) + "\n\t]"; } + public static string FormatGameplayTagContainer(FGameplayTagContainer container) { var tags = container.GameplayTags.ToList(); @@ -210,49 +172,31 @@ public static class KismetExtensions _ => "[\n" + string.Join(",\n", tags.Select(tag => $"\t\t\"{tag.TagName}\"")) + "\n\t]" }; } + public static string FormatStructType(object structType) { - switch (structType) + return structType switch { - case FVector vector: - return $"FVector({vector.X}, {vector.Y}, {vector.Z})"; - case FVector2D vector2D: - return $"FVector2D({vector2D.X}, {vector2D.Y})"; - case FRotator rotator: - return $"FRotator({rotator.Pitch}, {rotator.Yaw}, {rotator.Roll})"; - case FQuat quat: - return $"FQuat({quat.X}, {quat.Y}, {quat.Z}, {quat.W})"; - case FGuid guid: - return $"FGuid({guid.A}, {guid.B}, {guid.C}, {guid.D})"; - case FColor color: - return $"FColor({color.R}, {color.G}, {color.B}, {color.A})"; - case FLinearColor linearColor: - return $"FLinearColor({linearColor.R}, {linearColor.G}, {linearColor.B}, {linearColor.A})"; - case FSoftObjectPath path: - return $"FSoftObjectPath({path.AssetPathName})"; - case FUniqueNetIdRepl netId: - return $"FUniqueNetIdRepl({netId.UniqueNetId})"; - case FNavAgentSelector agent: - return $"FNavAgentSelector({agent.PackedBits})"; - case FBox box: - return $"FBox(FVector({box.Max.X}, {box.Max.Y}, {box.Max.Z}), FVector({box.Min.X}, {box.Min.Y}, {box.Min.Z}))"; - case FBox2D box2D: - return $"FBox2D(FVector2D({box2D.Max.X}, {box2D.Max.Y}), FVector2D({box2D.Min.X}, {box2D.Min.Y}))"; - case TIntVector3 intVec: - return $"FVector({intVec.X}, {intVec.Y}, {intVec.Z})"; - case TIntVector3 floatVec: - return $"FVector({floatVec.X}, {floatVec.Y}, {floatVec.Z})"; - case TIntVector2 floatVec2: - return $"FVector2D({floatVec2.X}, {floatVec2.Y})"; - case FDateTime dateTime: - return $"FDateTime({dateTime})"; - case FStructFallback fallback: - return FormatStructFallback(fallback); - case FGameplayTagContainer tagContainer: - return FormatGameplayTagContainer(tagContainer); - default: - return structType?.ToString() ?? "Issue here"; - } + FVector vector => $"FVector({vector.X}, {vector.Y}, {vector.Z})", + FVector2D vector2D => $"FVector2D({vector2D.X}, {vector2D.Y})", + FRotator rotator => $"FRotator({rotator.Pitch}, {rotator.Yaw}, {rotator.Roll})", + FQuat quat => $"FQuat({quat.X}, {quat.Y}, {quat.Z}, {quat.W})", + FGuid guid => $"FGuid({guid.A}, {guid.B}, {guid.C}, {guid.D})", + FColor color => $"FColor({color.R}, {color.G}, {color.B}, {color.A})", + FLinearColor linearColor => $"FLinearColor({linearColor.R}, {linearColor.G}, {linearColor.B}, {linearColor.A})", + FSoftObjectPath path => $"FSoftObjectPath({path.AssetPathName})", + FUniqueNetIdRepl netId => $"FUniqueNetIdRepl({netId.UniqueNetId})", + FNavAgentSelector agent => $"FNavAgentSelector({agent.PackedBits})", + FBox box => $"FBox(FVector({box.Max.X}, {box.Max.Y}, {box.Max.Z}), FVector({box.Min.X}, {box.Min.Y}, {box.Min.Z}))", + FBox2D box2D => $"FBox2D(FVector2D({box2D.Max.X}, {box2D.Max.Y}), FVector2D({box2D.Min.X}, {box2D.Min.Y}))", + TIntVector3 intVec => $"FVector({intVec.X}, {intVec.Y}, {intVec.Z})", + TIntVector3 floatVec => $"FVector({floatVec.X}, {floatVec.Y}, {floatVec.Z})", + TIntVector2 floatVec2 => $"FVector2D({floatVec2.X}, {floatVec2.Y})", + FDateTime dateTime => $"FDateTime({dateTime})", + FStructFallback fallback => FormatStructFallback(fallback), + FGameplayTagContainer tagContainer => FormatGameplayTagContainer(tagContainer), + _ => structType?.ToString() ?? "Issue here" + }; } private static string ProcessTextProperty(FKismetPropertyPointer property, bool temp) @@ -263,12 +207,14 @@ public static class KismetExtensions } return string.Join('.', property.New.Path.Select(n => n.Text)).Replace(" ", ""); } + public static void ProcessExpression(EExprToken token, KismetExpression expression, StringBuilder outputBuilder, List jumpCodeOffsets, bool isParameter = false) { if (jumpCodeOffsets.Contains(expression.StatementIndex)) { outputBuilder.Append("\t\tLabel_" + expression.StatementIndex + ":\n"); } + switch (token) { case EExprToken.EX_LetValueOnPersistentFrame: @@ -406,8 +352,7 @@ public static class KismetExtensions { ProcessExpression(oppMath.Token, oppMath, outputBuilder, jumpCodeOffsets, true); } - else - {} + break; } case EExprToken.EX_PopExecutionFlowIfNot: @@ -423,7 +368,7 @@ public static class KismetExtensions { EX_Cast op = (EX_Cast) expression;// support CST_ObjectToInterface when I have an example of how it works - if (ECastToken.CST_ObjectToBool == op.ConversionType || ECastToken.CST_InterfaceToBool == op.ConversionType) + if (op.ConversionType is ECastToken.CST_ObjectToBool or ECastToken.CST_InterfaceToBool) { outputBuilder.Append("(bool)"); } @@ -455,7 +400,7 @@ public static class KismetExtensions } outputBuilder.Append(op.Elements.Length < 1 ? " " : ' '); - outputBuilder.Append("}"); + outputBuilder.Append('}'); break; } case EExprToken.EX_SetArray: @@ -575,8 +520,8 @@ public static class KismetExtensions { EX_SwitchValue op = (EX_SwitchValue) expression; - bool useTernary = op.Cases.Length <= 2 - && op.Cases.All(c => c.CaseIndexValueTerm.Token == EExprToken.EX_True || c.CaseIndexValueTerm.Token == EExprToken.EX_False); + bool useTernary = op.Cases.Length <= 2 && + op.Cases.All(c => c.CaseIndexValueTerm.Token is EExprToken.EX_True or EExprToken.EX_False); if (useTernary) { @@ -644,9 +589,9 @@ public static class KismetExtensions { EX_ArrayGetByRef op = (EX_ArrayGetByRef) expression; // FortniteGame/Plugins/GameFeatures/FM/PilgrimCore/Content/Player/Components/BP_PilgrimPlayerControllerComponent.uasset ProcessExpression(op.ArrayVariable.Token, op.ArrayVariable, outputBuilder, jumpCodeOffsets, true); - outputBuilder.Append("["); + outputBuilder.Append('['); ProcessExpression(op.ArrayIndex.Token, op.ArrayIndex, outputBuilder, jumpCodeOffsets); - outputBuilder.Append("]"); + outputBuilder.Append(']'); break; } case EExprToken.EX_MetaCast: @@ -658,14 +603,14 @@ public static class KismetExtensions EX_CastBase op = (EX_CastBase) expression; outputBuilder.Append($"Cast(");// m? ProcessExpression(op.Target.Token, op.Target, outputBuilder, jumpCodeOffsets, true); - outputBuilder.Append(")"); + outputBuilder.Append(')'); break; } case EExprToken.EX_StructConst: { EX_StructConst op = (EX_StructConst) expression; outputBuilder.Append($"{GetPrefix(op.Struct.GetType().Name)}{op.Struct.Name}"); - outputBuilder.Append($"("); + outputBuilder.Append('('); for (int i = 0; i < op.Properties.Length; i++) { var property = op.Properties[i]; @@ -673,19 +618,19 @@ public static class KismetExtensions if (i < op.Properties.Length - 1 && property.Token != EExprToken.EX_ArrayConst) outputBuilder.Append(", "); } - outputBuilder.Append($")"); + outputBuilder.Append(')'); break; } case EExprToken.EX_ObjectConst: { EX_ObjectConst op = (EX_ObjectConst) expression; - outputBuilder.Append(!isParameter ? "\t\tFindObject<" : outputBuilder.ToString().EndsWith("\n") ? "\t\tFindObject<" : "FindObject<"); // please don't complain, i know this is bad but i MUST do it. - string classString = op?.Value?.ResolvedObject?.Class?.ToString()?.Replace("'", ""); + outputBuilder.Append(!isParameter ? "\t\tFindObject<" : outputBuilder.ToString().EndsWith('\n') ? "\t\tFindObject<" : "FindObject<"); // please don't complain, i know this is bad but i MUST do it. + string classString = op.Value.ResolvedObject?.Class?.ToString().Replace("'", ""); - if (classString?.Contains(".") == true) + if (classString?.Contains('.') == true) { - outputBuilder.Append(GetPrefix(op?.Value?.ResolvedObject?.Class?.GetType().Name) + classString.Split(".")[1]); + outputBuilder.Append(GetPrefix(op?.Value?.ResolvedObject?.Class?.GetType().Name) + classString.Split('.')[1]); } else { @@ -698,15 +643,7 @@ public static class KismetExtensions var name = op?.Value?.Name ?? string.Empty; outputBuilder.Append(outerString.Replace(outerClassString, "") + "." + name); - - if (isParameter) - { - outputBuilder.Append("\")"); - } - else - { - outputBuilder.Append("\")"); - } + outputBuilder.Append("\")"); break; } case EExprToken.EX_BindDelegate: @@ -714,23 +651,23 @@ public static class KismetExtensions EX_BindDelegate op = (EX_BindDelegate) expression; outputBuilder.Append("\t\t"); ProcessExpression(op.Delegate.Token, op.Delegate, outputBuilder, jumpCodeOffsets); - outputBuilder.Append($".BindUFunction("); + outputBuilder.Append(".BindUFunction("); ProcessExpression(op.ObjectTerm.Token, op.ObjectTerm, outputBuilder, jumpCodeOffsets); outputBuilder.Append($", \"{op.FunctionName}\""); - outputBuilder.Append($");\n\n"); + outputBuilder.Append(");\n\n"); break; } // all the delegate functions suck case EExprToken.EX_AddMulticastDelegate: { EX_AddMulticastDelegate op = (EX_AddMulticastDelegate) expression; - if (op.Delegate.Token == EExprToken.EX_LocalVariable || op.Delegate.Token == EExprToken.EX_InstanceVariable) + if (op.Delegate.Token is EExprToken.EX_LocalVariable or EExprToken.EX_InstanceVariable) { outputBuilder.Append("\t\t"); ProcessExpression(op.Delegate.Token, op.Delegate, outputBuilder, jumpCodeOffsets, true); outputBuilder.Append(".AddDelegate("); ProcessExpression(op.DelegateToAdd.Token, op.DelegateToAdd, outputBuilder, jumpCodeOffsets); - outputBuilder.Append($");\n\n"); + outputBuilder.Append(");\n\n"); } else if (op.Delegate.Token != EExprToken.EX_Context) {} @@ -743,23 +680,25 @@ public static class KismetExtensions //ProcessExpression(opp.ContextExpression.Token, opp.ContextExpression, outputBuilder, jumpCodeOffsets); outputBuilder.Append(".AddDelegate("); ProcessExpression(op.DelegateToAdd.Token, op.DelegateToAdd, outputBuilder, jumpCodeOffsets); - outputBuilder.Append($");\n\n"); + outputBuilder.Append(");\n\n"); } break; } case EExprToken.EX_RemoveMulticastDelegate: // everything here has been guessed not compared to actual UE but does work fine and displays all information { EX_RemoveMulticastDelegate op = (EX_RemoveMulticastDelegate) expression; - if (op.Delegate.Token == EExprToken.EX_LocalVariable || op.Delegate.Token == EExprToken.EX_InstanceVariable) + if (op.Delegate.Token is EExprToken.EX_LocalVariable or EExprToken.EX_InstanceVariable) { outputBuilder.Append("\t\t"); ProcessExpression(op.Delegate.Token, op.Delegate, outputBuilder, jumpCodeOffsets, true); outputBuilder.Append(".RemoveDelegate("); ProcessExpression(op.DelegateToAdd.Token, op.DelegateToAdd, outputBuilder, jumpCodeOffsets); - outputBuilder.Append($");\n\n"); + outputBuilder.Append(");\n\n"); } else if (op.Delegate.Token != EExprToken.EX_Context) - {} + { + + } else { EX_Context opp = (EX_Context) op.Delegate; @@ -769,7 +708,7 @@ public static class KismetExtensions ProcessExpression(opp.ContextExpression.Token, opp.ContextExpression, outputBuilder, jumpCodeOffsets); outputBuilder.Append(".RemoveDelegate("); ProcessExpression(op.DelegateToAdd.Token, op.DelegateToAdd, outputBuilder, jumpCodeOffsets); - outputBuilder.Append($");\n\n"); + outputBuilder.Append(");\n\n"); } break; } @@ -800,10 +739,12 @@ public static class KismetExtensions outputBuilder.Append(", "); } } - outputBuilder.Append($");\n\n"); + outputBuilder.Append(");\n\n"); } else if (op.Delegate.Token != EExprToken.EX_Context) - {} + { + + } else { outputBuilder.Append("\t\t"); @@ -819,7 +760,7 @@ public static class KismetExtensions outputBuilder.Append(", "); } } - outputBuilder.Append($");\n\n"); + outputBuilder.Append(");\n\n"); } break; } @@ -827,7 +768,7 @@ public static class KismetExtensions case EExprToken.EX_Context: { EX_Context op = (EX_Context) expression; - outputBuilder.Append(outputBuilder.ToString().EndsWith("\n") ? "\t\t" : ""); + outputBuilder.Append(outputBuilder.ToString().EndsWith('\n') ? "\t\t" : ""); ProcessExpression(op.ObjectExpression.Token, op.ObjectExpression, outputBuilder, jumpCodeOffsets, true); outputBuilder.Append("->"); @@ -847,7 +788,7 @@ public static class KismetExtensions { outputBuilder.Append("->"); ProcessExpression(op.ContextExpression.Token, op.ContextExpression, outputBuilder, jumpCodeOffsets, true); - outputBuilder.Append($";\n\n"); + outputBuilder.Append(";\n\n"); } break; } @@ -883,11 +824,11 @@ public static class KismetExtensions ProcessExpression(op.Assignment.Token, op.Assignment, outputBuilder, jumpCodeOffsets, true); if (!isParameter || op.Assignment.Token == EExprToken.EX_LocalFinalFunction || op.Assignment.Token == EExprToken.EX_FinalFunction || op.Assignment.Token == EExprToken.EX_CallMath) { - outputBuilder.Append($";\n\n"); + outputBuilder.Append(";\n\n"); } else { - outputBuilder.Append($";"); + outputBuilder.Append(';'); } break; } @@ -941,7 +882,7 @@ public static class KismetExtensions { EX_Return op = (EX_Return) expression; bool check = op.ReturnExpression.Token == EExprToken.EX_Nothing; - outputBuilder.Append($"\t\treturn"); + outputBuilder.Append("\t\treturn"); if (!check) outputBuilder.Append(' '); ProcessExpression(op.ReturnExpression.Token, op.ReturnExpression, outputBuilder, jumpCodeOffsets, true); diff --git a/FModel/Extensions/StringExtensions.cs b/FModel/Extensions/StringExtensions.cs index 63cd033a..359ad0e0 100644 --- a/FModel/Extensions/StringExtensions.cs +++ b/FModel/Extensions/StringExtensions.cs @@ -24,29 +24,21 @@ public static partial class StringExtensions return $"{size:# ###.##} {sizes[order]}".TrimStart(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetNameLineNumberText(this string s, string lineToFind) - { - using var reader = new StringReader(s); - var lineNum = 0; - while (reader.ReadLine() is { } line) - { - lineNum++; - if (line.Contains(lineToFind, StringComparison.OrdinalIgnoreCase)) - return lineNum; - } - return -1; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetNameLineNumber(this string s, string lineToFind) { if (KismetRegex().IsMatch(lineToFind)) return s.GetKismetLineNumber(lineToFind); + if (int.TryParse(lineToFind, out var index)) return s.GetLineNumber(index); - lineToFind = $" \"Name\": \"{lineToFind}\","; + return s.GetNameLineNumberText($" \"Name\": \"{lineToFind}\","); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetNameLineNumberText(this string s, string lineToFind) + { using var reader = new StringReader(s); var lineNum = 0; while (reader.ReadLine() is { } line) @@ -55,7 +47,6 @@ public static partial class StringExtensions if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase)) return lineNum; } - return -1; } diff --git a/FModel/MainWindow.xaml b/FModel/MainWindow.xaml index 428464e2..9021fa43 100644 --- a/FModel/MainWindow.xaml +++ b/FModel/MainWindow.xaml @@ -468,7 +468,7 @@ - + @@ -501,7 +501,7 @@ - + @@ -515,6 +515,15 @@ + + + diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index a678bc44..e1d71bc3 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -165,18 +165,6 @@ public partial class MainWindow await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); }); } - private void AssetsListName_ContextMenuOpening(object sender, ContextMenuEventArgs e) - { - var listBox = sender as ListBox; - var contextMenu = listBox.ContextMenu; - - var decompItem = contextMenu.Items - .OfType() - .FirstOrDefault(mi => mi.Name == "Decompile"); - - decompItem.Visibility = UserSettings.Default.ShowDecompileOption ? Visibility.Visible : Visibility.Collapsed; - } - private async void OnFolderExtractClick(object sender, RoutedEventArgs e) { if (AssetsFolderName.SelectedItem is TreeItem folder) diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 598c4ce9..d203b34f 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -960,15 +960,11 @@ public class CUE4ParseViewModel : ViewModel public void Decompile(GameFile entry) { - var package = Provider.LoadPackage(entry); - - if (TabControl.CanAddTabs) - TabControl.AddTab(entry); - else - TabControl.SelectedTab.SoftReset(entry); + if (TabControl.CanAddTabs) TabControl.AddTab(entry); + else TabControl.SelectedTab.SoftReset(entry); TabControl.SelectedTab.TitleExtra = "Decompiled"; - TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("txt"); + TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("cpp"); var pkg = Provider.LoadPackage(entry); @@ -979,472 +975,247 @@ public class CUE4ParseViewModel : ViewModel if (pointer?.Object is null) continue; - var dummy = ((AbstractUePackage) pkg).ConstructObject( - pointer.Class?.Object?.Value as UStruct, pkg); - switch (dummy) + var dummy = ((AbstractUePackage) pkg).ConstructObject(pointer.Class?.Object?.Value as UStruct, pkg); + if (dummy is not UBlueprintGeneratedClass || pointer.Object.Value is not UBlueprintGeneratedClass blueprint) + continue; + + var type = blueprint.GetType().Name; + var typePrefix = KismetExtensions.GetPrefix(type); + var className = blueprint.Name; + var superClassName = blueprint?.SuperStruct?.Name ?? string.Empty; + + outputBuilder.AppendLine($"class {typePrefix}{className} : public {typePrefix}{superClassName}\n{{\npublic:"); + + if (!blueprint.ClassDefaultObject.TryLoad(out var bpObject)) + continue; + + var strings = new List(); + foreach (var property in bpObject.Properties) { - case UBlueprintGeneratedClass _: - case UVerseClass _: + var propertyName = property.Name.ToString(); + var propertyValue = property.Tag?.GenericValue; + strings.Add(propertyName); + string placeholder = $"{propertyName}placeholder"; + + void ShouldAppend(string value) + { + if (outputBuilder.ToString().Contains(placeholder)) { - var blueprintGeneratedClass = pkg.ExportsLazy.FirstOrDefault(e => e.Value is UBlueprintGeneratedClass)?.Value as UBlueprintGeneratedClass; - var verseClass = pkg.ExportsLazy.Where(export => export.Value is UVerseClass).Select(export => (UVerseClass) export.Value).FirstOrDefault(); - - var stringsarray = new List(); - if (blueprintGeneratedClass != null) - { - { - var mainClass = blueprintGeneratedClass?.Name ?? verseClass?.Name; - var superStructName = blueprintGeneratedClass?.SuperStruct?.Name ?? verseClass?.SuperStruct?.Name ?? string.Empty; - outputBuilder.AppendLine( - $"class {KismetExtensions.GetPrefix(blueprintGeneratedClass?.GetType().Name ?? verseClass?.GetType().Name)}{mainClass} : public {KismetExtensions.GetPrefix(blueprintGeneratedClass?.GetType().Name ?? verseClass?.GetType().Name)}{superStructName}\n{{\npublic:"); - - foreach (var export in pkg.ExportsLazy) - { - if (export.Value is not UBlueprintGeneratedClass) - - if (export.Value.Name.StartsWith("Default__") && - export.Value.Name.EndsWith(mainClass ?? string.Empty)) - { - var exportObject = export.Value; - foreach (var key in exportObject.Properties) - { - stringsarray.Add(key.Name.PlainText); - string placeholder = $"{key.Name}placenolder"; - string result = key.Tag.GenericValue.ToString(); - string keyName = key.Name.PlainText.Replace(" ", ""); - - var propertyTag = key.Tag.GetValue(typeof(object)); - - void ShouldAppend(string? value) - { - if (value == null) - return; - if (outputBuilder.ToString().Contains(placeholder)) - { - outputBuilder.Replace(placeholder, value); - } - else - { - outputBuilder.AppendLine( - $"\t{KismetExtensions.GetPropertyType(propertyTag)} {keyName} = {value};"); - } - } - - if (key.Tag.GenericValue is FScriptStruct structTag) - { - if (structTag.StructType is FVector vector) - { - ShouldAppend( - $"FVector({vector.X}, {vector.Y}, {vector.Z})"); - } - else if (structTag.StructType is FGuid guid) - { - ShouldAppend( - $"FGuid({guid.A}, {guid.B}, {guid.C}, {guid.D})"); - } - else if (structTag.StructType is TIntVector3 vector3) - { - ShouldAppend( - $"FVector({vector3.X}, {vector3.Y}, {vector3.Z})"); - } - else if (structTag.StructType is TIntVector3 - floatVector3) - { - ShouldAppend( - $"FVector({floatVector3.X}, {floatVector3.Y}, {floatVector3.Z})"); - } - else if (structTag.StructType is TIntVector2 - floatVector2) - { - ShouldAppend( - $"FVector2D({floatVector2.X}, {floatVector2.Y})"); - } - else if (structTag.StructType is FVector2D vector2d) - { - ShouldAppend($"FVector2D({vector2d.X}, {vector2d.Y})"); - } - else if (structTag.StructType is FRotator rotator) - { - ShouldAppend( - $"FRotator({rotator.Pitch}, {rotator.Yaw}, {rotator.Roll})"); - } - else if (structTag.StructType is FStructFallback fallback) - { - string formattedTags; - if (fallback.Properties.Count > 0) - { - formattedTags = "[\n" + string.Join(",\n", - fallback.Properties.Select(tag => - { - string tagDataFormatted; - if (tag.Tag is TextProperty text) - { - tagDataFormatted = $"\"{text.Value.Text}\""; - } - else if (tag.Tag is NameProperty name) - { - tagDataFormatted = $"\"{name.Value.Text}\""; - } - else if (tag.Tag is ObjectProperty - objectproperty) - { - tagDataFormatted = - $"\"{objectproperty.Value}\""; - } - else if (tag.Tag.GenericValue is FScriptStruct innerStruct && innerStruct.StructType is FStructFallback nestedFallback) - { - if (nestedFallback.Properties.Count > 0) - { - tagDataFormatted = "{ " + string.Join(", ", - nestedFallback.Properties.Select(nested => - { - string nestedVal; - if (nested.Tag is TextProperty textProp) - nestedVal = $"\"{textProp.Value.Text}\""; - else if (nested.Tag is NameProperty nameProp) - nestedVal = $"\"{nameProp.Value.Text}\""; - else - nestedVal = $"\"{nested.Tag.GenericValue}\""; - - return $"\"{nested.Name}\": {nestedVal}"; - })) + " }"; - } - else - { - tagDataFormatted = "{}"; - } - } - else - { - tagDataFormatted = tag.Tag.GenericValue != null ? tag.Tag.GenericValue.ToString() : "{}"; - } - ; - - return $"\t\t{{ \"{tag.Name}\": {tagDataFormatted} }}"; - })) + "\n\t]"; - } - else - { - formattedTags = "[]"; - } - - ShouldAppend(formattedTags); - } - else if (structTag.StructType is FGameplayTagContainer - gameplayTag) - { - var tags = gameplayTag.GameplayTags.ToList(); - if (tags.Count > 1) - { - var formattedTags = "[\n" + string.Join(",\n", - tags.Select(tag => - $"\t\t\"{tag.TagName}\"")) + - "\n\t]"; - ShouldAppend(formattedTags); - } - else if (tags.Any()) - { - ShouldAppend($"\"{tags.First().TagName}\""); - } - else - { - ShouldAppend("[]"); - } - } - else if (structTag.StructType is FLinearColor color) - { - ShouldAppend( - $"FLinearColor({color.R}, {color.G}, {color.B}, {color.A})"); - } - else - { - ShouldAppend($"\"{result}\""); - } - } - else if (key.Tag.GetType().Name == "ObjectProperty" || - key.Tag.GetType().Name == "TextProperty" || - key.PropertyType == "StrProperty" || - key.PropertyType == "NameProperty" || - key.PropertyType == "ClassProperty") - { - ShouldAppend($"\"{result}\""); - } - else if (key.Tag.GenericValue is UScriptSet set) - { - var formattedSet = "[\n" + string.Join(",\n", - set.Properties.Select(p => - $"\t\"{p.GenericValue}\"")) + - "\n\t]"; - ShouldAppend(formattedSet); - } - else if (key.Tag.GenericValue is UScriptMap map) - { - var formattedMap = "[\n" + string.Join(",\n", - map.Properties.Select(kvp => - $"\t{{\n\t\t\"{kvp.Key}\": \"{kvp.Value}\"\n\t}}")) + - "\n\t]"; - ShouldAppend(formattedMap); - } - else if (key.Tag.GenericValue is UScriptArray array) - { - var formattedArray = "[\n" + string.Join(",\n", - array.Properties.Select(p => - { - if (p.GenericValue is FScriptStruct vectorInArray && - vectorInArray.StructType is FVector vector) - { - return - $"FVector({vector.X}, {vector.Y}, {vector.Z})"; - } - - if (p.GenericValue is FScriptStruct - vector2dInArray && - vector2dInArray - .StructType is FVector2D vector2d) - { - return $"FVector2D({vector2d.X}, {vector2d.Y})"; - } - - if (p.GenericValue is FScriptStruct structInArray && - structInArray.StructType is FRotator rotator) - { - return - $"FRotator({rotator.Pitch}, {rotator.Yaw}, {rotator.Roll})"; - } - else if - (p.GenericValue is FScriptStruct - fallbacksInArray && - fallbacksInArray.StructType is FStructFallback - fallback) - { - string formattedTags; - if (fallback.Properties.Count > 0) - { - formattedTags = "\t[\n" + string.Join(",\n", - fallback.Properties.Select(tag => - { - string tagDataFormatted; - if (tag.Tag is TextProperty text) - { - tagDataFormatted = - $"\"{text.Value.Text}\""; - } - else if (tag.Tag is NameProperty - name) - { - tagDataFormatted = - $"\"{name.Value.Text}\""; - } - else if (tag.Tag is ObjectProperty - objectproperty) - { - tagDataFormatted = - $"\"{objectproperty.Value}\""; - } - else - { - tagDataFormatted = - $"\"{tag.Tag.GenericValue}\""; - } - - return - $"\t\t\"{tag.Name}\": {tagDataFormatted}"; - })) + "\n\t]"; - } - else - { - formattedTags = "{}"; - } - - return formattedTags; - } - else if - (p.GenericValue is FScriptStruct - gameplayTagsInArray && - gameplayTagsInArray.StructType is - FGameplayTagContainer gameplayTag) - { - var tags = gameplayTag.GameplayTags.ToList(); - if (tags.Count > 1) - { - var formattedTags = - "[\n" + string.Join(",\n", - tags.Select(tag => - $"\t\t\"{tag.TagName}\"")) + - "\n\t]"; - return formattedTags; - } - else - { - return $"\"{tags.First().TagName}\""; - } - } - - return $"\t\t\"{p.GenericValue}\""; - })) + "\n\t]"; - ShouldAppend(formattedArray); - } - else if (key.Tag.GenericValue is FMulticastScriptDelegate multicast) - { - var list = multicast.InvocationList; - ShouldAppend(list.Length == 0 ? "[]" : $"[{string.Join(", ", list.Select(x => $"\"{x.FunctionName}\""))}]"); - } - - else if (key.Tag.GenericValue is bool boolResult) - { - ShouldAppend(boolResult.ToString().ToLower()); - } - else - { - ShouldAppend(result); - } - } - } - else - { - //outputBuilder.Append($"\nType: {export.Value.Name}"); - } - } - } - - var childProperties = blueprintGeneratedClass?.ChildProperties ?? - verseClass?.ChildProperties; - if (childProperties != null) - { - foreach (FProperty property in childProperties) - { - if (!stringsarray.Contains(property.Name.PlainText)) - outputBuilder.AppendLine( - $"\t{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || property.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || KismetExtensions.GetPropertyProperty(property) ? "*" : string.Empty)} {property.Name.PlainText.Replace(" ", "")} = {property.Name.PlainText.Replace(" ", "")}placenolder;"); - } - } - var funcMapOrder = - blueprintGeneratedClass?.FuncMap?.Keys.Select(fname => fname.ToString()) - .ToList() ?? verseClass?.FuncMap.Keys.Select(fname => fname.ToString()) - .ToList(); - var functions = pkg.ExportsLazy - .Where(e => e.Value is UFunction) - .Select(e => (UFunction) e.Value) - .OrderBy(f => - { - if (funcMapOrder != null) - { - var functionName = f.Name.ToString(); - int indexx = funcMapOrder.IndexOf(functionName); - return indexx >= 0 ? indexx : int.MaxValue; - } - - return int.MaxValue; - }) - .ThenBy(f => f.Name.ToString()) - .ToList(); - - var jumpCodeOffsetsMap = new Dictionary>(); - - foreach (var function in functions.AsEnumerable().Reverse()) - { - if (function?.ScriptBytecode == null) - continue; - - foreach (var property in function.ScriptBytecode) - { - string? label = null; - int? offset = null; - - switch (property.Token) - { - case EExprToken.EX_JumpIfNot: - label = ((EX_JumpIfNot) property).ObjectPath?.ToString()?.Split('.').Last().Split('[')[0]; - offset = (int) ((EX_JumpIfNot) property).CodeOffset; - break; - - case EExprToken.EX_Jump: - label = ((EX_Jump) property).ObjectPath?.ToString()?.Split('.').Last().Split('[')[0]; - offset = (int) ((EX_Jump) property).CodeOffset; - break; - - case EExprToken.EX_LocalFinalFunction: - { - EX_FinalFunction op = (EX_FinalFunction) property; - label = op.StackNode?.Name?.ToString()?.Split('.').Last().Split('[')[0]; - - if (op.Parameters.Length == 1 && op.Parameters[0] is EX_IntConst intConst) - offset = intConst.Value; - break; - } - } - - if (!string.IsNullOrEmpty(label) && offset.HasValue) - { - if (!jumpCodeOffsetsMap.TryGetValue(label, out var list)) - jumpCodeOffsetsMap[label] = list = new List(); - - list.Add(offset.Value); - } - } - } - - - - foreach (var function in functions) - { - string argsList = ""; - string returnFunc = "void"; - if (function?.ChildProperties != null) - { - foreach (FProperty property in function.ChildProperties) - { - if (property.Name.PlainText == "ReturnValue") - { - returnFunc = - $"{(property.PropertyFlags.HasFlag(EPropertyFlags.ConstParm) ? "const " : string.Empty)}{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || KismetExtensions.GetPrefix(property.GetType().Name) == "U" ? "*" : string.Empty)}"; - } - else if (!(property.Name.ToString().EndsWith("_ReturnValue") || - property.Name.ToString().StartsWith("CallFunc_") || - property.Name.ToString().StartsWith("K2Node_") || - property.Name.ToString() - .StartsWith("Temp_")) || // removes useless args - property.PropertyFlags.HasFlag(EPropertyFlags.Edit)) - { - argsList += - $"{(property.PropertyFlags.HasFlag(EPropertyFlags.ConstParm) ? "const " : string.Empty)}{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || KismetExtensions.GetPrefix(property.GetType().Name) == "U" ? "*" : string.Empty)}{(property.PropertyFlags.HasFlag(EPropertyFlags.OutParm) ? "&" : string.Empty)} {Regex.Replace(property.Name.ToString(), @"^__verse_0x[0-9A-Fa-f]+_", "")}, "; - } - } - } - argsList = argsList.TrimEnd(',', ' '); - - outputBuilder.AppendLine($"\n\t{returnFunc} {function.Name.Replace(" ", "")}({argsList})\n\t{{"); - if (function?.ScriptBytecode != null) - { - var jumpCodeOffsets = jumpCodeOffsetsMap.TryGetValue(function.Name, out var list) ? list : new List(); - foreach (KismetExpression property in function.ScriptBytecode) - { - KismetExtensions.ProcessExpression(property.Token, property, outputBuilder, jumpCodeOffsets); - } - } - else - { - outputBuilder.Append( - "\n\t // This function does not have Bytecode \n\n"); - outputBuilder.Append("\t}\n"); - } - } - - outputBuilder.Append("\n\n}"); - } - else - { - continue; - } - break; + outputBuilder.Replace(placeholder, value); } + else + { + outputBuilder.AppendLine($"\t{KismetExtensions.GetPropertyType(propertyValue)} {propertyName.Replace(" ", "")} = {value};"); + } + } + + string GetLineOfText(object value) + { + string? text = null; + switch (value) + { + case FScriptStruct structTag: + switch (structTag.StructType) + { + case FVector vector: + text = $"FVector({vector.X}, {vector.Y}, {vector.Z})"; + break; + case FGuid guid: + text = $"FGuid({guid.A}, {guid.B}, {guid.C}, {guid.D})"; + break; + case TIntVector3 vector3: + text = $"FVector({vector3.X}, {vector3.Y}, {vector3.Z})"; + break; + case TIntVector3 floatVector3: + text = $"FVector({floatVector3.X}, {floatVector3.Y}, {floatVector3.Z})"; + break; + case TIntVector2 floatVector2: + text = $"FVector2D({floatVector2.X}, {floatVector2.Y})"; + break; + case FVector2D vector2d: + text = $"FVector2D({vector2d.X}, {vector2d.Y})"; + break; + case FRotator rotator: + text = $"FRotator({rotator.Pitch}, {rotator.Yaw}, {rotator.Roll})"; + break; + case FLinearColor linearColor: + text = $"FLinearColor({linearColor.R}, {linearColor.G}, {linearColor.B}, {linearColor.A})"; + break; + case FGameplayTagContainer gTag: + text = gTag.GameplayTags.Length switch + { + > 1 => "[\n" + string.Join(",\n", gTag.GameplayTags.Select(tag => $"\t\t\"{tag.TagName}\"")) + "\n\t]", + > 0 => $"\"{gTag.GameplayTags[0].TagName}\"", + _ => "[]" + }; + break; + case FStructFallback fallback: + if (fallback.Properties.Count > 0) + { + text = "[\n" + string.Join(",\n", fallback.Properties.Select(p => $"\t\"{GetLineOfText(p)}\"")) + "\n\t]"; + } + else + { + text = "[]"; + } + break; + } + break; + case UScriptSet: + case UScriptMap: + case UScriptArray: + IEnumerable inner = value switch + { + UScriptSet set => set.Properties.Select(p => $"\t\"{p.GenericValue}\""), + UScriptMap map => map.Properties.Select(kvp => $"\t{{\n\t\t\"{kvp.Key}\": \"{kvp.Value}\"\n\t}}"), + UScriptArray array => array.Properties.Select(p => $"\t\"{GetLineOfText(p)}\""), + _ => throw new ArgumentOutOfRangeException(nameof(value), value, null) + }; + + text = "[\n" + string.Join(",\n", inner) + "\n\t]"; + break; + case FMulticastScriptDelegate multicast: + text = multicast.InvocationList.Length == 0 ? "[]" : $"[{string.Join(", ", multicast.InvocationList.Select(x => $"\"{x.FunctionName}\""))}]"; + break; + case bool: + text = value.ToString()?.ToLowerInvariant(); + break; + } + + return text ?? value.ToString(); + } + + ShouldAppend(GetLineOfText(propertyValue)); + } + + { + var childProperties = blueprint?.ChildProperties; + if (childProperties != null) + { + foreach (FProperty property in childProperties) + { + if (!strings.Contains(property.Name.PlainText)) + outputBuilder.AppendLine( + $"\t{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || property.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || KismetExtensions.GetPropertyProperty(property) ? "*" : string.Empty)} {property.Name.PlainText.Replace(" ", "")} = {property.Name.PlainText.Replace(" ", "")}placeholder;"); + } + } + + var funcMapOrder = blueprint?.FuncMap?.Keys.Select(fname => fname.ToString()).ToList(); + var functions = pkg.ExportsLazy + .Where(e => e.Value is UFunction) + .Select(e => (UFunction) e.Value) + .OrderBy(f => + { + if (funcMapOrder != null) + { + var functionName = f.Name.ToString(); + int indexx = funcMapOrder.IndexOf(functionName); + return indexx >= 0 ? indexx : int.MaxValue; + } + + return int.MaxValue; + }) + .ThenBy(f => f.Name.ToString()) + .ToList(); + + var jumpCodeOffsetsMap = new Dictionary>(); + + foreach (var function in functions.AsEnumerable().Reverse()) + { + if (function?.ScriptBytecode == null) + continue; + + foreach (var property in function.ScriptBytecode) + { + string? label = null; + int? offset = null; + + switch (property.Token) + { + case EExprToken.EX_JumpIfNot: + label = ((EX_JumpIfNot) property).ObjectPath?.ToString()?.Split('.').Last().Split('[')[0]; + offset = (int) ((EX_JumpIfNot) property).CodeOffset; + break; + + case EExprToken.EX_Jump: + label = ((EX_Jump) property).ObjectPath?.ToString()?.Split('.').Last().Split('[')[0]; + offset = (int) ((EX_Jump) property).CodeOffset; + break; + + case EExprToken.EX_LocalFinalFunction: + { + EX_FinalFunction op = (EX_FinalFunction) property; + label = op.StackNode?.Name?.ToString()?.Split('.').Last().Split('[')[0]; + + if (op.Parameters.Length == 1 && op.Parameters[0] is EX_IntConst intConst) + offset = intConst.Value; + break; + } + } + + if (!string.IsNullOrEmpty(label) && offset.HasValue) + { + if (!jumpCodeOffsetsMap.TryGetValue(label, out var list)) + jumpCodeOffsetsMap[label] = list = new List(); + + list.Add(offset.Value); + } + } + } + + + + foreach (var function in functions) + { + string argsList = ""; + string returnFunc = "void"; + if (function?.ChildProperties != null) + { + foreach (FProperty property in function.ChildProperties) + { + if (property.Name.PlainText == "ReturnValue") + { + returnFunc = + $"{(property.PropertyFlags.HasFlag(EPropertyFlags.ConstParm) ? "const " : string.Empty)}{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || KismetExtensions.GetPrefix(property.GetType().Name) == "U" ? "*" : string.Empty)}"; + } + else if (!(property.Name.ToString().EndsWith("_ReturnValue") || + property.Name.ToString().StartsWith("CallFunc_") || + property.Name.ToString().StartsWith("K2Node_") || + property.Name.ToString() + .StartsWith("Temp_")) || // removes useless args + property.PropertyFlags.HasFlag(EPropertyFlags.Edit)) + { + argsList += + $"{(property.PropertyFlags.HasFlag(EPropertyFlags.ConstParm) ? "const " : string.Empty)}{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || KismetExtensions.GetPrefix(property.GetType().Name) == "U" ? "*" : string.Empty)}{(property.PropertyFlags.HasFlag(EPropertyFlags.OutParm) ? "&" : string.Empty)} {Regex.Replace(property.Name.ToString(), @"^__verse_0x[0-9A-Fa-f]+_", "")}, "; + } + } + } + argsList = argsList.TrimEnd(',', ' '); + + outputBuilder.AppendLine($"\n\t{returnFunc} {function.Name.Replace(" ", "")}({argsList})\n\t{{"); + if (function?.ScriptBytecode != null) + { + var jumpCodeOffsets = jumpCodeOffsetsMap.TryGetValue(function.Name, out var list) ? list : new List(); + foreach (KismetExpression property in function.ScriptBytecode) + { + KismetExtensions.ProcessExpression(property.Token, property, outputBuilder, jumpCodeOffsets); + } + } + else + { + outputBuilder.Append("\n\t // This function does not have Bytecode \n\n"); + outputBuilder.Append("\t}\n"); + } + } + + outputBuilder.Append("\n\n}"); } } - string pattern = $@"\w+placenolder"; - string updatedOutput = Regex.Replace(outputBuilder.ToString(), pattern, "nullptr"); - TabControl.SelectedTab.SetDocumentText(updatedOutput, false, false); + var cpp = Regex.Replace(outputBuilder.ToString(), @"\w+placeholder", "nullptr"); + TabControl.SelectedTab.SetDocumentText(cpp, false, false); } + private void SaveAndPlaySound(string fullPath, string ext, byte[] data) { if (fullPath.StartsWith("/")) fullPath = fullPath[1..]; diff --git a/FModel/Views/SettingsView.xaml b/FModel/Views/SettingsView.xaml index 378e71c5..a45da7b0 100644 --- a/FModel/Views/SettingsView.xaml +++ b/FModel/Views/SettingsView.xaml @@ -229,7 +229,7 @@ - + From df3fdbbd2b8ca39ab01d784b7d2ba3b5b0fa4b30 Mon Sep 17 00:00:00 2001 From: Asval Date: Sun, 6 Jul 2025 19:17:57 +0200 Subject: [PATCH 07/25] refactored ChildProperties --- FModel/ViewModels/CUE4ParseViewModel.cs | 40 +++++++++++++++---------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index d203b34f..638707c4 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -979,12 +979,8 @@ public class CUE4ParseViewModel : ViewModel if (dummy is not UBlueprintGeneratedClass || pointer.Object.Value is not UBlueprintGeneratedClass blueprint) continue; - var type = blueprint.GetType().Name; - var typePrefix = KismetExtensions.GetPrefix(type); - var className = blueprint.Name; - var superClassName = blueprint?.SuperStruct?.Name ?? string.Empty; - - outputBuilder.AppendLine($"class {typePrefix}{className} : public {typePrefix}{superClassName}\n{{\npublic:"); + var typePrefix = KismetExtensions.GetPrefix(blueprint.GetType().Name); + outputBuilder.AppendLine($"class {typePrefix}{blueprint.Name} : public {typePrefix}{blueprint?.SuperStruct?.Name ?? string.Empty}\n{{\npublic:"); if (!blueprint.ClassDefaultObject.TryLoad(out var bpObject)) continue; @@ -1011,7 +1007,7 @@ public class CUE4ParseViewModel : ViewModel string GetLineOfText(object value) { - string? text = null; + string text = null; switch (value) { case FScriptStruct structTag: @@ -1088,18 +1084,30 @@ public class CUE4ParseViewModel : ViewModel ShouldAppend(GetLineOfText(propertyValue)); } + foreach (var field in blueprint.ChildProperties) { - var childProperties = blueprint?.ChildProperties; - if (childProperties != null) + if (field is not FProperty property || strings.Contains(property.Name.Text)) continue; + + var propertyName = property.Name.ToString().Replace(" ", ""); + var type = KismetExtensions.GetPropertyType(property); + var prefix = KismetExtensions.GetPrefix(property.GetType().Name); + + string whatever; + if (property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || + property.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || + KismetExtensions.GetPropertyProperty(property)) { - foreach (FProperty property in childProperties) - { - if (!strings.Contains(property.Name.PlainText)) - outputBuilder.AppendLine( - $"\t{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || property.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || KismetExtensions.GetPropertyProperty(property) ? "*" : string.Empty)} {property.Name.PlainText.Replace(" ", "")} = {property.Name.PlainText.Replace(" ", "")}placeholder;"); - } + whatever = "*"; + } + else + { + whatever = string.Empty; } + outputBuilder.AppendLine($"\t{prefix}{type}{whatever} {propertyName} = {propertyName}placeholder;"); + } + + { var funcMapOrder = blueprint?.FuncMap?.Keys.Select(fname => fname.ToString()).ToList(); var functions = pkg.ExportsLazy .Where(e => e.Value is UFunction) @@ -1218,7 +1226,7 @@ public class CUE4ParseViewModel : ViewModel private void SaveAndPlaySound(string fullPath, string ext, byte[] data) { - if (fullPath.StartsWith("/")) fullPath = fullPath[1..]; + if (fullPath.StartsWith('/')) fullPath = fullPath[1..]; var savedAudioPath = Path.Combine(UserSettings.Default.AudioDirectory, UserSettings.Default.KeepDirectoryStructure ? fullPath : fullPath.SubstringAfterLast('/')).Replace('\\', '/') + $".{ext.ToLowerInvariant()}"; From 89c8cbaccc632fa18124b71dc243ca05b6ef15e3 Mon Sep 17 00:00:00 2001 From: Asval Date: Sun, 6 Jul 2025 22:48:55 +0200 Subject: [PATCH 08/25] fixed goto --- .../Controls/Aed/JumpVisualLineText.cs | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/FModel/Views/Resources/Controls/Aed/JumpVisualLineText.cs b/FModel/Views/Resources/Controls/Aed/JumpVisualLineText.cs index 1d8aef57..602e8432 100644 --- a/FModel/Views/Resources/Controls/Aed/JumpVisualLineText.cs +++ b/FModel/Views/Resources/Controls/Aed/JumpVisualLineText.cs @@ -11,17 +11,14 @@ namespace FModel.Views.Resources.Controls; public class JumpVisualLineText : VisualLineText { - private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; - private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; - public delegate void JumpOnClick(string Jump); public event JumpOnClick OnJumpClicked; - private readonly string _Jump; + private readonly string _jump; - public JumpVisualLineText(string Jump, VisualLine parentVisualLine, int length) : base(parentVisualLine, length) + public JumpVisualLineText(string jump, VisualLine parentVisualLine, int length) : base(parentVisualLine, length) { - _Jump = Jump; + _jump = jump; } public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) @@ -38,7 +35,7 @@ public class JumpVisualLineText : VisualLineText return new TextCharacters(text.Text, text.Offset, text.Count, TextRunProperties); } - private bool JumpIsClickable() => !string.IsNullOrEmpty(_Jump) && Keyboard.Modifiers == ModifierKeys.None; + private bool JumpIsClickable() => !string.IsNullOrEmpty(_jump) && Keyboard.Modifiers == ModifierKeys.None; protected override void OnQueryCursor(QueryCursorEventArgs e) { @@ -55,23 +52,21 @@ public class JumpVisualLineText : VisualLineText if (e.Handled || OnJumpClicked == null) return; - OnJumpClicked(_Jump); + OnJumpClicked(_jump); e.Handled = true; } protected override VisualLineText CreateInstance(int length) { - var a = new JumpVisualLineText(_Jump, ParentVisualLine, length); - a.OnJumpClicked += async (Jump) => + var a = new JumpVisualLineText(_jump, ParentVisualLine, length); + a.OnJumpClicked += jump => { - var lineNumber = a.ParentVisualLine.Document.Text.GetNameLineNumberText($"Label_{_Jump}:"); - + var lineNumber = a.ParentVisualLine.Document.Text.GetNameLineNumberText($"\t\tLabel_{jump}:"); if (lineNumber > -1) { var line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber); AvalonEditor.YesWeEditor.Select(line.Offset, line.Length); AvalonEditor.YesWeEditor.ScrollToLine(lineNumber); - return; } }; return a; From a8a5207decce607c03b551d394e693bc71717d53 Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Mon, 7 Jul 2025 07:52:34 +1000 Subject: [PATCH 09/25] Improvements --- FModel/Extensions/KismetExtensions.cs | 40 ++- FModel/Resources/Cpp.xshd | 347 +++++++++--------------- FModel/ViewModels/CUE4ParseViewModel.cs | 55 ++-- 3 files changed, 176 insertions(+), 266 deletions(-) diff --git a/FModel/Extensions/KismetExtensions.cs b/FModel/Extensions/KismetExtensions.cs index 0dde9710..c84f06ed 100644 --- a/FModel/Extensions/KismetExtensions.cs +++ b/FModel/Extensions/KismetExtensions.cs @@ -17,7 +17,7 @@ namespace FModel.Extensions; public static class KismetExtensions { - public static string GetPrefix(string type, string extra = "") + public static string GetPrefix(string type, string extra = "") // todo: implement better handling { return type switch { @@ -79,14 +79,18 @@ public static class KismetExtensions } public static string GetPropertyType(FProperty? property) { - if (property is null) - return "None"; + 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 set => $"TSet<{GetPrefix(set.ElementProp.GetType().Name)}{GetPropertyType(set.ElementProp)}{(set.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || property.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || set.PropertyFlags.HasFlag(EPropertyFlags.ContainsInstancedReference) ? "*" : string.Empty)}>", - FMapProperty map => $"TMap<{GetPrefix(map.ValueProp.GetType().Name)}{GetPropertyType(map.KeyProp)}, {GetPrefix(map.ValueProp.GetType().Name)}{GetPropertyType(map.ValueProp)}{(map.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || property.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || map.PropertyFlags.HasFlag(EPropertyFlags.ContainsInstancedReference) ? "*" : string.Empty)}>", - FArrayProperty array => $"TArray<{GetPrefix(array.Inner.GetType().Name)}{GetPropertyType(array.Inner)}{(array.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || property.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || array.PropertyFlags.HasFlag(EPropertyFlags.ContainsInstancedReference) || GetPropertyProperty(array.Inner.GetType().Name) ? "*" : string.Empty)}>", + 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) ? "*" : "")}>", _ => GetPropertyType((object)property) }; } @@ -224,14 +228,7 @@ public static class KismetExtensions var destination = ProcessTextProperty(op.DestinationProperty, false); var variable = ProcessTextProperty(opp.Variable, false); - if (!isParameter) - { - outputBuilder.Append($"\t\t{(destination.Contains("K2Node_") ? $"UberGraphFrame->{destination}" : destination)} = {variable};\n\n"); // hardcoded but works - } - else - { - outputBuilder.Append($"\t\t{(destination.Contains("K2Node_") ? $"UberGraphFrame->{destination}" : destination)} = {variable}"); - } + outputBuilder.Append($"\t\t{(destination.Contains("K2Node_") ? "UberGraphFrame->" + destination : destination)} = {variable}{(!isParameter ? ";\n\n" : "")}"); // Hardcoded but works break; } case EExprToken.EX_LocalFinalFunction: @@ -366,7 +363,7 @@ public static class KismetExtensions } case EExprToken.EX_Cast: { - EX_Cast op = (EX_Cast) expression;// support CST_ObjectToInterface when I have an example of how it works + EX_Cast op = (EX_Cast) expression; // support CST_ObjectToInterface when I have an example of how it works if (op.ConversionType is ECastToken.CST_ObjectToBool or ECastToken.CST_InterfaceToBool) { @@ -474,7 +471,7 @@ public static class KismetExtensions { var element = op.Elements[i]; outputBuilder.Append(' '); - ProcessExpression(element.Token, element, outputBuilder, jumpCodeOffsets);// sometimes the start of an array is a byte not a variable + ProcessExpression(element.Token, element, outputBuilder, jumpCodeOffsets); // sometimes the start of an array is a byte not a variable if (i < op.Elements.Length - 1) { @@ -499,7 +496,7 @@ public static class KismetExtensions { var element = op.Elements[i]; outputBuilder.Append(' '); - ProcessExpression(element.Token, element, outputBuilder, jumpCodeOffsets, true);// sometimes the start of an array is a byte not a variable + ProcessExpression(element.Token, element, outputBuilder, jumpCodeOffsets, true); // sometimes the start of an array is a byte not a variable if (i < op.Elements.Length - 1) { @@ -624,7 +621,7 @@ public static class KismetExtensions case EExprToken.EX_ObjectConst: { EX_ObjectConst op = (EX_ObjectConst) expression; - outputBuilder.Append(!isParameter ? "\t\tFindObject<" : outputBuilder.ToString().EndsWith('\n') ? "\t\tFindObject<" : "FindObject<"); // please don't complain, i know this is bad but i MUST do it. + outputBuilder.Append(!isParameter ? "\t\tFindObject<" : outputBuilder.ToString().EndsWith('\n') ? "\t\tFindObject<" : "FindObject<"); // please don't complain, I know this is bad but I MUST do it. string classString = op.Value.ResolvedObject?.Class?.ToString().Replace("'", ""); if (classString?.Contains('.') == true) @@ -638,8 +635,8 @@ public static class KismetExtensions } outputBuilder.Append(">(\""); var resolvedObject = op?.Value?.ResolvedObject; - var outerString = resolvedObject?.Outer?.ToString()?.Replace("'", "") ?? "UNKNOWN"; - var outerClassString = resolvedObject?.Class?.ToString()?.Replace("'", "") ?? "UNKNOWN"; + var outerString = resolvedObject?.Outer?.ToString()?.Replace("'", "") ?? "outerUnknown"; + var outerClassString = resolvedObject?.Class?.ToString()?.Replace("'", "") ?? "outerClassUnknown"; var name = op?.Value?.Name ?? string.Empty; outputBuilder.Append(outerString.Replace(outerClassString, "") + "." + name); @@ -866,7 +863,7 @@ public static class KismetExtensions } else { - outputBuilder.Append(op.Value); + outputBuilder.Append(op.Value); // impossible to reach? } } break; @@ -1024,6 +1021,7 @@ public static class KismetExtensions // some here are "useful" and unsupported break; /* + Todo: check what uses these, fortnite has none instances EExprToken.EX_Assert EExprToken.EX_Skip EExprToken.EX_InstrumentationEvent diff --git a/FModel/Resources/Cpp.xshd b/FModel/Resources/Cpp.xshd index 5c3cc352..f02e55c0 100644 --- a/FModel/Resources/Cpp.xshd +++ b/FModel/Resources/Cpp.xshd @@ -1,240 +1,147 @@ - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + - - + + - - BlueprintGeneratedClass - Class - FSoftObjectPath - UScriptArray - NavmeshGeneratorComponent - EndGameComponent - USceneComponent + + + + UCLASS + USTRUCT + UPROPERTY + UFUNCTION + GENERATED_BODY + GENERATED_USTRUCT_BODY + GENERATED_UCLASS_BODY - - bool - char - unsigned - union - virtual - double - float - short - signed - void - class - enum - struct - - - \b(BlueprintGeneratedClass|Class|FSoftObjectPath|UScriptArray|NavmeshGeneratorComponent|EndGameComponent|USceneComponent)\b - - ABP_[A-Za-z0-9_]+_C - - \bF[A-Z][A-Za-z0-9_]*\b - \bU[A-Z][A-Za-z0-9_]*\b - \bA[A-Z][A-Za-z0-9_]*\b - \bE[A-Z][A-Za-z0-9_]*\b - - Label_[0-9]+ - - CallFunc_[A-Za-z0-9_]+ - K2Node_[A-Za-z0-9_]+ - Temp_delegate_Variable(_[0-9]+)? - - ExecuteUbergraph_[A-Za-z0-9_]+ - UserConstructionScript - ReceiveBeginPlay - OnBeginFadeOut - .*__UpdateFunc - .*__FinishedFunc - - goto - nullptr - - FindObject<[A-Za-z0-9_]+> - - "[^"]*" - \b[0-9]+(\.[0-9]+)?\b - [\[\]\{\}] - - UKismetMathLibrary - UKismetSystemLibrary - - [?,.;()\[\]{}+\-/%*<>^=~!&]+ - - - __abstract - __box - __delegate - __gc - __identifier - __nogc - __pin - __property - __sealed - __try_cast - __typeof - __value - __event - __hook - __raise - __unhook - __interface - ref class - ref struct - value class - value struct - interface class - interface struct - enum class - enum struct - delegate - event - property - abstract - override - sealed - generic - where - finally - for each - gcnew - in - initonly - literal - nullptr - - - - this - - - and - and_eq - bitand - bitor - new - not - not_eq - or - or_eq - xor - xor_eq - - - using - namespace - - - friend - - - private - protected - public - const - volatile - static - - - true - false - NULL - - - break - case - catch - const_cast - continue - default - delete - dynamic_cast - else - explicit - export - extern - false - for - friend - goto - if - mutable - namespace - new - operator - private - protected - public - register - reinterpret_cast - return - sizeof - static_cast - template - throw - try - typedef - typeid - typename - using - virtual - volatile - while - - - do - for - while - break - continue - goto return throw - - try - catch - throw - finally - - + + + + void + int + Int8 + Int16 + Int32 + Int64 + uint + UInt16 + UInt32 + UInt64 + float + double + bool + return if else + for + while + do switch case - default break continue - return + namespace + using + typedef + sizeof + new + delete + class + struct + enum + template + typename + const + static + mutable + volatile + override + virtual + explicit + friend + inline + constexpr + + + nullptr + + + + true + True + false + False + NULL + + + + public + protected + private + + + + this + + + + (?<=[A-Za-z0-9_>&\]])&(?=\s*[A-Za-z_<]) + + \bLabel_\d+: + + + \b(0x[0-9a-fA-F]+|[0-9]+(\.[0-9]+)?)\b + + \bU[A-Z][A-Za-z0-9_]*\b(?=::) + [A-Za-z_][A-Za-z0-9_]*\s*(?=\() + + [\[\]\{\}] + + + \b[A-Za-z_][A-Za-z0-9_]*\b(?=<) + + + \b[A-Z][A-Za-z0-9_]*(?:<[^>]+>)?[*&]?(?=\s+[*&]?[A-Za-z_][A-Za-z0-9_]*\s*(=|;|\)|,)) + + + (?<=<)\s*[A-Z][A-Za-z0-9_]*(?:<[^>]+>)?[*&]?\s*(?=[>,]) + + + \b(?<=class\s)[A-Za-z_][A-Za-z0-9_]* + + + \b(?<=public\s)[A-Za-z_][A-Za-z0-9_]* + + + \b(?:T|F|U|E)[A-Z][A-Za-z0-9_]*(?:<[^>]+>)?[*&]?(?=\s+[*&]?[A-Za-z_][A-Za-z0-9_]*\s*(?:=|,|\))) + \b(?<=[,(]\s*const\s)(?:T|F|U)[A-Z][A-Za-z0-9_]*[*&]?(?=\s) + + + \b(?<=\()\s*[TUF][A-Z][A-Za-z0-9_]*(?=\s*<) + \b(?<=\()\s*[TUF][A-Z][A-Za-z0-9_]*[*&]?(?=\s) + \b(?<=\(\s*const\s)[TUF][A-Z][A-Za-z0-9_]*[*&]?(?=\s) + diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 638707c4..db0b099f 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -976,7 +976,7 @@ public class CUE4ParseViewModel : ViewModel continue; var dummy = ((AbstractUePackage) pkg).ConstructObject(pointer.Class?.Object?.Value as UStruct, pkg); - if (dummy is not UBlueprintGeneratedClass || pointer.Object.Value is not UBlueprintGeneratedClass blueprint) + if (dummy is not UClass || pointer.Object.Value is not UClass blueprint) continue; var typePrefix = KismetExtensions.GetPrefix(blueprint.GetType().Name); @@ -991,7 +991,7 @@ public class CUE4ParseViewModel : ViewModel var propertyName = property.Name.ToString(); var propertyValue = property.Tag?.GenericValue; strings.Add(propertyName); - string placeholder = $"{propertyName}placeholder"; + string placeholder = $"{propertyName}fmodelholder"; // spelling mistake is intended void ShouldAppend(string value) { @@ -1092,19 +1092,19 @@ public class CUE4ParseViewModel : ViewModel var type = KismetExtensions.GetPropertyType(property); var prefix = KismetExtensions.GetPrefix(property.GetType().Name); - string whatever; + string pointerIdentifier; if (property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || property.PropertyFlags.HasFlag(EPropertyFlags.ReferenceParm) || KismetExtensions.GetPropertyProperty(property)) { - whatever = "*"; + pointerIdentifier = "*"; } else { - whatever = string.Empty; + pointerIdentifier = string.Empty; } - outputBuilder.AppendLine($"\t{prefix}{type}{whatever} {propertyName} = {propertyName}placeholder;"); + outputBuilder.AppendLine($"\t{prefix}{type}{pointerIdentifier} {propertyName} = {propertyName}fmodelholder;"); } { @@ -1117,8 +1117,8 @@ public class CUE4ParseViewModel : ViewModel if (funcMapOrder != null) { var functionName = f.Name.ToString(); - int indexx = funcMapOrder.IndexOf(functionName); - return indexx >= 0 ? indexx : int.MaxValue; + int index = funcMapOrder.IndexOf(functionName); + return index >= 0 ? index : int.MaxValue; } return int.MaxValue; @@ -1171,8 +1171,6 @@ public class CUE4ParseViewModel : ViewModel } } - - foreach (var function in functions) { string argsList = ""; @@ -1181,21 +1179,28 @@ public class CUE4ParseViewModel : ViewModel { foreach (FProperty property in function.ChildProperties) { - if (property.Name.PlainText == "ReturnValue") + var name = property.Name.ToString(); + var plainName = property.Name.PlainText; + var prefix = KismetExtensions.GetPrefix(property.GetType().Name); + 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 = - $"{(property.PropertyFlags.HasFlag(EPropertyFlags.ConstParm) ? "const " : string.Empty)}{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || KismetExtensions.GetPrefix(property.GetType().Name) == "U" ? "*" : string.Empty)}"; - } - else if (!(property.Name.ToString().EndsWith("_ReturnValue") || - property.Name.ToString().StartsWith("CallFunc_") || - property.Name.ToString().StartsWith("K2Node_") || - property.Name.ToString() - .StartsWith("Temp_")) || // removes useless args - property.PropertyFlags.HasFlag(EPropertyFlags.Edit)) - { - argsList += - $"{(property.PropertyFlags.HasFlag(EPropertyFlags.ConstParm) ? "const " : string.Empty)}{KismetExtensions.GetPrefix(property.GetType().Name)}{KismetExtensions.GetPropertyType(property)}{(property.PropertyFlags.HasFlag(EPropertyFlags.InstancedReference) || KismetExtensions.GetPrefix(property.GetType().Name) == "U" ? "*" : string.Empty)}{(property.PropertyFlags.HasFlag(EPropertyFlags.OutParm) ? "&" : string.Empty)} {Regex.Replace(property.Name.ToString(), @"^__verse_0x[0-9A-Fa-f]+_", "")}, "; + returnFunc = $"{(isConst ? "const " : "")}{prefix}{type}{(isInstanced || prefix == "U" ? "*" : "")}"; + continue; } + + bool uselessIgnore = name.EndsWith("_ReturnValue") || name.StartsWith("CallFunc_") || name.StartsWith("K2Node_") || name.StartsWith("Temp_"); // read variable name + + if (uselessIgnore && !isEdit) + continue; + + var strippedVerseName = Regex.Replace(name, @"^__verse_0x[0-9A-Fa-f]+_", ""); + argsList += $"{(isConst ? "const " : "")}{prefix}{type}{(isInstanced || prefix == "U" ? "*" : "")}{(isOut ? "&" : "")} {strippedVerseName}, "; } } argsList = argsList.TrimEnd(',', ' '); @@ -1211,7 +1216,7 @@ public class CUE4ParseViewModel : ViewModel } else { - outputBuilder.Append("\n\t // This function does not have Bytecode \n\n"); + outputBuilder.Append("\n\t // No Bytecode (Make sure \"Serialize Script Bytecode\" is enabled \n\n"); outputBuilder.Append("\t}\n"); } } @@ -1220,7 +1225,7 @@ public class CUE4ParseViewModel : ViewModel } } - var cpp = Regex.Replace(outputBuilder.ToString(), @"\w+placeholder", "nullptr"); + var cpp = Regex.Replace(outputBuilder.ToString(), @"\w+fmodelholder", "nullptr"); TabControl.SelectedTab.SetDocumentText(cpp, false, false); } From 0f3d80bafb7f52aacd929f8338766ebfd12aafa4 Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Tue, 8 Jul 2025 11:37:31 +1000 Subject: [PATCH 10/25] extensions --- FModel/ViewModels/CUE4ParseViewModel.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index db0b099f..5d495b9b 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -588,18 +588,29 @@ public class CUE4ParseViewModel : ViewModel break; } case "upluginmanifest": + case "code-workspace": case "projectstore": + case "uefnproject": case "uproject": case "manifest": case "uplugin": case "archive": case "dnearchive": // Banishers: Ghosts of New Eden case "gitignore": + case "LICENSE": + case "template": case "vmodule": + case "glslfx": + case "cptake": case "uparam": // Steel Hunters + case "spi1d": case "verse": case "html": case "json": + case "uref": + case "cube": + case "usda": + case "ocio": case "ini": case "txt": case "log": @@ -617,6 +628,10 @@ public class CUE4ParseViewModel : ViewModel case "pem": case "tps": case "tgc": // State of Decay 2 + case "cpp": + case "apx": + case "udn": + case "doc": case "lua": case "js": case "po": @@ -677,8 +692,13 @@ public class CUE4ParseViewModel : ViewModel break; } case "xvag": + case "flac": case "at9": case "wem": + case "wav": + case "WAV": + case "ogg": + // todo: CSCore.MediaFoundation.MediaFoundationException The byte stream type of the given URL is unsupported. case "aif": { var data = Provider.SaveAsset(entry); SaveAndPlaySound(entry.PathWithoutExtension, entry.Extension, data); 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 11/25] 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(',', ' '); From 51541e5ef9dc111464d9305f4eedb3da72c76327 Mon Sep 17 00:00:00 2001 From: Krowe Moh <27891447+Krowe-moh@users.noreply.github.com> Date: Thu, 24 Jul 2025 14:36:38 +1000 Subject: [PATCH 12/25] Use Refactored blueprint decompilation (unfinished code) ignore the json var and GetLoadPackageResult, will be removed once the method is found Removed the KismetExtensions.cs file and refactored blueprint decompilation logic in CUE4ParseViewModel.cs to use a new DecompileBlueprintToPseudo method. Updated CUE4ParseExtensions.cs with GetLoadPackageResult helper. Improved C++ syntax highlighting in Cpp.xshd by adding 'default' keyword and enhanced comment rule. Minor formatting and code organization improvements across affected files. --- FModel/Extensions/CUE4ParseExtensions.cs | 16 + FModel/Extensions/KismetExtensions.cs | 1051 ---------------------- FModel/Resources/Cpp.xshd | 3 + FModel/ViewModels/CUE4ParseViewModel.cs | 280 +----- FModel/ViewModels/SearchViewModel.cs | 58 +- FModel/Views/Resources/Resources.xaml | 1 + FModel/Views/SearchView.xaml | 8 + FModel/Views/SearchView.xaml.cs | 5 +- 8 files changed, 114 insertions(+), 1308 deletions(-) delete mode 100644 FModel/Extensions/KismetExtensions.cs diff --git a/FModel/Extensions/CUE4ParseExtensions.cs b/FModel/Extensions/CUE4ParseExtensions.cs index 463a668e..3e3ac993 100644 --- a/FModel/Extensions/CUE4ParseExtensions.cs +++ b/FModel/Extensions/CUE4ParseExtensions.cs @@ -3,6 +3,7 @@ using CUE4Parse.FileProvider; using CUE4Parse.FileProvider.Objects; using CUE4Parse.UE4.Assets; using CUE4Parse.UE4.Objects.UObject; +using CUE4Parse.Utils; using FModel.Settings; namespace FModel.Extensions; @@ -67,4 +68,19 @@ public static class CUE4ParseExtensions return result; } + + public static LoadPackageResult GetLoadPackageResult(this IFileProvider provider, string file, string objectName = null) + { + var result = new LoadPackageResult { Package = provider.LoadPackage(file) }; + if (result.IsPaginated || (result.Package.HasFlags(EPackageFlags.PKG_ContainsMap) && UserSettings.Default.PreviewWorlds)) // focus on UWorld if it's a map we want to preview + { + result.RequestedIndex = result.Package.GetExportIndex(file.SubstringBeforeLast('.')); + if (objectName != null) + { + result.RequestedIndex = int.TryParse(objectName, out var index) ? index : result.Package.GetExportIndex(objectName); + } + } + + return result; + } } diff --git a/FModel/Extensions/KismetExtensions.cs b/FModel/Extensions/KismetExtensions.cs deleted file mode 100644 index 76562497..00000000 --- a/FModel/Extensions/KismetExtensions.cs +++ /dev/null @@ -1,1051 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -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.Core.Misc; -using CUE4Parse.UE4.Objects.Engine.Ai; -using CUE4Parse.UE4.Objects.Engine.GameFramework; -using CUE4Parse.UE4.Objects.GameplayTags; -using CUE4Parse.UE4.Objects.UObject; - -namespace FModel.Extensions; - -public static class KismetExtensions -{ - public static string GetPrefix(this UStruct val) - { - var ret = string.Empty; - var super = val; - - while (super is not null) - { - 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 - // https://github.com/CrystalFerrai/UeBlueprintDumper/blob/main/UeBlueprintDumper/BlueprintDumper.cs#L352 - // nothing else is from UeBlueprintDumper - - public static string GetUnknownFieldType(object field) - { - string typeName = field.GetType().Name; - int suffixIndex = typeName.IndexOf("Property", StringComparison.Ordinal); - return suffixIndex < 0 ? typeName : typeName.Substring(1, suffixIndex - 1); - } - - public static string GetPropertyType(object? property) - { - if (property is null) - return "None"; - - return property switch - { - FIntProperty or int => "int", - FInt8Property or byte => "int8", - FInt16Property or short => "int16", - FInt64Property or long => "int64", - FUInt16Property or ushort => "uint16", - FUInt32Property or uint => "uint32", - FUInt64Property or ulong => "uint64", - FBoolProperty or bool => "bool", - FStrProperty or string => "FString", - FFloatProperty or float => "float", - FDoubleProperty or double => "double", - FObjectProperty objct => property switch - { - FClassProperty clss => $"{clss.MetaClass?.Name ?? "UKN_ObjectMetaClass"} Class", - FSoftClassProperty softClass => $"{softClass.MetaClass?.Name ?? "UKN_ObjectMetaClass"} Class (soft)", - _ => objct.PropertyClass?.Name ?? "UKN_ObjectPropertyClass" - }, - FPackageIndex pkg => pkg.ResolvedObject?.Class?.Name.ToString() ?? "Package", - FName fme => fme.PlainText.Contains("::") ? fme.PlainText.Split("::")[0] : fme.PlainText, - FEnumProperty enm => enm.Enum?.Name ?? "Enum", - FByteProperty bt => bt.Enum?.ResolvedObject?.Name.Text ?? "Byte", - FInterfaceProperty intrfc => $"{intrfc.InterfaceClass?.Name ?? "UKN_InterfaceClass"} interface", - FStructProperty strct => strct.Struct?.ResolvedObject?.Name.Text ?? "Struct", - FFieldPathProperty fieldPath => $"{fieldPath.PropertyClass.Text} field path", - FDelegateProperty dlgt => $"{dlgt.SignatureFunction?.Name ?? "UKN_SignatureFunction"} (Delegate)", - FMulticastDelegateProperty mdlgt => $"{mdlgt.SignatureFunction?.Name ?? "UKN_SignatureFunction"} (MulticastDelegateProperty)", - FMulticastInlineDelegateProperty midlgt => $"{midlgt.SignatureFunction?.Name ?? "UKN_SignatureFunction"} (MulticastInlineDelegateProperty)", - _ => GetUnknownFieldType(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"; - - return property switch - { - 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 string FormatStructFallback(FStructFallback fallback) - { - if (fallback.Properties.Count == 0) - return "[]"; - - var tags = fallback.Properties.Select(tag => - { - string tagDataFormatted; - - switch (tag.Tag) - { - case TextProperty text: - tagDataFormatted = $"\"{text.Value.Text}\""; - break; - case NameProperty name: - tagDataFormatted = $"\"{name.Value.Text}\""; - break; - case ObjectProperty obj: - tagDataFormatted = $"\"{obj.Value}\""; - break; - default: - { - if (tag.Tag.GenericValue is FScriptStruct { StructType: FStructFallback nestedFallback }) - { - if (nestedFallback.Properties.Count > 0) - { - tagDataFormatted = "{ " + string.Join(", ", - nestedFallback.Properties.Select(nested => - { - string nestedVal = nested.Tag switch - { - TextProperty textProp => $"\"{textProp.Value.Text}\"", - NameProperty nameProp => $"\"{nameProp.Value.Text}\"", - _ => $"\"{nested.Tag.GenericValue}\"" - }; - - return $"\"{nested.Name}\": {nestedVal}"; - })) + " }"; - } - else - { - tagDataFormatted = "{}"; - } - } - else - { - tagDataFormatted = tag.Tag.GenericValue != null ? $"\"{tag.Tag.GenericValue}\"" : "{}"; - } - - break; - } - } - - return $"\t\t{{ \"{tag.Name}\": {tagDataFormatted} }}"; - }); - - return "[\n" + string.Join(",\n", tags) + "\n\t]"; - } - - public static string FormatGameplayTagContainer(FGameplayTagContainer container) - { - var tags = container.GameplayTags.ToList(); - return tags.Count switch - { - 0 => "[]", - 1 => $"\"{tags[0].TagName}\"", - _ => "[\n" + string.Join(",\n", tags.Select(tag => $"\t\t\"{tag.TagName}\"")) + "\n\t]" - }; - } - - public static string FormatStructType(object structType) - { - return structType switch - { - FVector vector => $"FVector({vector.X}, {vector.Y}, {vector.Z})", - FVector2D vector2D => $"FVector2D({vector2D.X}, {vector2D.Y})", - FRotator rotator => $"FRotator({rotator.Pitch}, {rotator.Yaw}, {rotator.Roll})", - FQuat quat => $"FQuat({quat.X}, {quat.Y}, {quat.Z}, {quat.W})", - FGuid guid => $"FGuid({guid.A}, {guid.B}, {guid.C}, {guid.D})", - FColor color => $"FColor({color.R}, {color.G}, {color.B}, {color.A})", - FLinearColor linearColor => $"FLinearColor({linearColor.R}, {linearColor.G}, {linearColor.B}, {linearColor.A})", - FSoftObjectPath path => $"FSoftObjectPath({path.AssetPathName})", - FUniqueNetIdRepl netId => $"FUniqueNetIdRepl({netId.UniqueNetId})", - FNavAgentSelector agent => $"FNavAgentSelector({agent.PackedBits})", - FBox box => $"FBox(FVector({box.Max.X}, {box.Max.Y}, {box.Max.Z}), FVector({box.Min.X}, {box.Min.Y}, {box.Min.Z}))", - FBox2D box2D => $"FBox2D(FVector2D({box2D.Max.X}, {box2D.Max.Y}), FVector2D({box2D.Min.X}, {box2D.Min.Y}))", - TIntVector3 intVec => $"FVector({intVec.X}, {intVec.Y}, {intVec.Z})", - TIntVector3 floatVec => $"FVector({floatVec.X}, {floatVec.Y}, {floatVec.Z})", - TIntVector2 floatVec2 => $"FVector2D({floatVec2.X}, {floatVec2.Y})", - FDateTime dateTime => $"FDateTime({dateTime})", - FStructFallback fallback => FormatStructFallback(fallback), - FGameplayTagContainer tagContainer => FormatGameplayTagContainer(tagContainer), - _ => structType?.ToString() ?? "Issue here" - }; - } - - private static string ProcessTextProperty(FKismetPropertyPointer property, bool temp) - { - if (property.New is null) - { - return property.Old?.Name ?? string.Empty; - } - return string.Join('.', property.New.Path.Select(n => n.Text)).Replace(" ", ""); - } - - public static void ProcessExpression(EExprToken token, KismetExpression expression, StringBuilder outputBuilder, List jumpCodeOffsets, bool isParameter = false) - { - if (jumpCodeOffsets.Contains(expression.StatementIndex)) - { - outputBuilder.Append("\t\tLabel_" + expression.StatementIndex + ":\n"); - } - - switch (token) - { - case EExprToken.EX_LetValueOnPersistentFrame: - { - EX_LetValueOnPersistentFrame op = (EX_LetValueOnPersistentFrame) expression; - EX_VariableBase opp = (EX_VariableBase) op.AssignmentExpression; - var destination = ProcessTextProperty(op.DestinationProperty, false); - var variable = ProcessTextProperty(opp.Variable, false); - - outputBuilder.Append($"\t\t{(destination.Contains("K2Node_") ? "UberGraphFrame->" + destination : destination)} = {variable}{(!isParameter ? ";\n\n" : "")}"); // Hardcoded but works - break; - } - case EExprToken.EX_LocalFinalFunction: - { - EX_FinalFunction op = (EX_FinalFunction) expression; - KismetExpression[] opp = op.Parameters; - if (isParameter) - { - outputBuilder.Append($"{op.StackNode.Name.Replace(" ", "")}("); - } - else if (opp.Length < 1) - { - outputBuilder.Append($"\t\t{op?.StackNode?.Name.Replace(" ", "")}("); - } - else - { - outputBuilder.Append($"\t\t{op?.StackNode?.ResolvedObject?.Outer?.GetType()?.Name.GetPrefix()}{op?.StackNode?.Name.Replace(" ", "")}("); - } - - for (int i = 0; i < opp.Length; i++) - { - if (opp.Length > 4) - outputBuilder.Append("\n\t\t"); - ProcessExpression(opp[i].Token, opp[i], outputBuilder, jumpCodeOffsets, true); - if (i < opp.Length - 1) - { - outputBuilder.Append(", "); - } - } - outputBuilder.Append(isParameter ? ")" : ");\n"); - break; - } - case EExprToken.EX_FinalFunction: - { - EX_FinalFunction op = (EX_FinalFunction) expression; - KismetExpression[] opp = op.Parameters; - if (isParameter) - { - outputBuilder.Append($"{op.StackNode.Name.Replace(" ", "")}("); - } - else if (opp.Length < 1) - { - outputBuilder.Append($"\t\t{op?.StackNode?.Name.Replace(" ", "")}("); - } - else - { - 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++) - { - if (opp.Length > 4) - outputBuilder.Append("\n\t\t"); - ProcessExpression(opp[i].Token, opp[i], outputBuilder, jumpCodeOffsets, true); - if (i < opp.Length - 1) - { - outputBuilder.Append(", "); - } - } - outputBuilder.Append(isParameter ? ")" : ");\n\n"); - break; - } - case EExprToken.EX_CallMath: - { - EX_FinalFunction op = (EX_FinalFunction) expression; - KismetExpression[] opp = op.Parameters; - outputBuilder.Append(isParameter ? string.Empty : "\t\t"); - 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++) - { - if (opp.Length > 4) - outputBuilder.Append("\n\t\t\t"); - ProcessExpression(opp[i].Token, opp[i], outputBuilder, jumpCodeOffsets, true); - if (i < opp.Length - 1) - { - outputBuilder.Append(", "); - } - } - outputBuilder.Append(isParameter ? ")" : ");\n\n"); - break; - } - case EExprToken.EX_LocalVirtualFunction: - case EExprToken.EX_VirtualFunction: - { - EX_VirtualFunction op = (EX_VirtualFunction) expression; - KismetExpression[] opp = op.Parameters; - if (isParameter) - { - outputBuilder.Append($"{op.VirtualFunctionName.PlainText.Replace(" ", "")}("); - } - else - { - outputBuilder.Append($"\t\t{op.VirtualFunctionName.PlainText.Replace(" ", "")}("); - } - for (int i = 0; i < opp.Length; i++) - { - if (opp.Length > 4) - outputBuilder.Append("\n\t\t"); - - ProcessExpression(opp[i].Token, opp[i], outputBuilder, jumpCodeOffsets, true); - if (i < opp.Length - 1) - { - outputBuilder.Append(", "); - } - } - outputBuilder.Append(isParameter ? ")" : ");\n\n"); - break; - } - case EExprToken.EX_ComputedJump: - { - EX_ComputedJump op = (EX_ComputedJump) expression; - if (op.CodeOffsetExpression is EX_VariableBase opp) - { - outputBuilder.AppendLine($"\t\tgoto {ProcessTextProperty(opp.Variable, false)};\n"); - } - else if (op.CodeOffsetExpression is EX_CallMath oppMath) - { - ProcessExpression(oppMath.Token, oppMath, outputBuilder, jumpCodeOffsets, true); - } - - break; - } - case EExprToken.EX_PopExecutionFlowIfNot: - { - EX_PopExecutionFlowIfNot op = (EX_PopExecutionFlowIfNot) expression; - outputBuilder.Append("\t\tif (!"); - ProcessExpression(op.BooleanExpression.Token, op.BooleanExpression, outputBuilder, jumpCodeOffsets, true); - outputBuilder.Append(") \r\n"); - outputBuilder.Append($"\t\t FlowStack.Pop();\n\n"); - break; - } - case EExprToken.EX_Cast: - { - EX_Cast op = (EX_Cast) expression; // support CST_ObjectToInterface when I have an example of how it works - - if (op.ConversionType is ECastToken.CST_ObjectToBool or ECastToken.CST_InterfaceToBool) - { - outputBuilder.Append("(bool)"); - } - if (ECastToken.CST_DoubleToFloat == op.ConversionType) - { - outputBuilder.Append("(float)"); - } - if (ECastToken.CST_FloatToDouble == op.ConversionType) - { - outputBuilder.Append("(double)"); - } - ProcessExpression(op.Target.Token, op.Target, outputBuilder, jumpCodeOffsets); - break; - } - case EExprToken.EX_InterfaceContext: - { - EX_InterfaceContext op = (EX_InterfaceContext) expression; - ProcessExpression(op.InterfaceValue.Token, op.InterfaceValue, outputBuilder, jumpCodeOffsets); - break; - } - case EExprToken.EX_ArrayConst: - { - EX_ArrayConst op = (EX_ArrayConst) expression; - outputBuilder.Append("TArray {"); - foreach (KismetExpression element in op.Elements) - { - outputBuilder.Append(' '); - ProcessExpression(element.Token, element, outputBuilder, jumpCodeOffsets); - } - outputBuilder.Append(op.Elements.Length < 1 ? " " : ' '); - - outputBuilder.Append('}'); - break; - } - case EExprToken.EX_SetArray: - { - EX_SetArray op = (EX_SetArray) expression; - outputBuilder.Append("\t\t"); - ProcessExpression(op.AssigningProperty.Token, op.AssigningProperty, outputBuilder, jumpCodeOffsets); - outputBuilder.Append(" = "); - outputBuilder.Append("TArray {"); - for (int i = 0; i < op.Elements.Length; i++) - { - KismetExpression element = op.Elements[i]; - outputBuilder.Append(' '); - ProcessExpression(element.Token, element, outputBuilder, jumpCodeOffsets); - - outputBuilder.Append(i < op.Elements.Length - 1 ? "," : ""); - } - - outputBuilder.Append(op.Elements.Length < 1 ? " " : ' '); - - outputBuilder.Append("};\n\n"); - break; - } - case EExprToken.EX_SetSet: - { - EX_SetSet op = (EX_SetSet) expression; - outputBuilder.Append("\t\t"); - ProcessExpression(op.SetProperty.Token, op.SetProperty, outputBuilder, jumpCodeOffsets); - outputBuilder.Append(" = "); - outputBuilder.Append("TArray {"); - for (int i = 0; i < op.Elements.Length; i++) - { - KismetExpression element = op.Elements[i]; - outputBuilder.Append(' '); - ProcessExpression(element.Token, element, outputBuilder, jumpCodeOffsets); - - outputBuilder.Append(i < op.Elements.Length - 1 ? "," : ""); - } - - outputBuilder.Append(op.Elements.Length < 1 ? " " : ' '); - - outputBuilder.Append("};\n\n"); - break; - } - case EExprToken.EX_SetConst: - { - EX_SetConst op = (EX_SetConst) expression; - outputBuilder.Append("TArray {"); - for (int i = 0; i < op.Elements.Length; i++) - { - KismetExpression element = op.Elements[i]; - outputBuilder.Append(' '); - ProcessExpression(element.Token, element, outputBuilder, jumpCodeOffsets, true); - - outputBuilder.Append(i < op.Elements.Length - 1 ? "," : ""); - } - - outputBuilder.Append(op.Elements.Length < 1 ? " " : ' '); - - outputBuilder.Append("};\n\n"); - break; - } - case EExprToken.EX_SetMap: - { - EX_SetMap op = (EX_SetMap) expression; - outputBuilder.Append("\t\t"); - ProcessExpression(op.MapProperty.Token, op.MapProperty, outputBuilder, jumpCodeOffsets); - outputBuilder.Append(" = "); - outputBuilder.Append("TMap {"); - for (int i = 0; i < op.Elements.Length; i++) - { - var element = op.Elements[i]; - outputBuilder.Append(' '); - ProcessExpression(element.Token, element, outputBuilder, jumpCodeOffsets); // sometimes the start of an array is a byte not a variable - - if (i < op.Elements.Length - 1) - { - outputBuilder.Append(element.Token == EExprToken.EX_InstanceVariable ? ": " : ", "); - } - else - { - outputBuilder.Append(' '); - } - } - - if (op.Elements.Length < 1) - outputBuilder.Append(" "); - outputBuilder.Append("}\n"); - break; - } - case EExprToken.EX_MapConst: - { - EX_MapConst op = (EX_MapConst) expression; - outputBuilder.Append("TMap {"); - for (int i = 0; i < op.Elements.Length; i++) - { - var element = op.Elements[i]; - outputBuilder.Append(' '); - ProcessExpression(element.Token, element, outputBuilder, jumpCodeOffsets, true); // sometimes the start of an array is a byte not a variable - - if (i < op.Elements.Length - 1) - { - outputBuilder.Append(element.Token == EExprToken.EX_InstanceVariable ? ": " : ", "); - } - else - { - outputBuilder.Append(' '); - } - } - - if (op.Elements.Length < 1) - outputBuilder.Append(" "); - outputBuilder.Append("}\n"); - break; - } - case EExprToken.EX_SwitchValue: - { - EX_SwitchValue op = (EX_SwitchValue) expression; - - bool useTernary = op.Cases.Length <= 2 && - op.Cases.All(c => c.CaseIndexValueTerm.Token is EExprToken.EX_True or EExprToken.EX_False); - - if (useTernary) - { - ProcessExpression(op.IndexTerm.Token, op.IndexTerm, outputBuilder, jumpCodeOffsets); - outputBuilder.Append(" ? "); - - bool isFirst = true; - foreach (var caseItem in op.Cases.Where(c => c.CaseIndexValueTerm.Token == EExprToken.EX_True)) - { - if (!isFirst) - outputBuilder.Append(" : "); - - ProcessExpression(caseItem.CaseTerm.Token, caseItem.CaseTerm, outputBuilder, jumpCodeOffsets, true); - isFirst = false; - } - - foreach (var caseItem in op.Cases.Where(c => c.CaseIndexValueTerm.Token == EExprToken.EX_False)) - { - if (!isFirst) - outputBuilder.Append(" : "); - - ProcessExpression(caseItem.CaseTerm.Token, caseItem.CaseTerm, outputBuilder, jumpCodeOffsets, true); - } - } - else - { - outputBuilder.Append("switch ("); - ProcessExpression(op.IndexTerm.Token, op.IndexTerm, outputBuilder, jumpCodeOffsets); - outputBuilder.Append(")\n"); - outputBuilder.Append("\t\t{\n"); - - foreach (var caseItem in op.Cases) - { - if (caseItem.CaseIndexValueTerm.Token == EExprToken.EX_IntConst) - { - int caseValue = ((EX_IntConst) caseItem.CaseIndexValueTerm).Value; - outputBuilder.Append($"\t\t\tcase {caseValue}:\n"); - } - else - { - outputBuilder.Append("\t\t\tcase "); - ProcessExpression(caseItem.CaseIndexValueTerm.Token, caseItem.CaseIndexValueTerm, outputBuilder, jumpCodeOffsets); - outputBuilder.Append(":\n"); - } - - outputBuilder.Append("\t\t\t{\n"); - outputBuilder.Append("\t\t\t "); - ProcessExpression(caseItem.CaseTerm.Token, caseItem.CaseTerm, outputBuilder, jumpCodeOffsets); - outputBuilder.Append(";\n"); - outputBuilder.Append("\t\t\t break;\n"); - outputBuilder.Append("\t\t\t}\n"); - } - - outputBuilder.Append("\t\t\tdefault:\n"); - outputBuilder.Append("\t\t\t{\n"); - outputBuilder.Append("\t\t\t "); - ProcessExpression(op.DefaultTerm.Token, op.DefaultTerm, outputBuilder, jumpCodeOffsets); - outputBuilder.Append("\n\t\t\t}\n"); - - outputBuilder.Append("\t\t}"); - } - break; - } - case EExprToken.EX_ArrayGetByRef: // I assume get array with index - { - EX_ArrayGetByRef op = (EX_ArrayGetByRef) expression; // FortniteGame/Plugins/GameFeatures/FM/PilgrimCore/Content/Player/Components/BP_PilgrimPlayerControllerComponent.uasset - ProcessExpression(op.ArrayVariable.Token, op.ArrayVariable, outputBuilder, jumpCodeOffsets, true); - outputBuilder.Append('['); - ProcessExpression(op.ArrayIndex.Token, op.ArrayIndex, outputBuilder, jumpCodeOffsets); - outputBuilder.Append(']'); - break; - } - case EExprToken.EX_MetaCast: - case EExprToken.EX_DynamicCast: - case EExprToken.EX_ObjToInterfaceCast: - case EExprToken.EX_CrossInterfaceCast: - case EExprToken.EX_InterfaceToObjCast: - { - EX_CastBase op = (EX_CastBase) expression; - outputBuilder.Append($"Cast(");// m? - ProcessExpression(op.Target.Token, op.Target, outputBuilder, jumpCodeOffsets, true); - outputBuilder.Append(')'); - break; - } - case EExprToken.EX_StructConst: - { - EX_StructConst op = (EX_StructConst) expression; - outputBuilder.Append($"{op.Struct.GetType().Name.GetPrefix()}{op.Struct.Name}"); - outputBuilder.Append('('); - for (int i = 0; i < op.Properties.Length; i++) - { - var property = op.Properties[i]; - ProcessExpression(property.Token, property, outputBuilder, jumpCodeOffsets); - if (i < op.Properties.Length - 1 && property.Token != EExprToken.EX_ArrayConst) - outputBuilder.Append(", "); - } - outputBuilder.Append(')'); - break; - } - case EExprToken.EX_ObjectConst: - { - EX_ObjectConst op = (EX_ObjectConst) expression; - outputBuilder.Append(!isParameter ? "\t\tFindObject<" : outputBuilder.ToString().EndsWith('\n') ? "\t\tFindObject<" : "FindObject<"); // please don't complain, I know this is bad but I MUST do it. - string classString = op.Value.ResolvedObject?.Class?.ToString().Replace("'", ""); - - if (classString?.Contains('.') == true) - { - - outputBuilder.Append(op?.Value?.ResolvedObject?.Class?.GetType().Name.GetPrefix() + classString.Split('.')[1]); - } - else - { - outputBuilder.Append(op?.Value?.ResolvedObject?.Class?.GetType().Name.GetPrefix() + classString); - } - outputBuilder.Append(">(\""); - var resolvedObject = op?.Value?.ResolvedObject; - var outerString = resolvedObject?.Outer?.ToString()?.Replace("'", "") ?? "outerUnknown"; - var outerClassString = resolvedObject?.Class?.ToString()?.Replace("'", "") ?? "outerClassUnknown"; - var name = op?.Value?.Name ?? string.Empty; - - outputBuilder.Append(outerString.Replace(outerClassString, "") + "." + name); - outputBuilder.Append("\")"); - break; - } - case EExprToken.EX_BindDelegate: - { - EX_BindDelegate op = (EX_BindDelegate) expression; - outputBuilder.Append("\t\t"); - ProcessExpression(op.Delegate.Token, op.Delegate, outputBuilder, jumpCodeOffsets); - outputBuilder.Append(".BindUFunction("); - ProcessExpression(op.ObjectTerm.Token, op.ObjectTerm, outputBuilder, jumpCodeOffsets); - outputBuilder.Append($", \"{op.FunctionName}\""); - outputBuilder.Append(");\n\n"); - break; - } - // all the delegate functions suck - case EExprToken.EX_AddMulticastDelegate: - { - EX_AddMulticastDelegate op = (EX_AddMulticastDelegate) expression; - if (op.Delegate.Token is EExprToken.EX_LocalVariable or EExprToken.EX_InstanceVariable) - { - outputBuilder.Append("\t\t"); - ProcessExpression(op.Delegate.Token, op.Delegate, outputBuilder, jumpCodeOffsets, true); - outputBuilder.Append(".AddDelegate("); - ProcessExpression(op.DelegateToAdd.Token, op.DelegateToAdd, outputBuilder, jumpCodeOffsets); - outputBuilder.Append(");\n\n"); - } - else if (op.Delegate.Token != EExprToken.EX_Context) - {} - else - { - //EX_Context opp = (EX_Context) op.Delegate; - outputBuilder.Append("\t\t"); - ProcessExpression(op.Delegate.Token, op.Delegate, outputBuilder, jumpCodeOffsets, true); - //outputBuilder.Append("->"); - //ProcessExpression(opp.ContextExpression.Token, opp.ContextExpression, outputBuilder, jumpCodeOffsets); - outputBuilder.Append(".AddDelegate("); - ProcessExpression(op.DelegateToAdd.Token, op.DelegateToAdd, outputBuilder, jumpCodeOffsets); - outputBuilder.Append(");\n\n"); - } - break; - } - case EExprToken.EX_RemoveMulticastDelegate: // everything here has been guessed not compared to actual UE but does work fine and displays all information - { - EX_RemoveMulticastDelegate op = (EX_RemoveMulticastDelegate) expression; - if (op.Delegate.Token is EExprToken.EX_LocalVariable or EExprToken.EX_InstanceVariable) - { - outputBuilder.Append("\t\t"); - ProcessExpression(op.Delegate.Token, op.Delegate, outputBuilder, jumpCodeOffsets, true); - outputBuilder.Append(".RemoveDelegate("); - ProcessExpression(op.DelegateToAdd.Token, op.DelegateToAdd, outputBuilder, jumpCodeOffsets); - outputBuilder.Append(");\n\n"); - } - else if (op.Delegate.Token != EExprToken.EX_Context) - { - - } - else - { - EX_Context opp = (EX_Context) op.Delegate; - outputBuilder.Append("\t\t"); - ProcessExpression(op.Delegate.Token, op.Delegate, outputBuilder, jumpCodeOffsets, true); - outputBuilder.Append("->"); - ProcessExpression(opp.ContextExpression.Token, opp.ContextExpression, outputBuilder, jumpCodeOffsets); - outputBuilder.Append(".RemoveDelegate("); - ProcessExpression(op.DelegateToAdd.Token, op.DelegateToAdd, outputBuilder, jumpCodeOffsets); - outputBuilder.Append(");\n\n"); - } - break; - } - case EExprToken.EX_ClearMulticastDelegate: // this also - { - EX_ClearMulticastDelegate op = (EX_ClearMulticastDelegate) expression; - outputBuilder.Append("\t\t"); - ProcessExpression(op.DelegateToClear.Token, op.DelegateToClear, outputBuilder, jumpCodeOffsets, true); - outputBuilder.Append(".Clear();\n\n"); - break; - } - case EExprToken.EX_CallMulticastDelegate: // this also - { - EX_CallMulticastDelegate op = (EX_CallMulticastDelegate) expression; - KismetExpression[] opp = op.Parameters; - if (op.Delegate.Token == EExprToken.EX_LocalVariable || op.Delegate.Token == EExprToken.EX_InstanceVariable) - { - outputBuilder.Append("\t\t"); - ProcessExpression(op.Delegate.Token, op.Delegate, outputBuilder, jumpCodeOffsets, true); - outputBuilder.Append(".Call("); - for (int i = 0; i < opp.Length; i++) - { - if (opp.Length > 4) - outputBuilder.Append("\n\t\t"); - ProcessExpression(opp[i].Token, opp[i], outputBuilder, jumpCodeOffsets, true); - if (i < opp.Length - 1) - { - outputBuilder.Append(", "); - } - } - outputBuilder.Append(");\n\n"); - } - else if (op.Delegate.Token != EExprToken.EX_Context) - { - - } - else - { - outputBuilder.Append("\t\t"); - ProcessExpression(op.Delegate.Token, op.Delegate, outputBuilder, jumpCodeOffsets, true); - outputBuilder.Append(".Call("); - for (int i = 0; i < opp.Length; i++) - { - if (opp.Length > 4) - outputBuilder.Append("\n\t\t"); - ProcessExpression(opp[i].Token, opp[i], outputBuilder, jumpCodeOffsets, true); - if (i < opp.Length - 1) - { - outputBuilder.Append(", "); - } - } - outputBuilder.Append(");\n\n"); - } - break; - } - case EExprToken.EX_ClassContext: - case EExprToken.EX_Context: - { - EX_Context op = (EX_Context) expression; - outputBuilder.Append(outputBuilder.ToString().EndsWith('\n') ? "\t\t" : ""); - ProcessExpression(op.ObjectExpression.Token, op.ObjectExpression, outputBuilder, jumpCodeOffsets, true); - - outputBuilder.Append("->"); - ProcessExpression(op.ContextExpression.Token, op.ContextExpression, outputBuilder, jumpCodeOffsets, true); - if (!isParameter) - { - outputBuilder.Append(";\n\n"); - } - break; - } - case EExprToken.EX_Context_FailSilent: - { - EX_Context op = (EX_Context) expression; - outputBuilder.Append("\t\t"); - ProcessExpression(op.ObjectExpression.Token, op.ObjectExpression, outputBuilder, jumpCodeOffsets, true); - if (!isParameter) - { - outputBuilder.Append("->"); - ProcessExpression(op.ContextExpression.Token, op.ContextExpression, outputBuilder, jumpCodeOffsets, true); - outputBuilder.Append(";\n\n"); - } - break; - } - case EExprToken.EX_Let: - { - EX_Let op = (EX_Let) expression; - if (!isParameter) - { - outputBuilder.Append("\t\t"); - } - ProcessExpression(op.Variable.Token, op.Variable, outputBuilder, jumpCodeOffsets, true); - outputBuilder.Append(" = "); - ProcessExpression(op.Assignment.Token, op.Assignment, outputBuilder, jumpCodeOffsets, true); - if (!isParameter) - { - outputBuilder.Append(";\n\n"); - } - break; - } - case EExprToken.EX_LetObj: - case EExprToken.EX_LetWeakObjPtr: - case EExprToken.EX_LetBool: - case EExprToken.EX_LetDelegate: - case EExprToken.EX_LetMulticastDelegate: - { - EX_LetBase op = (EX_LetBase) expression; - if (!isParameter) - { - outputBuilder.Append("\t\t"); - } - ProcessExpression(op.Variable.Token, op.Variable, outputBuilder, jumpCodeOffsets, true); - outputBuilder.Append(" = "); - ProcessExpression(op.Assignment.Token, op.Assignment, outputBuilder, jumpCodeOffsets, true); - if (!isParameter || op.Assignment.Token == EExprToken.EX_LocalFinalFunction || op.Assignment.Token == EExprToken.EX_FinalFunction || op.Assignment.Token == EExprToken.EX_CallMath) - { - outputBuilder.Append(";\n\n"); - } - else - { - outputBuilder.Append(';'); - } - break; - } - case EExprToken.EX_JumpIfNot: - { - EX_JumpIfNot op = (EX_JumpIfNot) expression; - outputBuilder.Append("\t\tif (!"); - ProcessExpression(op.BooleanExpression.Token, op.BooleanExpression, outputBuilder, jumpCodeOffsets, true); - outputBuilder.Append(") \r\n"); - outputBuilder.Append("\t\t goto Label_"); - outputBuilder.Append(op.CodeOffset); - outputBuilder.Append(";\n\n"); - break; - } - case EExprToken.EX_Jump: - { - EX_Jump op = (EX_Jump) expression; - outputBuilder.Append($"\t\tgoto Label_{op.CodeOffset};\n\n"); - break; - } - // Static expressions - - case EExprToken.EX_TextConst: - { - EX_TextConst op = (EX_TextConst) expression; - - if (op.Value is FScriptText scriptText) - { - if (scriptText.SourceString == null) - { - outputBuilder.Append("nullptr"); - } - else - ProcessExpression(scriptText.SourceString.Token, scriptText.SourceString, outputBuilder, jumpCodeOffsets, true); - } - else - { - outputBuilder.Append(op.Value); // impossible to reach? - } - } - break; - case EExprToken.EX_StructMemberContext: - { - EX_StructMemberContext op = (EX_StructMemberContext) expression; - ProcessExpression(op.StructExpression.Token, op.StructExpression, outputBuilder, jumpCodeOffsets); - outputBuilder.Append('.'); - outputBuilder.Append(ProcessTextProperty(op.Property, false)); - break; - } - case EExprToken.EX_Return: - { - EX_Return op = (EX_Return) expression; - bool check = op.ReturnExpression.Token == EExprToken.EX_Nothing; - outputBuilder.Append("\t\treturn"); - if (!check) - outputBuilder.Append(' '); - ProcessExpression(op.ReturnExpression.Token, op.ReturnExpression, outputBuilder, jumpCodeOffsets, true); - outputBuilder.AppendLine(";"); - break; - } - case EExprToken.EX_RotationConst: - { - EX_RotationConst op = (EX_RotationConst) expression; - FRotator value = op.Value; - outputBuilder.Append($"FRotator({value.Pitch}, {value.Yaw}, {value.Roll})"); - break; - } - case EExprToken.EX_VectorConst: - { - EX_VectorConst op = (EX_VectorConst) expression; - FVector value = op.Value; - outputBuilder.Append($"FVector({value.X}, {value.Y}, {value.Z})"); - break; - } - case EExprToken.EX_Vector3fConst: - { - EX_Vector3fConst op = (EX_Vector3fConst) expression; - FVector value = op.Value; - outputBuilder.Append($"FVector3f({value.X}, {value.Y}, {value.Z})"); - break; - } - case EExprToken.EX_TransformConst: - { - EX_TransformConst op = (EX_TransformConst) expression; - FTransform value = op.Value; - outputBuilder.Append($"FTransform(FQuat({value.Rotation.X}, {value.Rotation.Y}, {value.Rotation.Z}, {value.Rotation.W}), FVector({value.Translation.X}, {value.Translation.Y}, {value.Translation.Z}), FVector({value.Scale3D.X}, {value.Scale3D.Y}, {value.Scale3D.Z}))"); - break; - } - - - case EExprToken.EX_LocalVariable: - case EExprToken.EX_DefaultVariable: - case EExprToken.EX_LocalOutVariable: - case EExprToken.EX_ClassSparseDataVariable: - outputBuilder.Append(ProcessTextProperty(((EX_VariableBase) expression).Variable, false)); - break; - case EExprToken.EX_InstanceVariable: - outputBuilder.Append(ProcessTextProperty(((EX_VariableBase) expression).Variable, true)); - break; - - case EExprToken.EX_ByteConst: - case EExprToken.EX_IntConstByte: - outputBuilder.Append($"0x{((KismetExpression) expression).Value.ToString("X")}"); - break; - case EExprToken.EX_SoftObjectConst: - ProcessExpression(((EX_SoftObjectConst) expression).Value.Token, ((EX_SoftObjectConst) expression).Value, outputBuilder, jumpCodeOffsets); - break; - case EExprToken.EX_DoubleConst: - { - double value = ((EX_DoubleConst) expression).Value; - outputBuilder.Append(Math.Abs(value - Math.Floor(value)) < 1e-10 ? (int) value : value.ToString("R")); - break; - } - case EExprToken.EX_NameConst: - outputBuilder.Append($"\"{((EX_NameConst) expression).Value}\""); - break; - case EExprToken.EX_IntConst: - outputBuilder.Append(((EX_IntConst) expression).Value.ToString()); - break; - case EExprToken.EX_PropertyConst: - outputBuilder.Append(ProcessTextProperty(((EX_PropertyConst) expression).Property, false)); - break; - case EExprToken.EX_StringConst: - outputBuilder.Append($"\"{((EX_StringConst) expression).Value}\""); - break; - case EExprToken.EX_FieldPathConst: - ProcessExpression(((EX_FieldPathConst) expression).Value.Token, ((EX_FieldPathConst) expression).Value, outputBuilder, jumpCodeOffsets); - break; - case EExprToken.EX_Int64Const: - outputBuilder.Append(((EX_Int64Const) expression).Value.ToString()); - break; - case EExprToken.EX_UInt64Const: - outputBuilder.Append(((EX_UInt64Const) expression).Value.ToString()); - break; - case EExprToken.EX_SkipOffsetConst: - outputBuilder.Append(((EX_SkipOffsetConst) expression).Value.ToString()); - break; - case EExprToken.EX_FloatConst: - outputBuilder.Append(((EX_FloatConst) expression).Value.ToString(CultureInfo.GetCultureInfo("en-US"))); - break; - case EExprToken.EX_BitFieldConst: - outputBuilder.Append(((EX_BitFieldConst) expression).ConstValue); - break; - case EExprToken.EX_UnicodeStringConst: - outputBuilder.Append(((EX_UnicodeStringConst) expression).Value); - break; - case EExprToken.EX_InstanceDelegate: - outputBuilder.Append($"\"{((EX_InstanceDelegate) expression).FunctionName}\""); - break; - case EExprToken.EX_EndOfScript: - case EExprToken.EX_EndParmValue: - outputBuilder.Append("\t}\n"); - break; - case EExprToken.EX_NoObject: - case EExprToken.EX_NoInterface: - outputBuilder.Append("nullptr"); - break; - case EExprToken.EX_IntOne: - outputBuilder.Append(1); - break; - case EExprToken.EX_IntZero: - outputBuilder.Append(0); - break; - case EExprToken.EX_True: - outputBuilder.Append("true"); - break; - case EExprToken.EX_False: - outputBuilder.Append("false"); - break; - case EExprToken.EX_Self: - outputBuilder.Append("this"); - break; - - case EExprToken.EX_Nothing: - case EExprToken.EX_NothingInt32: - case EExprToken.EX_EndFunctionParms: - case EExprToken.EX_EndStructConst: - case EExprToken.EX_EndArray: - case EExprToken.EX_EndArrayConst: - case EExprToken.EX_EndSet: - case EExprToken.EX_EndMap: - case EExprToken.EX_EndMapConst: - case EExprToken.EX_EndSetConst: - case EExprToken.EX_PushExecutionFlow: - case EExprToken.EX_PopExecutionFlow: - case EExprToken.EX_DeprecatedOp4A: - case EExprToken.EX_WireTracepoint: - case EExprToken.EX_Tracepoint: - case EExprToken.EX_Breakpoint: - case EExprToken.EX_AutoRtfmStopTransact: - case EExprToken.EX_AutoRtfmTransact: - case EExprToken.EX_AutoRtfmAbortIfNot: - // some here are "useful" and unsupported - break; - /* - Todo: check what uses these, fortnite has none instances - EExprToken.EX_Assert - EExprToken.EX_Skip - EExprToken.EX_InstrumentationEvent - EExprToken.EX_FieldPathConst - */ - default: - outputBuilder.Append($"{token}"); - break; - } - } -} diff --git a/FModel/Resources/Cpp.xshd b/FModel/Resources/Cpp.xshd index f02e55c0..f61b1264 100644 --- a/FModel/Resources/Cpp.xshd +++ b/FModel/Resources/Cpp.xshd @@ -82,6 +82,7 @@ friend inline constexpr + default @@ -119,6 +120,8 @@ [\[\]\{\}] + (\/\/.*|\/\*[\s\S]*?\*\/) + \b[A-Za-z_][A-Za-z0-9_]*\b(?=<) diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 1b87ad79..2590b975 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -74,10 +74,12 @@ public class CUE4ParseViewModel : ViewModel { private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView; + private readonly Regex _fnLiveRegex = new(@"^FortniteGame[/\\]Content[/\\]Paks[/\\]", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); private bool _modelIsOverwritingMaterial; + public bool ModelIsOverwritingMaterial { get => _modelIsOverwritingMaterial; @@ -86,6 +88,7 @@ public class CUE4ParseViewModel : ViewModel public bool IsSnooperOpen => _snooper is { Exists: true, IsVisible: true }; private Snooper _snooper; + public Snooper SnooperViewer { get @@ -445,6 +448,7 @@ public class CUE4ParseViewModel : ViewModel public int LocalizedResourcesCount { get; set; } public bool LocalResourcesDone { get; set; } public bool HotfixedResourcesDone { get; set; } + public async Task LoadLocalizedResources() { var snapshot = LocalizedResourcesCount; @@ -458,6 +462,7 @@ public class CUE4ParseViewModel : ViewModel Utils.Typefaces = new Typefaces(this); } } + private Task LoadGameLocalizedResources() { if (LocalResourcesDone) return Task.CompletedTask; @@ -466,6 +471,7 @@ public class CUE4ParseViewModel : ViewModel LocalResourcesDone = Provider.TryChangeCulture(Provider.GetLanguageCode(UserSettings.Default.AssetLanguage)); }); } + private Task LoadHotfixedLocalizedResources() { if (!Provider.ProjectName.Equals("fortnitegame", StringComparison.OrdinalIgnoreCase) || HotfixedResourcesDone) return Task.CompletedTask; @@ -480,6 +486,7 @@ public class CUE4ParseViewModel : ViewModel } private int _virtualPathCount { get; set; } + public Task LoadVirtualPaths() { if (_virtualPathCount > 0) return Task.CompletedTask; @@ -698,7 +705,7 @@ public class CUE4ParseViewModel : ViewModel case "wav": case "WAV": case "ogg": - // todo: CSCore.MediaFoundation.MediaFoundationException The byte stream type of the given URL is unsupported. case "aif": + // todo: CSCore.MediaFoundation.MediaFoundationException The byte stream type of the given URL is unsupported. case "aif": { var data = Provider.SaveAsset(entry); SaveAndPlaySound(entry.PathWithoutExtension, entry.Extension, data); @@ -988,271 +995,34 @@ public class CUE4ParseViewModel : ViewModel var pkg = Provider.LoadPackage(entry); - var outputBuilder = new StringBuilder(); + string mypathisapathrealwhathowPath = Path.Combine( + Path.GetDirectoryName(entry.Path)!, + $"{Path.GetFileNameWithoutExtension(entry.Path)}.o.uasset" + ); + + string idkhowtogetitwithoutthis = string.Empty; + try + { + var whatisthisforealreal = Provider.GetLoadPackageResult(mypathisapathrealwhathowPath); + idkhowtogetitwithoutthis = JsonConvert.SerializeObject(whatisthisforealreal.GetDisplayData(false), Formatting.Indented); + } + catch (Exception e) {} + + + var cpp = string.Empty; for (var i = 0; i < pkg.ExportMapLength; i++) { var pointer = new FPackageIndex(pkg, i + 1).ResolvedObject; - if (pointer?.Object is null) + if (pointer?.Object is null && pointer.Class?.Object?.Value is null) continue; var dummy = ((AbstractUePackage) pkg).ConstructObject(pointer.Class?.Object?.Value as UStruct, pkg); if (dummy is not UClass || pointer.Object.Value is not UClass blueprint) continue; - 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; - - var strings = new List(); - foreach (var property in bpObject.Properties) - { - var propertyName = property.Name.ToString(); - var propertyValue = property.Tag?.GenericValue; - strings.Add(propertyName); - string placeholder = $"{propertyName}fmodelholder"; // spelling mistake is intended - - void ShouldAppend(string value) - { - if (outputBuilder.ToString().Contains(placeholder)) - { - outputBuilder.Replace(placeholder, value); - } - else - { - outputBuilder.AppendLine($"\t{KismetExtensions.GetPropertyType(propertyValue)} {propertyName.Replace(" ", "")} = {value};"); - } - } - - string GetLineOfText(object value) - { - string text = null; - switch (value) - { - case FScriptStruct structTag: - switch (structTag.StructType) - { - case FVector vector: - text = $"FVector({vector.X}, {vector.Y}, {vector.Z})"; - break; - case FGuid guid: - text = $"FGuid({guid.A}, {guid.B}, {guid.C}, {guid.D})"; - break; - case TIntVector3 vector3: - text = $"FVector({vector3.X}, {vector3.Y}, {vector3.Z})"; - break; - case TIntVector3 floatVector3: - text = $"FVector({floatVector3.X}, {floatVector3.Y}, {floatVector3.Z})"; - break; - case TIntVector2 floatVector2: - text = $"FVector2D({floatVector2.X}, {floatVector2.Y})"; - break; - case FVector2D vector2d: - text = $"FVector2D({vector2d.X}, {vector2d.Y})"; - break; - case FRotator rotator: - text = $"FRotator({rotator.Pitch}, {rotator.Yaw}, {rotator.Roll})"; - break; - case FLinearColor linearColor: - text = $"FLinearColor({linearColor.R}, {linearColor.G}, {linearColor.B}, {linearColor.A})"; - break; - case FGameplayTagContainer gTag: - text = gTag.GameplayTags.Length switch - { - > 1 => "[\n" + string.Join(",\n", gTag.GameplayTags.Select(tag => $"\t\t\"{tag.TagName}\"")) + "\n\t]", - > 0 => $"\"{gTag.GameplayTags[0].TagName}\"", - _ => "[]" - }; - break; - case FStructFallback fallback: - if (fallback.Properties.Count > 0) - { - text = "[\n" + string.Join(",\n", fallback.Properties.Select(p => $"\t\"{GetLineOfText(p)}\"")) + "\n\t]"; - } - else - { - text = "[]"; - } - break; - } - break; - case UScriptSet: - case UScriptMap: - case UScriptArray: - IEnumerable inner = value switch - { - UScriptSet set => set.Properties.Select(p => $"\t\"{p.GenericValue}\""), - UScriptMap map => map.Properties.Select(kvp => $"\t{{\n\t\t\"{kvp.Key}\": \"{kvp.Value}\"\n\t}}"), - UScriptArray array => array.Properties.Select(p => $"\t\"{GetLineOfText(p)}\""), - _ => throw new ArgumentOutOfRangeException(nameof(value), value, null) - }; - - text = "[\n" + string.Join(",\n", inner) + "\n\t]"; - break; - case FMulticastScriptDelegate multicast: - text = multicast.InvocationList.Length == 0 ? "[]" : $"[{string.Join(", ", multicast.InvocationList.Select(x => $"\"{x.FunctionName}\""))}]"; - break; - case bool: - text = value.ToString()?.ToLowerInvariant(); - break; - } - - return text ?? value.ToString(); - } - - ShouldAppend(GetLineOfText(propertyValue)); - } - - foreach (var field in blueprint.ChildProperties) - { - if (field is not FProperty property || strings.Contains(property.Name.Text)) continue; - - var propertyName = property.Name.ToString().Replace(" ", ""); - var type = KismetExtensions.GetPropertyType(property); - - var prefix = ""; - switch (property) - { - case FFieldPathProperty pathProp: - prefix = pathProp.PropertyClass.ToString().GetPrefix(); - break; - case FObjectProperty objectProp: - prefix = objectProp.PropertyClass.ToString().GetPrefix(); - break; - } - - outputBuilder.AppendLine($"\t{prefix}{type}{(KismetExtensions.isPointer(property) ? '*' : "")} {propertyName} = {propertyName}fmodelholder;"); - } - - { - var funcMapOrder = blueprint?.FuncMap?.Keys.Select(fname => fname.ToString()).ToList(); - var functions = pkg.ExportsLazy - .Where(e => e.Value is UFunction) - .Select(e => (UFunction) e.Value) - .OrderBy(f => - { - if (funcMapOrder != null) - { - var functionName = f.Name.ToString(); - int index = funcMapOrder.IndexOf(functionName); - return index >= 0 ? index : int.MaxValue; - } - - return int.MaxValue; - }) - .ThenBy(f => f.Name.ToString()) - .ToList(); - - var jumpCodeOffsetsMap = new Dictionary>(); - - foreach (var function in functions.AsEnumerable().Reverse()) - { - if (function?.ScriptBytecode == null) - continue; - - foreach (var property in function.ScriptBytecode) - { - string label = string.Empty; - int offset = 0; - - switch (property.Token) - { - case EExprToken.EX_JumpIfNot: - label = ((EX_JumpIfNot) property).ObjectPath?.ToString()?.Split('.').Last().Split('[')[0]; - offset = (int) ((EX_JumpIfNot) property).CodeOffset; - break; - - case EExprToken.EX_Jump: - label = ((EX_Jump) property).ObjectPath?.ToString()?.Split('.').Last().Split('[')[0]; - offset = (int) ((EX_Jump) property).CodeOffset; - break; - - case EExprToken.EX_LocalFinalFunction: - { - EX_FinalFunction op = (EX_FinalFunction) property; - label = op.StackNode?.Name?.Split('.').Last().Split('[')[0]; - - if (op is { Parameters: [EX_IntConst intConst] }) - offset = intConst.Value; - break; - } - } - - if (!string.IsNullOrEmpty(label)) - { - if (!jumpCodeOffsetsMap.TryGetValue(label, out var list)) - jumpCodeOffsetsMap[label] = list = new List(); - - list.Add(offset); - } - } - } - - foreach (var function in functions) - { - string argsList = ""; - string returnFunc = "void"; - if (function?.ChildProperties != null) - { - foreach (FProperty property in function.ChildProperties) - { - var name = property.Name.ToString(); - var plainName = property.Name.PlainText; - 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 isEdit = property.PropertyFlags.HasFlag(EPropertyFlags.Edit); - - if (plainName == "ReturnValue") - { - returnFunc = $"{(isConst ? "const " : "")}{prefix}{type}{(KismetExtensions.isPointer(property) ? '*' : "")}"; - continue; - } - - bool uselessIgnore = name.EndsWith("_ReturnValue") || name.StartsWith("CallFunc_") || name.StartsWith("K2Node_") || name.StartsWith("Temp_"); // read variable name - - if (uselessIgnore && !isEdit) - continue; - - var strippedVerseName = Regex.Replace(name, @"^__verse_0x[0-9A-Fa-f]+_", ""); - argsList += $"{(isConst ? "const " : "")}{prefix}{type}{(KismetExtensions.isPointer(property) ? '*' : "")}{(isOut ? '&' : "")} {strippedVerseName}, "; - } - } - argsList = argsList.TrimEnd(',', ' '); - - outputBuilder.AppendLine($"\n\t{returnFunc} {function.Name.Replace(" ", "")}({argsList})\n\t{{"); - if (function?.ScriptBytecode != null) - { - var jumpCodeOffsets = jumpCodeOffsetsMap.TryGetValue(function.Name, out var list) ? list : new List(); - foreach (KismetExpression property in function.ScriptBytecode) - { - KismetExtensions.ProcessExpression(property.Token, property, outputBuilder, jumpCodeOffsets); - } - } - else - { - outputBuilder.Append("\n\t // No Bytecode (Make sure \"Serialize Script Bytecode\" is enabled \n\n"); - outputBuilder.Append("\t}\n"); - } - } - - outputBuilder.Append("\n\n}"); - } + cpp += blueprint.DecompileBlueprintToPseudo(idkhowtogetitwithoutthis); } - var cpp = Regex.Replace(outputBuilder.ToString(), @"\w+fmodelholder", "nullptr"); TabControl.SelectedTab.SetDocumentText(cpp, false, false); } diff --git a/FModel/ViewModels/SearchViewModel.cs b/FModel/ViewModels/SearchViewModel.cs index 15585911..35cb9bc3 100644 --- a/FModel/ViewModels/SearchViewModel.cs +++ b/FModel/ViewModels/SearchViewModel.cs @@ -4,13 +4,38 @@ using System.ComponentModel; using System.Linq; using System.Text.RegularExpressions; using System.Windows.Data; +using System.Windows.Input; using CUE4Parse.FileProvider.Objects; using FModel.Framework; namespace FModel.ViewModels; +public class RelayCommand : ICommand +{ + private readonly Action _execute; + private readonly Func? _canExecute; + + public event EventHandler? CanExecuteChanged; + + public RelayCommand(Action execute, Func? canExecute = null) + { + _execute = execute ?? throw new ArgumentNullException(nameof(execute)); + _canExecute = canExecute; + } + public bool CanExecute(object? parameter) => _canExecute?.Invoke() ?? true; + public void Execute(object? parameter) => _execute(); + +} + public class SearchViewModel : ViewModel { + public enum ESortSizeMode + { + None, + Ascending, + Descending + } + private string _filterText; public string FilterText { @@ -32,6 +57,29 @@ public class SearchViewModel : ViewModel set => SetProperty(ref _hasMatchCaseEnabled, value); } + private ESortSizeMode _currentSortSizeMode = ESortSizeMode.None; + public ESortSizeMode CurrentSortSizeMode + { + get => _currentSortSizeMode; + set => SetProperty(ref _currentSortSizeMode, value); + } + + public void CycleSortSizeMode() + { + CurrentSortSizeMode = CurrentSortSizeMode switch + { + ESortSizeMode.None => ESortSizeMode.Ascending, + ESortSizeMode.Ascending => ESortSizeMode.Descending, + ESortSizeMode.Descending => ESortSizeMode.None, + _ => ESortSizeMode.None + }; + + RefreshFilter(); + } + + private RelayCommand? _sortSizeModeCommand; + public ICommand SortSizeModeCommand => _sortSizeModeCommand ??= new RelayCommand(CycleSortSizeMode); + public int ResultsCount => SearchResults?.Count ?? 0; public RangeObservableCollection SearchResults { get; } public ICollectionView SearchResultsView { get; } @@ -48,6 +96,14 @@ public class SearchViewModel : ViewModel SearchResultsView.Filter = e => ItemFilter(e, FilterText.Trim().Split(' ')); else SearchResultsView.Refresh(); + + SearchResultsView.SortDescriptions.Clear(); + + if (CurrentSortSizeMode != ESortSizeMode.None) + SearchResultsView.SortDescriptions.Add(new SortDescription(nameof(GameFile.Size), + CurrentSortSizeMode == ESortSizeMode.Ascending + ? ListSortDirection.Ascending + : ListSortDirection.Descending)); } private bool ItemFilter(object item, IEnumerable filters) @@ -62,4 +118,4 @@ public class SearchViewModel : ViewModel if (!HasMatchCaseEnabled) o |= RegexOptions.IgnoreCase; return new Regex(FilterText, o).Match(entry.Path).Success; } -} +} \ No newline at end of file diff --git a/FModel/Views/Resources/Resources.xaml b/FModel/Views/Resources/Resources.xaml index 1d1500ed..c42ee085 100644 --- a/FModel/Views/Resources/Resources.xaml +++ b/FModel/Views/Resources/Resources.xaml @@ -70,6 +70,7 @@ M1.8 6q-.525 0-.887-.35Q.55 5.3.55 4.8V4q0-1.425 1.012-2.438Q2.575.55 4 .55h.8q.5 0 .85.362.35.363.35.888 0 .5-.35.85T4.8 3H4q-.425 0-.712.287Q3 3.575 3 4v.8q0 .5-.35.85T1.8 6ZM4 23.45q-1.425 0-2.438-1.012Q.55 21.425.55 20v-.8q0-.5.363-.85.362-.35.887-.35.5 0 .85.35t.35.85v.8q0 .425.288.712Q3.575 21 4 21h.8q.5 0 .85.35t.35.85q0 .525-.35.887-.35.363-.85.363Zm15.2 0q-.5 0-.85-.363-.35-.362-.35-.887 0-.5.35-.85t.85-.35h.8q.425 0 .712-.288Q21 20.425 21 20v-.8q0-.5.35-.85t.85-.35q.525 0 .888.35.362.35.362.85v.8q0 1.425-1.012 2.438Q21.425 23.45 20 23.45ZM22.2 6q-.5 0-.85-.35T21 4.8V4q0-.425-.288-.713Q20.425 3 20 3h-.8q-.5 0-.85-.35T18 1.8q0-.525.35-.888.35-.362.85-.362h.8q1.425 0 2.438 1.012Q23.45 2.575 23.45 4v.8q0 .5-.362.85-.363.35-.888.35ZM12 17.35l1-.575v-4.1l3.55-2.075V9.425l-1-.575L12 10.925 8.45 8.85l-1 .575V10.6L11 12.675v4.1Zm-1.325 2.325-4.55-2.65q-.625-.35-.975-.963-.35-.612-.35-1.337V9.45q0-.725.35-1.337.35-.613.975-.963l4.55-2.65Q11.3 4.15 12 4.15t1.325.35l4.55 2.65q.625.35.975.963.35.612.35 1.337v5.275q0 .725-.35 1.337-.35.613-.975.963l-4.55 2.65q-.625.35-1.325.35t-1.325-.35Z M3.5 1.75v11.5c0 .09.048.173.126.217a.75.75 0 0 1-.752 1.298A1.748 1.748 0 0 1 2 13.25V1.75C2 .784 2.784 0 3.75 0h5.586c.464 0 .909.185 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v8.586A1.75 1.75 0 0 1 12.25 15h-.5a.75.75 0 0 1 0-1.5h.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177L9.513 1.573a.25.25 0 0 0-.177-.073H7.25a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5h-3a.25.25 0 0 0-.25.25Zm3.75 8.75h.5c.966 0 1.75.784 1.75 1.75v3a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1-.75-.75v-3c0-.966.784-1.75 1.75-1.75ZM6 5.25a.75.75 0 0 1 .75-.75h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 6 5.25Zm.75 2.25h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5ZM8 6.75A.75.75 0 0 1 8.75 6h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 8 6.75ZM8.75 3h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5ZM8 9.75A.75.75 0 0 1 8.75 9h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 8 9.75Zm-1 2.5v2.25h1v-2.25a.25.25 0 0 0-.25-.25h-.5a.25.25 0 0 0-.25.25Z M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z + M8 16H4l6 6V2H8zm6-11v17h2V8h4l-6-6z