-
Notifications
You must be signed in to change notification settings - Fork 2
Lua API Reference
xAstroBoy edited this page Apr 14, 2026
·
2 revisions
This is the complete API reference for Quest UE4 Modloader's Lua scripting environment.
For the full detailed reference, see docs/LUA_API.md
- Logging
- Object Finding
- UObject Methods
- ProcessEvent Hooks
- Native Hooks
- Memory Read/Write
- Timers & Delayed Actions
- Structs (LuaUStruct)
- TArray
- Enums
- Widget Creation
- Config & File I/O
- Cross-Mod Communication
- Bridge Commands
Log("Info message")
LogWarn("Warning message")
LogError("Error message")
print("Debug", 42, true, obj) -- Auto-formats all types
Notify("Title", "Body") -- In-game notification-- Most commonly used:
local obj = FindFirstOf("ClassName") -- First live instance
local all = FindAllOf("ClassName") -- All live instances (table)
-- Advanced:
local cls = FindClass("ClassName") -- UClass object
local obj = FindObject("ShortName") -- By short name
local obj = StaticFindObject("FullPath") -- By full path
local obj = LoadAsset("/Game/Path/Asset") -- Load from PAK
local world = GetWorldContext() -- Current UWorldEvery UObject returned by the API has these methods:
obj:IsValid() -- Is this object still alive?
obj:GetName() -- Short name (e.g., "PlayerController_0")
obj:GetFullName() -- Full path
obj:GetClass() -- UClass object
obj:GetClassName() -- Class name string
-- Property access (reflection)
obj:Get("PropName") -- Read property
obj:Set("PropName", v) -- Write property
obj.PropName -- Shorthand read
obj.PropName = v -- Shorthand write
-- Function calls
obj:Call("FuncName", arg1, arg2, ...)
obj:FuncName(arg1, arg2, ...) -- Shorthand
-- Type checking
UObject_IsA(obj, "ClassName") -- Inheritance check-- Pre-hook: fires BEFORE original. Return "BLOCK" to prevent it.
RegisterPreHook("/Script/Game.Class:Function", function(self, func, parms)
local obj = self:get()
return "BLOCK" -- Optional: skip original
end)
-- Post-hook: fires AFTER original.
RegisterPostHook("/Script/Game.Class:Function", function(self, func, parms)
local obj = self:get()
WriteU8(parms, 0) -- Modify return value
end)
-- Combined (returns IDs for removal)
local preId, postId = RegisterHook("Class:Func", function(self, parms) end)
UnregisterHook("Class:Func", preId, postId)Function paths follow UE4's naming:
- Blueprint:
/Game/Blueprints/Path/BP.BP_C:FunctionName - Native:
/Script/ModuleName.ClassName:FunctionName
-- Hook by C++ symbol name
RegisterNativeHook("_ZN9SomeClass4FuncEv", "fp",
function(thisPtr) end, -- Pre-hook
function(thisPtr, retval) end -- Post-hook
)
-- Hook by address
local base = GetLibBase()
RegisterNativeHookAt(Offset(base, 0x1234), "vpp",
function(a1, a2) return "BLOCK" end,
nil
)
-- Call native function
local result = CallNativeBySymbol("_ZSymbol", "ip", ptr)
local result = CallNative(addr, "fp", ptr)Signature format: First char = return type, rest = arg types.
v=void, i=int32, u=uint32, b=bool, p=pointer, f=float, d=double, l=int64
⚠️ Only use for byte/instruction patching. For UObject properties, useGet/Set.
ReadU8(addr) / WriteU8(addr, val) -- 8-bit
ReadU16(addr) / WriteU16(addr, val) -- 16-bit
ReadU32(addr) / WriteU32(addr, val) -- 32-bit
ReadU64(addr) / WriteU64(addr, val) -- 64-bit
ReadS32(addr) / WriteS32(addr, val) -- Signed 32-bit
ReadFloat(addr) / WriteFloat(addr, v) -- Float
-- Address helpers
local base = GetLibBase() -- Game library base
local sym = FindSymbol("name") -- dlsym resolve
local sym = Resolve("name") -- Full resolution chain
local pat = FindPattern("FF 43 ?? ??") -- Byte pattern scan
local ptr = Offset(base, 0x1000) -- Pointer arithmetic
IsNull(ptr) -- NULL check
IsValidPtr(ptr) -- Mapped check-- One-shot timer
local h = ExecuteWithDelay(5000, function() end)
-- Next tick
ExecuteAsync(function() end)
-- Repeating timer
local h = LoopAsync(2000, function() end)
-- Game thread variants (safe for UObject modification)
ExecuteInGameThread(function() end)
ExecuteInGameThreadWithDelay(1000, function() end)
LoopInGameThread(2000, function() end)
-- Frame-based timers
ExecuteWithDelayFrames(60, function() end)
LoopAsyncFrames(1, function() end) -- Every frame
-- Cancel
CancelDelayedAction(h)
IsDelayedActionValid(h)Struct properties (FVector, FRotator, IntPoint, etc.) are returned as LuaUStruct userdata:
local pos = actor:Get("Position") -- LuaUStruct
-- Field access
pos.X -- Read field
pos.X = 100 -- Write field (writes to live memory)
-- Set from table
actor:Set("Position", {X=100, Y=200, Z=300})
-- Pass struct to Call()
actor:Call("SetActorLocation", {X=100, Y=200, Z=300})
-- Methods
pos:GetTypeName() -- "FVector"
pos:GetSize() -- Struct size in bytes
pos:Clone() -- Independent copy
pos:CopyFrom({X=1}) -- Copy from table
pos:GetFields() -- {X="float", Y="float", Z="float"}
tostring(pos) -- "UStruct(FVector: X=100, Y=200, Z=300)"TArray properties are returned as userdata with 1-based indexing:
local arr = obj:Get("SomeArray")
arr[1] -- First element (1-indexed!)
arr[3] = newValue -- Write element
#arr -- Length
arr:GetArrayNum() -- Element count
arr:IsEmpty() -- True if empty
arr:ForEach(function(i, elem)
Log(i .. ": " .. tostring(elem))
return false -- Return true to break
end)
arr:Add(value) -- Append element
arr:Clear() -- Remove all-- Global enum table (auto-populated from reflection)
Enums.ECollisionChannel -- {ECC_WorldStatic=0, ...}
Enums.DebugMenuType -- {NewEnumerator5=0, ...}
-- Lookup
local ue = FindEnum("EnumName")
local t = GetEnumTable("EnumName") -- {Name=Value, ...}
local names = GetEnumNames() -- All enum names
-- Extend (runtime, adds to UEnum metadata)
AppendEnumValue("EnumName", "NewValue", 99)local widget = CreateWidget("TextBlock")
local vbox = CreateWidget("VerticalBox")
local custom = CreateWidget("MyWidget_C")
-- With owning player
local pc = FindFirstOf("PlayerController")
local w = CreateWidget("TextBlock", pc)-- ModConfig (JSON, persistent)
ModConfig.Save("ModName", {key = "value", num = 42})
local cfg = ModConfig.Load("ModName")
local path = ModConfig.GetPath("ModName")
-- Raw file I/O
local text = ReadTextFile("/path/to/file")
WriteTextFile("/path/to/file", "content")
FileExists("/path/to/file")
-- Mod paths
GetModDir() -- This mod's directory
GetDataDir() -- Shared data directory-- SharedAPI table (shared across all mods)
SharedAPI.MyMod = { DoSomething = function() end }
-- Shared variables
SetSharedVariable("key", value)
local v = GetSharedVariable("key")RegisterBridgeCommand("my_command", function(args)
return { status = "ok", data = 42 }
end)Test: python tools/deploy.py console → my_command