refactored parts of this hell

This commit is contained in:
Asval 2025-07-06 18:52:16 +02:00
parent e78c7a7be9
commit 00bcb7ca16
6 changed files with 383 additions and 683 deletions

View File

@ -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<int> intVec:
return $"FVector({intVec.X}, {intVec.Y}, {intVec.Z})";
case TIntVector3<float> floatVec:
return $"FVector({floatVec.X}, {floatVec.Y}, {floatVec.Z})";
case TIntVector2<float> 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<int> intVec => $"FVector({intVec.X}, {intVec.Y}, {intVec.Z})",
TIntVector3<float> floatVec => $"FVector({floatVec.X}, {floatVec.Y}, {floatVec.Z})",
TIntVector2<float> 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<int> 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<U{op.ClassPtr.Name}*>(");// 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);

View File

@ -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;
}

View File

@ -468,7 +468,7 @@
<controls:Breadcrumb Grid.Row="0" MaxWidth="{Binding ActualWidth, ElementName=AssetsSearchName}" HorizontalAlignment="Left" Margin="0 5 0 5"
DataContext="{Binding SelectedItem.PathAtThisPoint, ElementName=AssetsFolderName, FallbackValue='No/Directory/Detected/In/Folder'}"/>
<ListBox Grid.Row="1" x:Name="AssetsListName" Style="{StaticResource AssetsListBox}" PreviewMouseDoubleClick="OnAssetsListMouseDoubleClick" PreviewKeyDown="OnPreviewKeyDown" ContextMenuOpening="AssetsListName_ContextMenuOpening">
<ListBox Grid.Row="1" x:Name="AssetsListName" Style="{StaticResource AssetsListBox}" PreviewMouseDoubleClick="OnAssetsListMouseDoubleClick" PreviewKeyDown="OnPreviewKeyDown">
<ListBox.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Extract in New Tab" Command="{Binding DataContext.RightClickMenuCommand}">
@ -501,7 +501,7 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Name="Decompile" Header="Decompile Blueprint" Command="{Binding DataContext.RightClickMenuCommand}" Visibility="Collapsed">
<MenuItem Header="Decompile Blueprint" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Decompile" />
@ -515,6 +515,15 @@
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem.Style>
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource {x:Type MenuItem}}">
<Style.Triggers>
<DataTrigger Binding="{Binding ShowDecompileOption, Source={x:Static settings:UserSettings.Default}}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
<Separator />
<MenuItem Command="{Binding DataContext.RightClickMenuCommand}">

View File

@ -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<MenuItem>()
.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)

View File

@ -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<string>();
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<string>();
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<int> vector3)
{
ShouldAppend(
$"FVector({vector3.X}, {vector3.Y}, {vector3.Z})");
}
else if (structTag.StructType is TIntVector3<float>
floatVector3)
{
ShouldAppend(
$"FVector({floatVector3.X}, {floatVector3.Y}, {floatVector3.Z})");
}
else if (structTag.StructType is TIntVector2<float>
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<string, List<int>>();
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<int>();
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<int>();
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<int> vector3:
text = $"FVector({vector3.X}, {vector3.Y}, {vector3.Z})";
break;
case TIntVector3<float> floatVector3:
text = $"FVector({floatVector3.X}, {floatVector3.Y}, {floatVector3.Z})";
break;
case TIntVector2<float> 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<string> 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<string, List<int>>();
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<int>();
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<int>();
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..];

View File

@ -229,7 +229,7 @@
<CheckBox Grid.Row="17" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding ReadShaderMaps, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"/>
<TextBlock Grid.Row="18" Grid.Column="0" Text="Show Blueprint Decompile" VerticalAlignment="Center" Margin="0 5 0 5" ToolTip="Shows Decompiled Blueprints in a cpp format" />
<TextBlock Grid.Row="18" Grid.Column="0" Text="Show Blueprint Decompile" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Shows Decompiled Blueprints in a cpp format" />
<CheckBox Grid.Row="18" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding ShowDecompileOption, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"/>
</Grid>