#include "LuaCall.h" #include "LuprexGameModeBase.h" #include "StringDecoder.h" #include "EdGraphSchema_K2.h" static void FatalBlueprintError(const TCHAR *message) { FBlueprintExceptionInfo ExceptionInfo(EBlueprintExceptionType::FatalError, FText::FromString(FString(message))); FBlueprintCoreDelegates::ThrowScriptException(FFrame::GetThreadLocalTopStackFrame()->Object, *FFrame::GetThreadLocalTopStackFrame(), ExceptionInfo); } static void CheckNotEmpty(const FlxStreamBuffer &sb) { if (sb.empty()) { FatalBlueprintError(TEXT("Must use LuaCallBegin before other LuaCall steps")); } } static constexpr uint64_t ParseNameAsToken(std::string_view str) { uint64_t result = 0; uint64_t maxint = uint64_t(-1); // Leading zeros are not allowed. if ((!str.empty()) && (str[0]=='0')) return 0; for (int i = 0; i < int(str.size()); i++) { char c = str[i]; uint64_t digit = 0; if ((c >= '0') && (c <= '9')) { digit = uint64_t(c - '0'); } else if ((c >= 'a') && (c <= 'z')) { digit = uint64_t(c - 'a' + 10); } else if ((c >= 'A') && (c <= 'Z')) { digit = uint64_t(c - 'A' + 10); } else { return maxint; } // Multiply existing number by 36, then add the digit. // We have two checks to prevent integer overflow. if (result > (maxint / 36)) return 0; result *= 36; if (digit > (maxint - result)) return 0; result += digit; } return result; } ///////////////////////////////////////////////////////////////// // // The lua function prototype parser. // ///////////////////////////////////////////////////////////////// bool FlxParsedProto::IsLiteral(const TCHAR *text) { return ((NextToken < Tokens.Num()) && (Tokens[NextToken] == text)); } bool FlxParsedProto::IsIdent() { return ((NextToken < Tokens.Num()) && (FChar::IsAlpha(Tokens[NextToken][0]))); } void FlxParsedProto::Empty() { ErrorMessage = TEXT(""); Tokens.Empty(); NextToken = 0; ClassName = TEXT(""); FunctionName = TEXT(""); Arguments.Empty(); ReturnValues.Empty(); ExtraReturnValues = false; } void FlxParsedProto::Syntax() { FString Message; if (Tokens.Num() == 0) { Message = TEXT("Function prototype cannot be blank"); } for (int i = 0; i < Tokens.Num(); i++) { if (i == NextToken) { Message.Append(TEXT(" ? ")); } else { if ((i > 0) && (FChar::IsAlpha(Tokens[i][0])) && (FChar::IsAlpha(Tokens[i-1][0]))) { Message.Append(TEXT(" ")); } } Message.Append(Tokens[i]); } Empty(); ErrorMessage = Message; } void FlxParsedProto::Parse(const FString &str) { Empty(); // Step one: tokenize. int offset = 0; while (offset < str.Len()) { TCHAR c = str[offset]; if (FChar::IsWhitespace(c)) { offset++; } else if (FChar::IsAlpha(c)) { int lo = offset; while ((offset < str.Len()) && FChar::IsAlnum(str[offset])) offset++; Tokens.Add(str.Mid(lo, offset-lo)); } else if (str.Mid(offset, 3) == TEXT("...")) { Tokens.Add(str.Mid(offset, 3)); offset += 3; } else if (FChar::IsPunct(c)) { Tokens.Add(str.Mid(offset, 1)); offset += 1; } else { Empty(); ErrorMessage = FString::Printf(TEXT("%s ? %s"), *str.Mid(0, offset), *str.Mid(offset)); return; } } NextToken = 0; // Step two: Parse. if (!IsLiteral(TEXT("*")) && !IsIdent()) return Syntax(); ClassName = Tokens[NextToken++]; if (!IsLiteral(TEXT("."))) return Syntax(); NextToken++; if (!IsIdent()) return Syntax(); FunctionName = Tokens[NextToken++]; if (!IsLiteral(TEXT("("))) return Syntax(); NextToken++; if (IsIdent()) { while (true) { if (!IsIdent()) return Syntax(); FString Type = Tokens[NextToken++]; if (!IsIdent()) return Syntax(); FString Name = Tokens[NextToken++]; Arguments.Add(Pin(Type, Name)); if (!IsLiteral(TEXT(","))) break; NextToken++; } } if (!IsLiteral(TEXT(")"))) return Syntax(); NextToken++; if (IsLiteral(TEXT(":"))) { NextToken++; while (true) { if (IsLiteral(TEXT("..."))) { ExtraReturnValues = true; NextToken++; break; } else if (IsIdent()) { FString Type = Tokens[NextToken++]; if (!IsIdent()) return Syntax(); FString Name = Tokens[NextToken++]; ReturnValues.Add(Pin(Type, Name)); if (!IsLiteral(TEXT(","))) break; NextToken++; } else { return Syntax(); } } } if (NextToken != Tokens.Num()) return Syntax(); } ///////////////////////////////////////////////////////////////// // // General functions of the Lua Call Library. // ///////////////////////////////////////////////////////////////// UFunction *UlxLuaCallLibrary::GetArgumentPacker(const FString &Type) { FString LType = Type.ToLower(); FName PackerName(FString::Printf(TEXT("LuaCallArgument_%s"), *LType)); return UlxLuaCallLibrary::StaticClass()->FindFunctionByName(PackerName); } UFunction *UlxLuaCallLibrary::GetReturnValueUnpacker(const FString &Type) { FString LType = Type.ToLower(); FName PackerName(FString::Printf(TEXT("LuaCallReturnValue_%s"), *LType)); return UlxLuaCallLibrary::StaticClass()->FindFunctionByName(PackerName); } FString UlxLuaCallLibrary::AllFunctionsWithPrefix(const TCHAR *Prefix) { FString Result; for ( TFieldIterator FIT ( UlxLuaCallLibrary::StaticClass(), EFieldIteratorFlags::ExcludeSuper); FIT; ++FIT) { UFunction* Function = *FIT; FString Name = Function->GetName(); if (Name.RemoveFromStart(Prefix)) { if (!Result.IsEmpty()) Result += TEXT(", "); Result += Name; } } return Result; } ///////////////////////////////////////////////////////////////// // // General Lua-Callable functions of the Lua Call Library. // ///////////////////////////////////////////////////////////////// void UlxLuaCallLibrary::LuaCallBegin(UObject *context, const FString &cname, const FString &fname) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); mode->LuaCallBegin(); sb.write_string(cname); sb.write_string(fname); } void UlxLuaCallLibrary::LuaCallInvoke(UObject *context, AActor *place) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); CheckNotEmpty(sb); mode->LuaCallEnd(InvocationKind::LUA_INVOKE, place); } void UlxLuaCallLibrary::LuaCallProbe(UObject *context, AActor *place) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); CheckNotEmpty(sb); mode->LuaCallEnd(InvocationKind::LUA_PROBE, place); } UlxLuaValues *UlxLuaCallLibrary::LuaCallGetRest(UObject *context) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); FlxStreamBuffer &sb = mode->LuaCallGetResult(); UlxLuaValues *Values = NewObject(context); if (!Values->Initialize(sb.view())) { UE_LOG(LogBlueprint, Error, TEXT("Lua call returned corrupt data")); return nullptr; } return Values; } ///////////////////////////////////////////////////////////////// // // Argument Packing functions // ///////////////////////////////////////////////////////////////// void UlxLuaCallLibrary::LuaCallArgument_string(UObject *context, const FString &pstring) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); CheckNotEmpty(sb); sb.write_simple_dynamic_tag(SimpleDynamicTag::STRING); sb.write_string(pstring); } void UlxLuaCallLibrary::LuaCallArgument_name(UObject *context, const FName &pname) { FTCHARToUTF8 utf8str(pname.ToString()); std::string_view namestr(utf8str.Get(), utf8str.Length()); uint64_t tokvalue = ParseNameAsToken(namestr); if ((tokvalue == 0) && !namestr.empty()) { FatalBlueprintError(TEXT("Names passed to lua must be short, and must contain only lowercase and digits")); } ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); CheckNotEmpty(sb); sb.write_simple_dynamic_tag(SimpleDynamicTag::TOKEN); sb.write_string(namestr); } void UlxLuaCallLibrary::LuaCallArgument_float(UObject *context, double pfloat) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); CheckNotEmpty(sb); sb.write_simple_dynamic_tag(SimpleDynamicTag::NUMBER); sb.write_double(pfloat); } void UlxLuaCallLibrary::LuaCallArgument_int(UObject *context, int value) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); CheckNotEmpty(sb); sb.write_simple_dynamic_tag(SimpleDynamicTag::NUMBER); sb.write_double(value); } void UlxLuaCallLibrary::LuaCallArgument_vector(UObject *context, const FVector &pvector) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); CheckNotEmpty(sb); sb.write_simple_dynamic_tag(SimpleDynamicTag::VECTOR); sb.write_fvector(pvector); } void UlxLuaCallLibrary::LuaCallArgument_vector2d(UObject *context, const FVector2D &pvector) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); CheckNotEmpty(sb); sb.write_simple_dynamic_tag(SimpleDynamicTag::VECTOR); sb.write_double(pvector.X); sb.write_double(pvector.Y); sb.write_double(0.0); } void UlxLuaCallLibrary::LuaCallArgument_boolean(UObject *context, bool pbool) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); CheckNotEmpty(sb); sb.write_simple_dynamic_tag(SimpleDynamicTag::BOOLEAN); sb.write_bool(pbool); } ///////////////////////////////////////////////////////////////// // // Return Value Unpacking functions // ///////////////////////////////////////////////////////////////// FString UlxLuaCallLibrary::LuaCallReturnValue_string(UObject *context) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); FlxStreamBuffer &sb = mode->LuaCallGetResult(); SimpleDynamicTag tag = sb.read_simple_dynamic_tag(); if (tag != SimpleDynamicTag::STRING) FatalBlueprintError(TEXT("expected lua to return a string")); return sb.read_fstring(); } FName UlxLuaCallLibrary::LuaCallReturnValue_name(UObject *context) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); FlxStreamBuffer &sb = mode->LuaCallGetResult(); SimpleDynamicTag tag = sb.read_simple_dynamic_tag(); if (tag != SimpleDynamicTag::TOKEN) FatalBlueprintError(TEXT("expected lua to return a name")); return sb.read_fname(); } double UlxLuaCallLibrary::LuaCallReturnValue_float(UObject *context) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); FlxStreamBuffer &sb = mode->LuaCallGetResult(); SimpleDynamicTag tag = sb.read_simple_dynamic_tag(); if (tag != SimpleDynamicTag::NUMBER) FatalBlueprintError(TEXT("expected lua to return a float")); return sb.read_double(); } int UlxLuaCallLibrary::LuaCallReturnValue_int(UObject *context) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); FlxStreamBuffer &sb = mode->LuaCallGetResult(); SimpleDynamicTag tag = sb.read_simple_dynamic_tag(); if (tag != SimpleDynamicTag::NUMBER) FatalBlueprintError(TEXT("expected lua to return a number")); return int(sb.read_double()); } FVector UlxLuaCallLibrary::LuaCallReturnValue_vector(UObject *context) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); FlxStreamBuffer &sb = mode->LuaCallGetResult(); SimpleDynamicTag tag = sb.read_simple_dynamic_tag(); if (tag != SimpleDynamicTag::VECTOR) FatalBlueprintError(TEXT("expected lua to return a vector")); return sb.read_fvector(); } FVector2D UlxLuaCallLibrary::LuaCallReturnValue_vector2d(UObject *context) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); FlxStreamBuffer &sb = mode->LuaCallGetResult(); SimpleDynamicTag tag = sb.read_simple_dynamic_tag(); if (tag != SimpleDynamicTag::VECTOR) FatalBlueprintError(TEXT("expected lua to return a vector")); FVector v = sb.read_fvector(); return FVector2D(v.X, v.Y); } bool UlxLuaCallLibrary::LuaCallReturnValue_boolean(UObject *context) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); FlxStreamBuffer &sb = mode->LuaCallGetResult(); SimpleDynamicTag tag = sb.read_simple_dynamic_tag(); if (tag != SimpleDynamicTag::BOOLEAN) FatalBlueprintError(TEXT("expected lua to return a boolean")); return sb.read_bool(); }