Every Cheat Table I ship contains Lua. Teleportation, UI modifications, persistent States, all systems made in Lua. It is also, conveniently, one of the smallest real programming languages in existence: the whole thing fits in an afternoon. This post is that afternoon.
No programming experience required. If you have written code in any other language, skim the first half and slow down at Tables, that is where Lua stops looking like everything else.
Running Lua#
You do not need to install anything. Cheat Engine ships with a complete
Lua environment: open the Memory Viewer and pick
Tools → Lua Engine. The window has an output pane at
the top and an input box below. Type a line, press execute, and whatever
you print lands in the output pane.
Lua Engineprint("hello from Cheat Engine") print(2 + 3) print(0x10) -- hex literals work: prints 16
Everything in this post runs in that window. Later, the same code moves into table scripts and hotkeys unchanged, the language is identical, only the place you store it differs.
Values and types#
Lua has exactly eight types, and you can ignore three of them for now. The five that matter:
nil, the absence of a value. Anything unset isnil.boolean,trueorfalse.number, integers and floats in one type.10,3.5,0xDEADBEEF.string, text in single or double quotes. Immutable.table, the one and only data structure. More on this below.
type(x) tells you what something is, and is worth using
liberally while learning:
Lua Engineprint(type(42)) -- number print(type("42")) -- string print(type(nil)) -- nil print(type(print)) -- function (yes, functions are values)
Variables and scope#
A bare assignment creates a global variable. Adding
local creates one that only exists in the current block.
This distinction is the source of roughly half of all Lua bugs, so build
the habit on day one: everything gets local unless
you have a reason for it not to.
Lua Enginelocal health = 100 -- lives in this script only score = 9999 -- global: visible to every script, forever local function heal(amount) health = health + amount -- finds the local above end
In Cheat Engine this matters more than usual: every table script and the
Lua Engine share one global environment. A stray global called
timer in one script will happily collide with a stray global
called timer in another, and the resulting bug reports are
not fun to read.
Strings and numbers#
Strings concatenate with .., not +. Numbers
convert to strings automatically when concatenated, and
string.format handles anything fancier:
Lua Enginelocal name = "Player" print(name .. " has " .. 100 .. " HP") -- printf-style formatting; %X is uppercase hex print(string.format("address: %X, value: %d", 0x7FF6A0001234, 87)) -- string to number and back print(tonumber("1337")) -- 1337 print(tonumber("FF", 16)) -- 255 (base 16) print(tostring(3.14)) -- "3.14"
A few string functions you will actually use:
string.sub(s, from, to), string.find(s, pattern),
string.lower(s), and #s for the length. Every
one of them is also callable method-style: s:lower() is the
same as string.lower(s).
Control flow#
Lua Enginelocal hp = 35 if hp <= 0 then print("dead") elseif hp < 50 then print("low") else print("fine") end -- numeric for: start, limit, optional step for i = 1, 5 do print("wave " .. i) end for i = 10, 2, -2 do print(i) -- 10, 8, 6, 4, 2 end -- while and repeat local tries = 0 while tries < 3 do tries = tries + 1 end repeat tries = tries - 1 until tries == 0
Comparison is ==, inequality is ~= (not
!=). Logic is spelled out: and,
or, not. There is break but no
continue, you restructure with an if instead,
and honestly, the code usually reads better for it.
Only nil and false count as false.
Zero is true. An empty string is true. If you come
from C, if damage then does not check for non-zero, it
checks for non-nil.
Tables, the only data structure you get#
Arrays, dictionaries, objects, namespaces: in Lua they are all the same thing, a table. Once this clicks, the whole language clicks.
As an array#
Lua Enginelocal weapons = { "Hammer", "Bow", "Gun Lance" } print(weapons[1]) -- "Hammer", Lua counts from 1, not 0 print(#weapons) -- 3 table.insert(weapons, "Insect Glaive") -- append table.remove(weapons, 2) -- remove "Bow", closes the gap
As a dictionary#
Lua Enginelocal player = { name = "Hunter", hp = 100, ["max hp"] = 150, -- keys with spaces need brackets } print(player.name) -- dot access print(player["max hp"]) -- bracket access, same table player.stamina = 75 -- new keys appear on assignment player.hp = nil -- assigning nil deletes the key
Iterating#
Lua Engine-- ipairs: the array part, in order, stops at the first hole for index, value in ipairs(weapons) do print(index, value) end -- pairs: every key, in no guaranteed order for key, value in pairs(player) do print(key, value) end
#t is only reliable on gap-free arrays. The moment you
write t[5] = nil in the middle of one, the length operator
may return anything. If you need to delete from an array, use
table.remove, it closes the gap.
Functions#
Functions are plain values: you can store them in variables, in tables, and pass them around. Two features surprise newcomers, multiple return values and variadic arguments:
Lua Enginelocal function clamp(value, low, high) if value < low then return low end if value > high then return high end return value end -- multiple returns local function minmax(t) local lo, hi = t[1], t[1] for _, v in ipairs(t) do lo = math.min(lo, v) hi = math.max(hi, v) end return lo, hi end local low, high = minmax({ 7, 2, 9, 4 }) print(low, high) -- 2 9 -- varargs local function sum(...) local total = 0 for _, n in ipairs({ ... }) do total = total + n end return total end print(sum(1, 2, 3, 4)) -- 10
When things go wrong#
An error in Lua stops the script and prints a message with a line number , read it, it is nearly always literally correct. The classic one:
Lua Enginelocal player = nil print(player.hp) -- attempt to index a nil value (local 'player')
When failure is expected, a game that is not running, an address that
does not resolve, wrap the risky call in pcall instead of
letting the script die:
Lua Enginelocal ok, result = pcall(function() return readInteger(getAddress("game.exe+1234")) end) if ok then print("value: " .. result) else print("could not read: " .. tostring(result)) end
Lua inside Cheat Engine#
Everything above is standard Lua. Cheat Engine adds a few hundred functions on top that talk to the attached process. These four cover a surprising amount of ground:
Lua Engine-- resolve a symbol/pointer expression to an address local addr = getAddress("Tutorial-x86_64.exe+325B00") -- read and write memory (Integer, Float, Double, Bytes, String variants exist) local value = readInteger(addr) writeInteger(addr, value + 100) print(string.format("%X -> %d", addr, readInteger(addr)))
Your cheat table is scriptable too. Every entry in the address list is a memory record you can toggle from code, which is exactly how master hotkeys work:
Table Lua scriptlocal list = getAddressList() local record = list.getMemoryRecordByDescription("Disable : Stamina Usage") createHotkey(function() record.Active = not record.Active print("Stamina usage: " .. (record.Active and "disabled" or "normal")) end, VK_F6)
The complete list of Cheat Engine's Lua functions lives in
celua.txt, right next to your Cheat Engine installation.
It is a plain text file and it is the single most useful document for
table scripting, search it before searching the web.
The classic beginner traps#
- Indexing starts at 1.
weapons[0]is not an error, it is quietlynil, which is worse. =assigns,==compares.if x = 5 thenis a syntax error; at least that one fails loudly.- Zero and empty strings are true. Only
nilandfalseare false. - Forgotten
localleaks globals. In Cheat Engine, all scripts share them. ..concatenates,+adds."10" + 5is15because strings coerce to numbers, convenient right up until it hides a bug.~=, not!=. Muscle memory from other languages loses this one for a week.
Where to go from here#
You now know more Lua than most table scripts ever use. To go deeper:
- Programming in Lua, the book, by Lua's author. The first edition is free online and still excellent.
- The Lua 5.3 reference manual, terse but complete.
celua.txt, see above. Cheat Engine's entire scripting surface in one file.