Files
UnrealEngineUWP/Engine/Source/Developer/ScriptDisassembler/Private/ScriptDisassembler.cpp
dave jones2 bcadd23489 UE-151338 - Implement refactored implicit conversion for math types.
Removed container and struct conversions from the script VM. This introduced complexity that the VM doesn't need, nor did it scale for the various struct types that we want to implicitly convert in Blueprints. Instead, the script VM is *only* aware of float<->double conversion. Container and struct conversions have now been moved to BlueprintTypeConversions. Currently, only the existing FVector3f<->FVector3d conversion has been added, but the remaining LWC types will be added in a subsequent change.

During Blueprint compilation of container and struct conversions, we now inject a function call into the bytecode that performs the conversion, which is better suited to the task instead of burdening the VM with the work. One drawback to this technique is that containers are slightly more inefficient when it comes to conversions. They won't know their type(s) ahead of time, which requires dynamically looking up a conversion function at runtime. We can possibly optimize this further, but the generaly recommendation is to avoid implicit conversions of container types when possible.

Additionally, a couple of convenience functions were added to the KismetCastingUtils to help remove a fair amount of boilerplate code that was used for implicit casting in various node types. ScriptCastingUtils.h was also removed since the VM no longer needs to concern itself with complex conversions.

#jira UE-151338
#preflight 629a507fb42820769428c133
#rb phillip.kavan

[CL 20560449 by dave jones2 in ue5-main branch]
2022-06-08 13:50:57 -04:00

1198 lines
34 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
ScriptDisassembler.cpp: Disassembler for Kismet bytecode.
=============================================================================*/
#include "ScriptDisassembler.h"
#include "UObject/Object.h"
#include "UObject/Class.h"
#include "UObject/UObjectHash.h"
#include "UObject/UObjectIterator.h"
#include "UObject/UnrealType.h"
DEFINE_LOG_CATEGORY_STATIC(LogScriptDisassembler, Log, All);
/////////////////////////////////////////////////////
// FKismetBytecodeDisassembler
// Construct a disassembler that will output to the specified archive.
FKismetBytecodeDisassembler::FKismetBytecodeDisassembler(FOutputDevice& InAr)
: Ar(InAr)
{
InitTables();
}
// Disassemble all of the script code in a single structure.
void FKismetBytecodeDisassembler::DisassembleStructure(UFunction* Source)
{
Script.Empty();
Script.Append(Source->Script);
int32 ScriptIndex = 0;
while (ScriptIndex < Script.Num())
{
Ar.Logf(TEXT("Label_0x%X:"), ScriptIndex);
AddIndent();
SerializeExpr(ScriptIndex);
DropIndent();
}
}
// Disassemble all functions in any classes that have matching names.
void FKismetBytecodeDisassembler::DisassembleAllFunctionsInClasses(FOutputDevice& Ar, const FString& ClassnameSubstring)
{
FKismetBytecodeDisassembler Disasm(Ar);
for (TObjectIterator<UClass> ClassIter; ClassIter; ++ClassIter)
{
UClass* Class = *ClassIter;
FString ClassName = Class->GetName();
if (FCString::Strifind(*ClassName, *ClassnameSubstring))
{
Ar.Logf(TEXT("Processing class %s"), *ClassName);
for (TFieldIterator<UFunction> FunctionIter(Class, EFieldIteratorFlags::ExcludeSuper); FunctionIter; ++FunctionIter)
{
UFunction* Function = *FunctionIter;
FString FunctionName = Function->GetName();
Ar.Logf(TEXT(" Processing function %s (%d bytes)"), *FunctionName, Function->Script.Num());
Disasm.DisassembleStructure(Function);
Ar.Logf(TEXT(""));
}
Ar.Logf(TEXT(""));
Ar.Logf(TEXT("-----------"));
Ar.Logf(TEXT(""));
}
}
}
EExprToken FKismetBytecodeDisassembler::SerializeExpr(int32& ScriptIndex)
{
AddIndent();
EExprToken Opcode = (EExprToken)Script[ScriptIndex];
ScriptIndex++;
ProcessCommon(ScriptIndex, Opcode);
DropIndent();
return Opcode;
}
int32 FKismetBytecodeDisassembler::ReadINT(int32& ScriptIndex)
{
int32 Value = Script[ScriptIndex]; ++ScriptIndex;
Value = Value | ((int32)Script[ScriptIndex] << 8); ++ScriptIndex;
Value = Value | ((int32)Script[ScriptIndex] << 16); ++ScriptIndex;
Value = Value | ((int32)Script[ScriptIndex] << 24); ++ScriptIndex;
return Value;
}
uint64 FKismetBytecodeDisassembler::ReadQWORD(int32& ScriptIndex)
{
uint64 Value = Script[ScriptIndex]; ++ScriptIndex;
Value = Value | ((uint64)Script[ScriptIndex] << 8); ++ScriptIndex;
Value = Value | ((uint64)Script[ScriptIndex] << 16); ++ScriptIndex;
Value = Value | ((uint64)Script[ScriptIndex] << 24); ++ScriptIndex;
Value = Value | ((uint64)Script[ScriptIndex] << 32); ++ScriptIndex;
Value = Value | ((uint64)Script[ScriptIndex] << 40); ++ScriptIndex;
Value = Value | ((uint64)Script[ScriptIndex] << 48); ++ScriptIndex;
Value = Value | ((uint64)Script[ScriptIndex] << 56); ++ScriptIndex;
return Value;
}
uint8 FKismetBytecodeDisassembler::ReadBYTE(int32& ScriptIndex)
{
uint8 Value = Script[ScriptIndex]; ++ScriptIndex;
return Value;
}
FString FKismetBytecodeDisassembler::ReadName(int32& ScriptIndex)
{
const FScriptName ConstValue = *(FScriptName*)(Script.GetData() + ScriptIndex);
ScriptIndex += sizeof(FScriptName);
return ScriptNameToName(ConstValue).ToString();
}
uint16 FKismetBytecodeDisassembler::ReadWORD(int32& ScriptIndex)
{
uint16 Value = Script[ScriptIndex]; ++ScriptIndex;
Value = Value | ((uint16)Script[ScriptIndex] << 8); ++ScriptIndex;
return Value;
}
float FKismetBytecodeDisassembler::ReadFLOAT(int32& ScriptIndex)
{
union { float f; int32 i; } Result;
Result.i = ReadINT(ScriptIndex);
return Result.f;
}
double FKismetBytecodeDisassembler::ReadDOUBLE(int32& ScriptIndex)
{
union { double d; int64 i; } Result;
Result.i = ReadQWORD(ScriptIndex);
return Result.d;
}
FVector FKismetBytecodeDisassembler::ReadFVECTOR(int32& ScriptIndex)
{
FVector Vec;
Vec.X = ReadDOUBLE(ScriptIndex);
Vec.Y = ReadDOUBLE(ScriptIndex);
Vec.Z = ReadDOUBLE(ScriptIndex);
return Vec;
}
FRotator FKismetBytecodeDisassembler::ReadFROTATOR(int32& ScriptIndex)
{
FRotator Rotator;
Rotator.Pitch = ReadDOUBLE(ScriptIndex);
Rotator.Yaw = ReadDOUBLE(ScriptIndex);
Rotator.Roll = ReadDOUBLE(ScriptIndex);
return Rotator;
}
FQuat FKismetBytecodeDisassembler::ReadFQUAT(int32& ScriptIndex)
{
FQuat Quat;
Quat.X = ReadDOUBLE(ScriptIndex);
Quat.Y = ReadDOUBLE(ScriptIndex);
Quat.Z = ReadDOUBLE(ScriptIndex);
Quat.W = ReadDOUBLE(ScriptIndex);
return Quat;
}
FTransform FKismetBytecodeDisassembler::ReadFTRANSFORM(int32& ScriptIndex)
{
FTransform Transform;
FQuat TmpRotation = ReadFQUAT(ScriptIndex);
FVector TmpTranslation = ReadFVECTOR(ScriptIndex);
FVector TmpScale = ReadFVECTOR(ScriptIndex);
Transform.SetComponents(TmpRotation, TmpTranslation, TmpScale);
return Transform;
}
CodeSkipSizeType FKismetBytecodeDisassembler::ReadSkipCount(int32& ScriptIndex)
{
#if SCRIPT_LIMIT_BYTECODE_TO_64KB
return ReadWORD(ScriptIndex);
#else
static_assert(sizeof(CodeSkipSizeType) == 4, "Update this code as size changed.");
return ReadINT(ScriptIndex);
#endif
}
FString FKismetBytecodeDisassembler::ReadString(int32& ScriptIndex)
{
const EExprToken Opcode = (EExprToken)Script[ScriptIndex++];
switch (Opcode)
{
case EX_StringConst:
return ReadString8(ScriptIndex);
case EX_UnicodeStringConst:
return ReadString16(ScriptIndex);
default:
checkf(false, TEXT("FKismetBytecodeDisassembler::ReadString - Unexpected opcode. Expected %d or %d, got %d"), (int)EX_StringConst, (int)EX_UnicodeStringConst, (int)Opcode);
break;
}
return FString();
}
FString FKismetBytecodeDisassembler::ReadString8(int32& ScriptIndex)
{
FString Result;
do
{
Result += (ANSICHAR)ReadBYTE(ScriptIndex);
}
while (Script[ScriptIndex-1] != 0);
return Result;
}
FString FKismetBytecodeDisassembler::ReadString16(int32& ScriptIndex)
{
FString Result;
do
{
Result += (TCHAR)ReadWORD(ScriptIndex);
}
while ((Script[ScriptIndex-1] != 0) || (Script[ScriptIndex-2] != 0));
// Inline combine any surrogate pairs in the data when loading into a UTF-32 string
StringConv::InlineCombineSurrogates(Result);
return Result;
}
void FKismetBytecodeDisassembler::ProcessCommon(int32& ScriptIndex, EExprToken Opcode)
{
static const TCHAR* CastNameTable[CST_Max] = {
TEXT("ObjectToInterface"),
TEXT("ObjectToBool"),
TEXT("InterfaceToBool"),
TEXT("DoubleToFloat"),
TEXT("FloatToDouble"),
};
auto PrintVariable = [&ScriptIndex, Opcode, this](FStringView VariableDescription)
{
FProperty* PropertyPtr = ReadPointer<FProperty>(ScriptIndex);
FString PropertyName = TEXT("(null)");
FString PropertyType = TEXT("(null)");
FString ParameterType;
if (PropertyPtr)
{
PropertyName = PropertyPtr->GetName();
FString ExtendedPropertyType;
PropertyType = PropertyPtr->GetCPPType(&ExtendedPropertyType);
PropertyType += ExtendedPropertyType;
if (PropertyPtr->HasAnyPropertyFlags(CPF_ParmFlags))
{
ParameterType = TEXT("(");
if (PropertyPtr->HasAnyPropertyFlags(CPF_Parm))
{
ParameterType += TEXT("Parameter,");
}
if (PropertyPtr->HasAnyPropertyFlags(CPF_OutParm))
{
ParameterType += TEXT("Out,");
}
if (PropertyPtr->HasAnyPropertyFlags(CPF_ReturnParm))
{
ParameterType += TEXT("Return,");
}
if (PropertyPtr->HasAnyPropertyFlags(CPF_ReferenceParm))
{
ParameterType += TEXT("Reference,");
}
if (PropertyPtr->HasAnyPropertyFlags(CPF_ConstParm))
{
ParameterType += TEXT("Const,");
}
int32 LastCommaLocation = ParameterType.Len() - 1;
check(LastCommaLocation > 0);
ParameterType[LastCommaLocation] = TCHAR(')');
}
}
FString Output =
FString::Printf(TEXT("%s $%X: %s of type %s named %s."), *Indents, (int32)Opcode, VariableDescription.GetData(), *PropertyType, *PropertyName);
if (ParameterType.Len() > 0)
{
Output += FString::Printf(TEXT(" Parameter flags: %s."), *ParameterType);
}
Ar.Logf(TEXT("%s"), *Output);
};
switch (Opcode)
{
case EX_Cast:
{
// A type conversion.
uint8 ConversionType = ReadBYTE(ScriptIndex);
check(CastNameTable[ConversionType] != nullptr);
Ar.Logf(TEXT("%s $%X: Cast of type %d (%s)"), *Indents, (int32)Opcode, ConversionType, CastNameTable[ConversionType]);
AddIndent();
Ar.Logf(TEXT("%s Argument:"), *Indents);
SerializeExpr(ScriptIndex);
DropIndent();
break;
}
case EX_SetSet:
{
Ar.Logf(TEXT("%s $%X: set set"), *Indents, (int32)Opcode);
SerializeExpr(ScriptIndex);
ReadINT(ScriptIndex);
while (SerializeExpr(ScriptIndex) != EX_EndSet)
{
// Set contents
}
break;
}
case EX_EndSet:
{
Ar.Logf(TEXT("%s $%X: EX_EndSet"), *Indents, (int32)Opcode);
break;
}
case EX_SetConst:
{
FProperty* InnerProp = ReadPointer<FProperty>(ScriptIndex);
int32 Num = ReadINT(ScriptIndex);
Ar.Logf(TEXT("%s $%X: set set const - elements number: %d, inner property: %s"), *Indents, (int32)Opcode, Num, *GetNameSafe(InnerProp));
while (SerializeExpr(ScriptIndex) != EX_EndSetConst)
{
// Set contents
}
break;
}
case EX_EndSetConst:
{
Ar.Logf(TEXT("%s $%X: EX_EndSetConst"), *Indents, (int32)Opcode);
break;
}
case EX_SetMap:
{
Ar.Logf(TEXT("%s $%X: set map"), *Indents, (int32)Opcode);
SerializeExpr(ScriptIndex);
ReadINT(ScriptIndex);
while (SerializeExpr(ScriptIndex) != EX_EndMap)
{
// Map contents
}
break;
}
case EX_EndMap:
{
Ar.Logf(TEXT("%s $%X: EX_EndMap"), *Indents, (int32)Opcode);
break;
}
case EX_MapConst:
{
FProperty* KeyProp = ReadPointer<FProperty>(ScriptIndex);
FProperty* ValProp = ReadPointer<FProperty>(ScriptIndex);
int32 Num = ReadINT(ScriptIndex);
Ar.Logf(TEXT("%s $%X: set map const - elements number: %d, key property: %s, val property: %s"), *Indents, (int32)Opcode, Num, *GetNameSafe(KeyProp), *GetNameSafe(ValProp));
while (SerializeExpr(ScriptIndex) != EX_EndMapConst)
{
// Map contents
}
break;
}
case EX_EndMapConst:
{
Ar.Logf(TEXT("%s $%X: EX_EndMapConst"), *Indents, (int32)Opcode);
break;
}
case EX_ObjToInterfaceCast:
{
// A conversion from an object variable to a native interface variable.
// We use a different bytecode to avoid the branching each time we process a cast token
// the interface class to convert to
UClass* InterfaceClass = ReadPointer<UClass>(ScriptIndex);
Ar.Logf(TEXT("%s $%X: ObjToInterfaceCast to %s"), *Indents, (int32)Opcode, *InterfaceClass->GetName());
SerializeExpr( ScriptIndex );
break;
}
case EX_CrossInterfaceCast:
{
// A conversion from one interface variable to a different interface variable.
// We use a different bytecode to avoid the branching each time we process a cast token
// the interface class to convert to
UClass* InterfaceClass = ReadPointer<UClass>(ScriptIndex);
Ar.Logf(TEXT("%s $%X: InterfaceToInterfaceCast to %s"), *Indents, (int32)Opcode, *InterfaceClass->GetName());
SerializeExpr( ScriptIndex );
break;
}
case EX_InterfaceToObjCast:
{
// A conversion from an interface variable to a object variable.
// We use a different bytecode to avoid the branching each time we process a cast token
// the interface class to convert to
UClass* ObjectClass = ReadPointer<UClass>(ScriptIndex);
Ar.Logf(TEXT("%s $%X: InterfaceToObjCast to %s"), *Indents, (int32)Opcode, *ObjectClass->GetName());
SerializeExpr( ScriptIndex );
break;
}
case EX_Let:
{
Ar.Logf(TEXT("%s $%X: Let (Variable = Expression)"), *Indents, (int32)Opcode);
AddIndent();
ReadPointer<FProperty>(ScriptIndex);
// Variable expr.
Ar.Logf(TEXT("%s Variable:"), *Indents);
SerializeExpr( ScriptIndex );
// Assignment expr.
Ar.Logf(TEXT("%s Expression:"), *Indents);
SerializeExpr( ScriptIndex );
DropIndent();
break;
}
case EX_LetObj:
case EX_LetWeakObjPtr:
{
if( Opcode == EX_LetObj )
{
Ar.Logf(TEXT("%s $%X: Let Obj (Variable = Expression)"), *Indents, (int32)Opcode);
}
else
{
Ar.Logf(TEXT("%s $%X: Let WeakObjPtr (Variable = Expression)"), *Indents, (int32)Opcode);
}
AddIndent();
// Variable expr.
Ar.Logf(TEXT("%s Variable:"), *Indents);
SerializeExpr( ScriptIndex );
// Assignment expr.
Ar.Logf(TEXT("%s Expression:"), *Indents);
SerializeExpr( ScriptIndex );
DropIndent();
break;
}
case EX_LetBool:
{
Ar.Logf(TEXT("%s $%X: LetBool (Variable = Expression)"), *Indents, (int32)Opcode);
AddIndent();
// Variable expr.
Ar.Logf(TEXT("%s Variable:"), *Indents);
SerializeExpr( ScriptIndex );
// Assignment expr.
Ar.Logf(TEXT("%s Expression:"), *Indents);
SerializeExpr( ScriptIndex );
DropIndent();
break;
}
case EX_LetValueOnPersistentFrame:
{
Ar.Logf(TEXT("%s $%X: LetValueOnPersistentFrame"), *Indents, (int32)Opcode);
AddIndent();
auto Prop = ReadPointer<FProperty>(ScriptIndex);
Ar.Logf(TEXT("%s Destination variable: %s, offset: %d"), *Indents, *GetNameSafe(Prop),
Prop ? Prop->GetOffset_ForDebug() : 0);
Ar.Logf(TEXT("%s Expression:"), *Indents);
SerializeExpr(ScriptIndex);
DropIndent();
break;
}
case EX_StructMemberContext:
{
Ar.Logf(TEXT("%s $%X: Struct member context "), *Indents, (int32)Opcode);
AddIndent();
FProperty* Prop = ReadPointer<FProperty>(ScriptIndex);
Ar.Logf(TEXT("%s Member named %s @ offset %d"), *Indents, *(Prop->GetName()),
Prop->GetOffset_ForDebug()); // although that isn't a UFunction, we are not going to indirect the props of a struct, so this should be fine
Ar.Logf(TEXT("%s Expression to struct:"), *Indents);
SerializeExpr( ScriptIndex );
DropIndent();
break;
}
case EX_LetDelegate:
{
Ar.Logf(TEXT("%s $%X: LetDelegate (Variable = Expression)"), *Indents, (int32)Opcode);
AddIndent();
// Variable expr.
Ar.Logf(TEXT("%s Variable:"), *Indents);
SerializeExpr( ScriptIndex );
// Assignment expr.
Ar.Logf(TEXT("%s Expression:"), *Indents);
SerializeExpr( ScriptIndex );
DropIndent();
break;
}
case EX_LocalVirtualFunction:
{
FString FunctionName = ReadName(ScriptIndex);
Ar.Logf(TEXT("%s $%X: Local Virtual Script Function named %s"), *Indents, (int32)Opcode, *FunctionName);
while (SerializeExpr(ScriptIndex) != EX_EndFunctionParms)
{
}
break;
}
case EX_LocalFinalFunction:
{
UStruct* StackNode = ReadPointer<UStruct>(ScriptIndex);
Ar.Logf(TEXT("%s $%X: Local Final Script Function (stack node %s::%s)"), *Indents, (int32)Opcode, StackNode ? *StackNode->GetOuter()->GetName() : TEXT("(null)"), StackNode ? *StackNode->GetName() : TEXT("(null)"));
while (SerializeExpr(ScriptIndex) != EX_EndFunctionParms)
{
// Params
}
break;
}
case EX_LetMulticastDelegate:
{
Ar.Logf(TEXT("%s $%X: LetMulticastDelegate (Variable = Expression)"), *Indents, (int32)Opcode);
AddIndent();
// Variable expr.
Ar.Logf(TEXT("%s Variable:"), *Indents);
SerializeExpr( ScriptIndex );
// Assignment expr.
Ar.Logf(TEXT("%s Expression:"), *Indents);
SerializeExpr( ScriptIndex );
DropIndent();
break;
}
case EX_ComputedJump:
{
Ar.Logf(TEXT("%s $%X: Computed Jump, offset specified by expression:"), *Indents, (int32)Opcode);
AddIndent();
SerializeExpr( ScriptIndex );
DropIndent();
break;
}
case EX_Jump:
{
CodeSkipSizeType SkipCount = ReadSkipCount(ScriptIndex);
Ar.Logf(TEXT("%s $%X: Jump to offset 0x%X"), *Indents, (int32)Opcode, SkipCount);
break;
}
case EX_LocalVariable:
{
PrintVariable(TEXT("Local variable"));
break;
}
case EX_DefaultVariable:
{
PrintVariable(TEXT("Default variable"));
break;
}
case EX_InstanceVariable:
{
PrintVariable(TEXT("Instance variable"));
break;
}
case EX_LocalOutVariable:
{
PrintVariable(TEXT("Local out variable"));
break;
}
case EX_ClassSparseDataVariable:
{
PrintVariable(TEXT("Class sparse data variable"));
break;
}
case EX_InterfaceContext:
{
Ar.Logf(TEXT("%s $%X: EX_InterfaceContext:"), *Indents, (int32)Opcode);
SerializeExpr(ScriptIndex);
break;
}
case EX_DeprecatedOp4A:
{
Ar.Logf(TEXT("%s $%X: This opcode has been removed and does nothing."), *Indents, (int32)Opcode);
break;
}
case EX_Nothing:
{
Ar.Logf(TEXT("%s $%X: EX_Nothing"), *Indents, (int32)Opcode);
break;
}
case EX_EndOfScript:
{
Ar.Logf(TEXT("%s $%X: EX_EndOfScript"), *Indents, (int32)Opcode);
break;
}
case EX_EndFunctionParms:
{
Ar.Logf(TEXT("%s $%X: EX_EndFunctionParms"), *Indents, (int32)Opcode);
break;
}
case EX_EndStructConst:
{
Ar.Logf(TEXT("%s $%X: EX_EndStructConst"), *Indents, (int32)Opcode);
break;
}
case EX_EndArray:
{
Ar.Logf(TEXT("%s $%X: EX_EndArray"), *Indents, (int32)Opcode);
break;
}
case EX_EndArrayConst:
{
Ar.Logf(TEXT("%s $%X: EX_EndArrayConst"), *Indents, (int32)Opcode);
break;
}
case EX_IntZero:
{
Ar.Logf(TEXT("%s $%X: EX_IntZero"), *Indents, (int32)Opcode);
break;
}
case EX_IntOne:
{
Ar.Logf(TEXT("%s $%X: EX_IntOne"), *Indents, (int32)Opcode);
break;
}
case EX_True:
{
Ar.Logf(TEXT("%s $%X: EX_True"), *Indents, (int32)Opcode);
break;
}
case EX_False:
{
Ar.Logf(TEXT("%s $%X: EX_False"), *Indents, (int32)Opcode);
break;
}
case EX_NoObject:
{
Ar.Logf(TEXT("%s $%X: EX_NoObject"), *Indents, (int32)Opcode);
break;
}
case EX_NoInterface:
{
Ar.Logf(TEXT("%s $%X: EX_NoObject"), *Indents, (int32)Opcode);
break;
}
case EX_Self:
{
Ar.Logf(TEXT("%s $%X: EX_Self"), *Indents, (int32)Opcode);
break;
}
case EX_EndParmValue:
{
Ar.Logf(TEXT("%s $%X: EX_EndParmValue"), *Indents, (int32)Opcode);
break;
}
case EX_Return:
{
Ar.Logf(TEXT("%s $%X: Return expression"), *Indents, (int32)Opcode);
SerializeExpr( ScriptIndex ); // Return expression.
break;
}
case EX_CallMath:
{
UStruct* StackNode = ReadPointer<UStruct>(ScriptIndex);
Ar.Logf(TEXT("%s $%X: Call Math (stack node %s::%s)"), *Indents, (int32)Opcode, *GetNameSafe(StackNode ? StackNode->GetOuter() : nullptr), *GetNameSafe(StackNode));
while (SerializeExpr(ScriptIndex) != EX_EndFunctionParms)
{
// Params
}
break;
}
case EX_FinalFunction:
{
UStruct* StackNode = ReadPointer<UStruct>(ScriptIndex);
Ar.Logf(TEXT("%s $%X: Final Function (stack node %s::%s)"), *Indents, (int32)Opcode, StackNode ? *StackNode->GetOuter()->GetName() : TEXT("(null)"), StackNode ? *StackNode->GetName() : TEXT("(null)"));
while (SerializeExpr(ScriptIndex) != EX_EndFunctionParms)
{
// Params
}
break;
}
case EX_CallMulticastDelegate:
{
UStruct* StackNode = ReadPointer<UStruct>(ScriptIndex);
Ar.Logf(TEXT("%s $%X: CallMulticastDelegate (signature %s::%s) delegate:"), *Indents, (int32)Opcode, StackNode ? *StackNode->GetOuter()->GetName() : TEXT("(null)"), StackNode ? *StackNode->GetName() : TEXT("(null)"));
SerializeExpr( ScriptIndex );
Ar.Logf(TEXT("Params:"));
while (SerializeExpr(ScriptIndex) != EX_EndFunctionParms)
{
// Params
}
break;
}
case EX_VirtualFunction:
{
FString FunctionName = ReadName(ScriptIndex);
Ar.Logf(TEXT("%s $%X: Virtual Function named %s"), *Indents, (int32)Opcode, *FunctionName);
while (SerializeExpr(ScriptIndex) != EX_EndFunctionParms)
{
}
break;
}
case EX_ClassContext:
case EX_Context:
case EX_Context_FailSilent:
{
Ar.Logf(TEXT("%s $%X: %s"), *Indents, (int32)Opcode, Opcode == EX_ClassContext ? TEXT("Class Context") : TEXT("Context"));
AddIndent();
// Object expression.
Ar.Logf(TEXT("%s ObjectExpression:"), *Indents);
SerializeExpr( ScriptIndex );
if (Opcode == EX_Context_FailSilent)
{
Ar.Logf(TEXT(" Can fail silently on access none "));
}
// Code offset for NULL expressions.
CodeSkipSizeType SkipCount = ReadSkipCount(ScriptIndex);
Ar.Logf(TEXT("%s Skip 0x%X bytes to offset 0x%X"), *Indents, SkipCount, ScriptIndex + sizeof(FField*) + SkipCount);
// Property corresponding to the r-value data, in case the l-value needs to be mem-zero'd
FField* Field = ReadPointer<FField>(ScriptIndex);
Ar.Logf(TEXT("%s R-Value Property: %s"), *Indents, Field ? *Field->GetName() : TEXT("(null)"));
// Context expression.
Ar.Logf(TEXT("%s ContextExpression:"), *Indents);
SerializeExpr( ScriptIndex );
DropIndent();
break;
}
case EX_IntConst:
{
int32 ConstValue = ReadINT(ScriptIndex);
Ar.Logf(TEXT("%s $%X: literal int32 %d"), *Indents, (int32)Opcode, ConstValue);
break;
}
case EX_Int64Const:
{
int64 ConstValue = ReadQWORD(ScriptIndex);
Ar.Logf(TEXT("%s $%X: literal int64 0x%" INT64_X_FMT), *Indents, (int32)Opcode, ConstValue);
break;
}
case EX_UInt64Const:
{
uint64 ConstValue = ReadQWORD(ScriptIndex);
Ar.Logf(TEXT("%s $%X: literal uint64 0x%" UINT64_X_FMT), *Indents, (int32)Opcode, ConstValue);
break;
}
case EX_SkipOffsetConst:
{
CodeSkipSizeType ConstValue = ReadSkipCount(ScriptIndex);
Ar.Logf(TEXT("%s $%X: literal CodeSkipSizeType 0x%X"), *Indents, (int32)Opcode, ConstValue);
break;
}
case EX_FloatConst:
{
float ConstValue = ReadFLOAT(ScriptIndex);
Ar.Logf(TEXT("%s $%X: literal float %f"), *Indents, (int32)Opcode, ConstValue);
break;
}
case EX_DoubleConst:
{
double ConstValue = ReadDOUBLE(ScriptIndex);
Ar.Logf(TEXT("%s $%X: literal double %lf"), *Indents, (int32)Opcode, ConstValue);
break;
}
case EX_StringConst:
{
FString ConstValue = ReadString8(ScriptIndex);
Ar.Logf(TEXT("%s $%X: literal ansi string \"%s\""), *Indents, (int32)Opcode, *ConstValue);
break;
}
case EX_UnicodeStringConst:
{
FString ConstValue = ReadString16(ScriptIndex);
Ar.Logf(TEXT("%s $%X: literal unicode string \"%s\""), *Indents, (int32)Opcode, *ConstValue);
break;
}
case EX_TextConst:
{
// What kind of text are we dealing with?
const EBlueprintTextLiteralType TextLiteralType = (EBlueprintTextLiteralType)Script[ScriptIndex++];
switch (TextLiteralType)
{
case EBlueprintTextLiteralType::Empty:
{
Ar.Logf(TEXT("%s $%X: literal text - empty"), *Indents, (int32)Opcode);
}
break;
case EBlueprintTextLiteralType::LocalizedText:
{
const FString SourceString = ReadString(ScriptIndex);
const FString KeyString = ReadString(ScriptIndex);
const FString Namespace = ReadString(ScriptIndex);
Ar.Logf(TEXT("%s $%X: literal text - localized text { namespace: \"%s\", key: \"%s\", source: \"%s\" }"), *Indents, (int32)Opcode, *Namespace, *KeyString, *SourceString);
}
break;
case EBlueprintTextLiteralType::InvariantText:
{
const FString SourceString = ReadString(ScriptIndex);
Ar.Logf(TEXT("%s $%X: literal text - invariant text: \"%s\""), *Indents, (int32)Opcode, *SourceString);
}
break;
case EBlueprintTextLiteralType::LiteralString:
{
const FString SourceString = ReadString(ScriptIndex);
Ar.Logf(TEXT("%s $%X: literal text - literal string: \"%s\""), *Indents, (int32)Opcode, *SourceString);
}
break;
case EBlueprintTextLiteralType::StringTableEntry:
{
ReadPointer<UObject>(ScriptIndex); // String Table asset (if any)
const FString TableIdString = ReadString(ScriptIndex);
const FString KeyString = ReadString(ScriptIndex);
Ar.Logf(TEXT("%s $%X: literal text - string table entry { tableid: \"%s\", key: \"%s\" }"), *Indents, (int32)Opcode, *TableIdString, *KeyString);
}
break;
default:
checkf(false, TEXT("Unknown EBlueprintTextLiteralType! Please update FKismetBytecodeDisassembler::ProcessCommon to handle this type of text."));
break;
}
break;
}
case EX_PropertyConst:
{
FProperty* Pointer = ReadPointer<FProperty>(ScriptIndex);
Ar.Logf(TEXT("%s $%X: EX_PropertyConst (%p:%s)"), *Indents, (int32)Opcode, Pointer, Pointer ? *Pointer->GetName() : TEXT("(null)"));
break;
}
case EX_ObjectConst:
{
UObject* Pointer = ReadPointer<UObject>(ScriptIndex);
Ar.Logf(TEXT("%s $%X: EX_ObjectConst (%p:%s)"), *Indents, (int32)Opcode, Pointer, Pointer ? (Pointer->IsValidLowLevel() ? *Pointer->GetFullName() : TEXT("(not a valid object)")) : TEXT("(null)"));
break;
}
case EX_SoftObjectConst:
{
Ar.Logf(TEXT("%s $%X: EX_SoftObjectConst"), *Indents, (int32)Opcode);
SerializeExpr(ScriptIndex);
break;
}
case EX_FieldPathConst:
{
Ar.Logf(TEXT("%s $%X: EX_FieldPathConst"), *Indents, (int32)Opcode);
SerializeExpr(ScriptIndex);
break;
}
case EX_NameConst:
{
FString ConstValue = ReadName(ScriptIndex);
Ar.Logf(TEXT("%s $%X: literal name %s"), *Indents, (int32)Opcode, *ConstValue);
break;
}
case EX_RotationConst:
{
const FRotator Rotator = ReadFROTATOR(ScriptIndex);
Ar.Logf(TEXT("%s $%X: literal rotation (%f,%f,%f)"), *Indents, (int32)Opcode, Rotator.Pitch, Rotator.Yaw, Rotator.Roll);
break;
}
case EX_VectorConst:
{
FVector Vec = ReadFVECTOR(ScriptIndex);
Ar.Logf(TEXT("%s $%X: literal vector (%f,%f,%f)"), *Indents, (int32)Opcode, Vec.X, Vec.Y, Vec.Z);
break;
}
case EX_Vector3fConst:
{
FVector3f Vec = (FVector3f)ReadFVECTOR(ScriptIndex);
Ar.Logf(TEXT("%s $%X: literal float vector (%f,%f,%f)"), *Indents, (int32)Opcode, Vec.X, Vec.Y, Vec.Z);
break;
}
case EX_TransformConst:
{
const FTransform Transform = ReadFTRANSFORM(ScriptIndex);
const FQuat Rotation = Transform.GetRotation();
const FVector Translation = Transform.GetTranslation();
const FVector Scale = Transform.GetScale3D();
Ar.Logf(TEXT("%s $%X: literal transform R(%f,%f,%f,%f) T(%f,%f,%f) S(%f,%f,%f)"),
*Indents,
(int32)Opcode,
Rotation.X, Rotation.Y, Rotation.Z, Rotation.W,
Translation.X, Translation.Y, Translation.Z,
Scale.X, Scale.Y, Scale.Z);
break;
}
case EX_StructConst:
{
UScriptStruct* Struct = ReadPointer<UScriptStruct>(ScriptIndex);
int32 SerializedSize = ReadINT(ScriptIndex);
Ar.Logf(TEXT("%s $%X: literal struct %s (serialized size: %d)"), *Indents, (int32)Opcode, *Struct->GetName(), SerializedSize);
while (SerializeExpr(ScriptIndex) != EX_EndStructConst)
{
// struct contents
}
break;
}
case EX_SetArray:
{
Ar.Logf(TEXT("%s $%X: set array"), *Indents, (int32)Opcode);
SerializeExpr(ScriptIndex);
while (SerializeExpr(ScriptIndex) != EX_EndArray)
{
// Array contents
}
break;
}
case EX_ArrayConst:
{
FProperty* InnerProp = ReadPointer<FProperty>(ScriptIndex);
int32 Num = ReadINT(ScriptIndex);
Ar.Logf(TEXT("%s $%X: set array const - elements number: %d, inner property: %s"), *Indents, (int32)Opcode, Num, *GetNameSafe(InnerProp));
while (SerializeExpr(ScriptIndex) != EX_EndArrayConst)
{
// Array contents
}
break;
}
case EX_ByteConst:
{
uint8 ConstValue = ReadBYTE(ScriptIndex);
Ar.Logf(TEXT("%s $%X: literal byte %d"), *Indents, (int32)Opcode, ConstValue);
break;
}
case EX_IntConstByte:
{
int32 ConstValue = ReadBYTE(ScriptIndex);
Ar.Logf(TEXT("%s $%X: literal int %d"), *Indents, (int32)Opcode, ConstValue);
break;
}
case EX_MetaCast:
{
UClass* Class = ReadPointer<UClass>(ScriptIndex);
Ar.Logf(TEXT("%s $%X: MetaCast to %s of expr:"), *Indents, (int32)Opcode, *Class->GetName());
SerializeExpr( ScriptIndex );
break;
}
case EX_DynamicCast:
{
UClass* Class = ReadPointer<UClass>(ScriptIndex);
Ar.Logf(TEXT("%s $%X: DynamicCast to %s of expr:"), *Indents, (int32)Opcode, *Class->GetName());
SerializeExpr( ScriptIndex );
break;
}
case EX_JumpIfNot:
{
// Code offset.
CodeSkipSizeType SkipCount = ReadSkipCount(ScriptIndex);
Ar.Logf(TEXT("%s $%X: Jump to offset 0x%X if not expr:"), *Indents, (int32)Opcode, SkipCount);
// Boolean expr.
SerializeExpr( ScriptIndex );
break;
}
case EX_Assert:
{
uint16 LineNumber = ReadWORD(ScriptIndex);
uint8 InDebugMode = ReadBYTE(ScriptIndex);
Ar.Logf(TEXT("%s $%X: assert at line %d, in debug mode = %d with expr:"), *Indents, (int32)Opcode, LineNumber, InDebugMode);
SerializeExpr( ScriptIndex ); // Assert expr.
break;
}
case EX_Skip:
{
CodeSkipSizeType W = ReadSkipCount(ScriptIndex);
Ar.Logf(TEXT("%s $%X: possibly skip 0x%X bytes of expr:"), *Indents, (int32)Opcode, W);
// Expression to possibly skip.
SerializeExpr( ScriptIndex );
break;
}
case EX_InstanceDelegate:
{
// the name of the function assigned to the delegate.
FString FuncName = ReadName(ScriptIndex);
Ar.Logf(TEXT("%s $%X: instance delegate function named %s"), *Indents, (int32)Opcode, *FuncName);
break;
}
case EX_AddMulticastDelegate:
{
Ar.Logf(TEXT("%s $%X: Add MC delegate"), *Indents, (int32)Opcode);
SerializeExpr( ScriptIndex );
SerializeExpr( ScriptIndex );
break;
}
case EX_RemoveMulticastDelegate:
{
Ar.Logf(TEXT("%s $%X: Remove MC delegate"), *Indents, (int32)Opcode);
SerializeExpr( ScriptIndex );
SerializeExpr( ScriptIndex );
break;
}
case EX_ClearMulticastDelegate:
{
Ar.Logf(TEXT("%s $%X: Clear MC delegate"), *Indents, (int32)Opcode);
SerializeExpr( ScriptIndex );
break;
}
case EX_BindDelegate:
{
// the name of the function assigned to the delegate.
FString FuncName = ReadName(ScriptIndex);
Ar.Logf(TEXT("%s $%X: BindDelegate '%s' "), *Indents, (int32)Opcode, *FuncName);
Ar.Logf(TEXT("%s Delegate:"), *Indents);
SerializeExpr( ScriptIndex );
Ar.Logf(TEXT("%s Object:"), *Indents);
SerializeExpr( ScriptIndex );
break;
}
case EX_PushExecutionFlow:
{
CodeSkipSizeType SkipCount = ReadSkipCount(ScriptIndex);
Ar.Logf(TEXT("%s $%X: FlowStack.Push(0x%X);"), *Indents, (int32)Opcode, SkipCount);
break;
}
case EX_PopExecutionFlow:
{
Ar.Logf(TEXT("%s $%X: if (FlowStack.Num()) { jump to statement at FlowStack.Pop(); } else { ERROR!!! }"), *Indents, (int32)Opcode);
break;
}
case EX_PopExecutionFlowIfNot:
{
Ar.Logf(TEXT("%s $%X: if (!condition) { if (FlowStack.Num()) { jump to statement at FlowStack.Pop(); } else { ERROR!!! } }"), *Indents, (int32)Opcode);
// Boolean expr.
SerializeExpr( ScriptIndex );
break;
}
case EX_Breakpoint:
{
Ar.Logf(TEXT("%s $%X: <<< BREAKPOINT >>>"), *Indents, (int32)Opcode);
break;
}
case EX_WireTracepoint:
{
Ar.Logf(TEXT("%s $%X: .. wire debug site .."), *Indents, (int32)Opcode);
break;
}
case EX_InstrumentationEvent:
{
const uint8 EventType = ReadBYTE(ScriptIndex);
switch (EventType)
{
case EScriptInstrumentation::InlineEvent:
Ar.Logf(TEXT("%s $%X: .. instrumented inline event .."), *Indents, (int32)Opcode);
break;
case EScriptInstrumentation::Stop:
Ar.Logf(TEXT("%s $%X: .. instrumented event stop .."), *Indents, (int32)Opcode);
break;
case EScriptInstrumentation::PureNodeEntry:
Ar.Logf(TEXT("%s $%X: .. instrumented pure node entry site .."), *Indents, (int32)Opcode);
break;
case EScriptInstrumentation::NodeDebugSite:
Ar.Logf(TEXT("%s $%X: .. instrumented debug site .."), *Indents, (int32)Opcode);
break;
case EScriptInstrumentation::NodeEntry:
Ar.Logf(TEXT("%s $%X: .. instrumented wire entry site .."), *Indents, (int32)Opcode);
break;
case EScriptInstrumentation::NodeExit:
Ar.Logf(TEXT("%s $%X: .. instrumented wire exit site .."), *Indents, (int32)Opcode);
break;
case EScriptInstrumentation::PushState:
Ar.Logf(TEXT("%s $%X: .. push execution state .."), *Indents, (int32)Opcode);
break;
case EScriptInstrumentation::RestoreState:
Ar.Logf(TEXT("%s $%X: .. restore execution state .."), *Indents, (int32)Opcode);
break;
case EScriptInstrumentation::ResetState:
Ar.Logf(TEXT("%s $%X: .. reset execution state .."), *Indents, (int32)Opcode);
break;
case EScriptInstrumentation::SuspendState:
Ar.Logf(TEXT("%s $%X: .. suspend execution state .."), *Indents, (int32)Opcode);
break;
case EScriptInstrumentation::PopState:
Ar.Logf(TEXT("%s $%X: .. pop execution state .."), *Indents, (int32)Opcode);
break;
case EScriptInstrumentation::TunnelEndOfThread:
Ar.Logf(TEXT("%s $%X: .. tunnel end of thread .."), *Indents, (int32)Opcode);
break;
}
break;
}
case EX_Tracepoint:
{
Ar.Logf(TEXT("%s $%X: .. debug site .."), *Indents, (int32)Opcode);
break;
}
case EX_SwitchValue:
{
const uint16 NumCases = ReadWORD(ScriptIndex);
const CodeSkipSizeType AfterSkip = ReadSkipCount(ScriptIndex);
Ar.Logf(TEXT("%s $%X: Switch Value %d cases, end in 0x%X"), *Indents, (int32)Opcode, NumCases, AfterSkip);
AddIndent();
Ar.Logf(TEXT("%s Index:"), *Indents);
SerializeExpr(ScriptIndex);
for (uint16 CaseIndex = 0; CaseIndex < NumCases; ++CaseIndex)
{
Ar.Logf(TEXT("%s [%d] Case Index (label: 0x%X):"), *Indents, CaseIndex, ScriptIndex);
SerializeExpr(ScriptIndex); // case index value term
const CodeSkipSizeType OffsetToNextCase = ReadSkipCount(ScriptIndex);
Ar.Logf(TEXT("%s [%d] Offset to the next case: 0x%X"), *Indents, CaseIndex, OffsetToNextCase);
Ar.Logf(TEXT("%s [%d] Case Result:"), *Indents, CaseIndex);
SerializeExpr(ScriptIndex); // case term
}
Ar.Logf(TEXT("%s Default result (label: 0x%X):"), *Indents, ScriptIndex);
SerializeExpr(ScriptIndex);
Ar.Logf(TEXT("%s (label: 0x%X)"), *Indents, ScriptIndex);
DropIndent();
break;
}
case EX_ArrayGetByRef:
{
Ar.Logf(TEXT("%s $%X: Array Get-by-Ref Index"), *Indents, (int32)Opcode);
AddIndent();
SerializeExpr(ScriptIndex);
SerializeExpr(ScriptIndex);
DropIndent();
break;
}
default:
{
// This should never occur.
UE_LOG(LogScriptDisassembler, Warning, TEXT("Unknown bytecode 0x%02X; ignoring it"), (uint8)Opcode );
break;
}
}
}
void FKismetBytecodeDisassembler::InitTables()
{
}