Elysium scripting Editor API index
Build

Files, data & network

The script's own folder, saved state, JSON and encodings, helpers - and, for trusted scripts, the network, the clipboard and raw memory.

This page covers everything a script does with data that is not the game: files it keeps, state it saves, text it parses, and - for scripts you have decided to trust - requests it sends and memory it reads.

#The script itself

#script.name

The script's name: its file name without .lua.

#script.trusted

true when the script has been switched to trusted. A script can check it to explain itself - or to stay away from features it cannot have.

#script.folder() → string

The script's own folder, C:/elysium/scripts/<name>, without a trailing slash. It is the one place a sandboxed script may read and write; it need not exist yet.

#script.path( relative ) → string

An absolute path to something inside the script's folder: script.path( "log/today.txt" ). Refuses .., drive letters and leading slashes, so a path built this way cannot leave the folder.

#Files

fs reads and writes files. Paths are relative to the script's own folder; .. and drive letters are refused, and an absolute path is accepted only from a trusted script. Write file names in ASCII - paths go through the system's code page, not UTF-8.

#fs.read( path ) → text | nil, message

The whole file as a string. nil and a message - that path is outside the script's folder, cannot open the file, the file is too large - when it cannot be read. Files over 64 MB are refused.

#fs.write( path, data ) → boolean

Writes data to the file, replacing it, and creates the folders on the way. false if the path is not allowed or the file could not be written.

#fs.append( path, data ) → boolean

Adds data to the end of the file, creating it if it is not there.

#fs.exists( path ) → boolean

#fs.size( path ) → integer | nil

The file's size in bytes; nil if it does not exist or the path is not allowed.

#fs.list( [directory] ) → list | nil

The names of the files and folders inside a folder - the script's own folder by default - sorted, at most 4096. nil if the path is not allowed.

#fs.mkdir( path ) → boolean

Creates a folder and the ones above it. true if it exists afterwards.

#fs.remove( path ) → boolean

Deletes a file or an empty folder.

fs.write( "log/session.txt", "started " .. os.date( "%H:%M" ) .. "\n" )
fs.append( "log/session.txt", "hello\n" )

print( fs.read( "log/session.txt" ) )

#Saved state

store is a small table that survives reloads and restarts. Put things in it that the script should remember: a counter, a list of positions, the last thing the user typed.

#store.set( key, value )

Saves value under key. The value has to be something JSON can hold: nil (which removes the key), booleans, numbers, strings, and tables of those. A table whose keys are exactly 1, 2, ..., n is stored as a list; any other table as an object with text keys. Functions, userdata, tables that contain themselves and tables nested deeper than 24 levels raise store.set: the value cannot be saved.

#store.get( key [, default] ) → value

The saved value, or default (nil if omitted) when there is none. The value you get is a fresh copy: changing it does not change what is stored until you set it again.

#store.keys() → list

Every key currently stored.

#store.clear()

Forgets everything.

The data is written to .data\<name>.store.json a moment after the last change - not on every call - and again when the script unloads, so setting a value every frame does not hammer the disk.

local runs = store.get( "runs", 0 ) + 1
store.set( "runs", runs )

print( "this script has been loaded " .. runs .. " times" )

#JSON, base64 and hashes

#json.encode( value [, pretty] ) → text | nil, message

Turns a value into JSON text. Tables follow the same rule as in store.set: keys 1..n make a list, anything else an object. With pretty = true the text is indented by two spaces. nil and a message when the value holds something JSON cannot - a function, userdata, a cycle, nesting past 24 levels, or a number that is not finite.

#json.decode( text ) → value | nil, message

Parses JSON text into tables, strings, numbers and booleans. nil and a message - not valid JSON, the text is too large - when it cannot. JSON null becomes nil, so a list containing nulls has holes in it. Text over 16 MB is refused.

local text  = json.encode( { name = "ak", shots = { 1, 2, 3 } } )
local again = json.decode( text )

print( again.shots[ 2 ] )     --> 2

#base64.encode( text ) → string

#base64.decode( text ) → string | nil, message

Base64 for arbitrary bytes. Decoding answers nil and not valid base64 for text that is not.

#hash.fnv1a( text ) → integer

The 32-bit FNV-1a hash of the bytes - the same function the cheat hashes names with.

#hash.murmur2( text [, seed] ) → integer

The 32-bit MurmurHash2, with an optional seed.

#hash.crc32( text ) → integer

The standard CRC-32 of the bytes.

All three answer a number between 0 and 4294967295.

#Time and randomness

#time.unix() → integer

Seconds since 1 January 1970, UTC. For readable dates the standard os.date and os.time work as usual.

#time.ms() → number

Milliseconds on a clock that only moves forward, with sub-millisecond precision. Good for measuring how long something took.

local started = time.ms( )
-- ... work ...
print( string.format( "took %.2f ms", time.ms( ) - started ) )

#util.random_hex( [bytes] ) → string

bytes random bytes from the system's random number source, as lowercase hex - twice as many characters. 1 to 256 bytes, 16 by default. For identifiers and nonces. For game logic math.random is the right tool.

#Bits

Lua 5.4 has bit operators of its own - &, |, ~, <<, >> - and they work on every integer. bits adds the few calls that read better when you are dealing with masks and flags, such as the button set and entity flags.

#bits.band( a, b, ... ) → integer

#bits.bor( a, b, ... ) → integer

#bits.bxor( a, b, ... ) → integer

Bitwise AND, OR and exclusive OR of any number of integers.

#bits.bnot( a ) → integer

Every bit flipped.

#bits.shl( a, n ), bits.shr( a, n ) → integer

Shift left or right by n bits. Both are logical - zeros are shifted in, also for negative numbers - and a count of 64 or more gives 0.

#bits.has( value, mask ) → boolean

true when every bit of mask is set in value.

#bits.any( value, mask ) → boolean

true when at least one bit of mask is set in value.

#bits.set( value, mask ), bits.clear( value, mask ) → integer

value with the bits of mask switched on, or off.

#bits.count( value ) → integer

How many bits are set.

if bits.has( me:flags( ), ents.flags.on_ground + ents.flags.ducking ) then
	-- standing on the ground, crouched
end

#Helpers added to string and table

Small functions Lua's own libraries never had. They are plain Lua, defined for every script.

#string.split( text, separator [, plain] ) → list

Cuts text at every separator. The separator is searched for as plain text unless plain is false, in which case it is a Lua pattern. An empty or missing separator splits into single characters.

local parts = string.split( "a,b,,c", "," )     --> { "a", "b", "", "c" }

#string.trim( text ) → string

Removes whitespace from both ends.

#string.starts_with( text, prefix ) → boolean

#string.ends_with( text, suffix ) → boolean

#table.copy( source ) → table

A shallow copy: the same values under the same keys.

#table.deep_copy( source ) → table

A copy that copies the tables inside too, keeps metatables, and copes with tables that refer to themselves.

#table.contains( list, value ) → boolean

Whether any value in the table equals value.

#table.keys( source ) → list, table.values( source ) → list

The keys, or the values, as a list - in no particular order.

#Trusted: network, clipboard, memory

#net.get( url [, options], handler ) → booleantrusted

Fetches an address without waiting: the call returns at once with true, and handler( response ) is called a frame or two later, on the render thread, when the answer is in. false and a message if the request could not be started - the options were bad, or four requests are already running.

response is a table:

FieldMeaning
oktrue for an HTTP status in the 200s.
statusThe HTTP status code; 0 if no answer arrived.
bodyThe answer, as a string. Absent when save_to was used.
pathWith save_to: where the answer was written.
headersThe raw response headers, as text.
errorWhat went wrong, when the request failed at the network level.

options is an optional table:

FieldMeaningDefault
headersA table { Name = "value" } of request headers. Names are letters, digits, - and _; values must not contain line breaks.none
timeoutSeconds before giving up, 1 to 120.15
save_toA file in the script's folder to write the answer to instead of returning it.none
max_bytesThe most the answer may be, 1 KB to 64 MB; larger is an error.8 MB
user_agentThe User-Agent header.script/1.0
net.get( "https://example.com/data.json", { timeout = 5 }, function( response )
	if response.ok then
		local data = json.decode( response.body )
		-- ...
	else
		warn( "request failed: " .. ( response.error or response.status ) )
	end
end )

Only http and https addresses are accepted. At most four requests run at once. If the script is reloaded before an answer arrives, the answer is dropped.

#net.post( url, body [, options], handler ) → booleantrusted

The same, sending body (a string, up to 8 MB) with a POST. Add a Content-Type header yourself when the server needs one.

#clipboard.get() → string | niltrusted

The text on the clipboard; nil if there is none.

#clipboard.set( text ) → booleantrusted

Puts text on the clipboard.

#util.open_url( url ) → booleantrusted

Opens an address in the default browser. Only http:// and https:// addresses are accepted.

#Memory

mem reads and writes the game's memory and calls code in it. Reads and writes are protected: an address that cannot be reached answers nil or false instead of crashing the game. Nothing else is checked - a wrong write, or a call with the wrong arguments, can crash the game or worse. This is for people who know what they are doing.

#mem.module( name ) → integer | niltrusted

The base address of a loaded module: mem.module( "client.dll" ).

#mem.module_size( base ) → integertrusted

The size of a loaded module in bytes.

#mem.export( module_base, name ) → integer | niltrusted

The address of a function a module exports.

#mem.pattern( module, pattern ) → integer | niltrusted

Finds a byte pattern in a module and returns an address, using the cheat's own pattern syntax. The pattern is up to 128 bytes of hex pairs - spaces are allowed - with ? for a byte that can be anything:

local address = mem.pattern( "client.dll", "48 8B 05 ? ? ? ? 48 85 C0" )

Markers say what to return instead of the place the pattern was found:

MarkerMeaning
*The 4 bytes at this point are a relative offset: return the address they point at (for lea, mov and the like with a RIP-relative operand).
>Put before the opcode byte of a call or jmp: return the address it calls.
^The bytes at this point are an absolute 8-byte address: return it.
+n, -nn in hex: added to the result afterwards.
~Read the pointer stored at the result and return that.

nil when the pattern is not found.

#mem.vfunc( object, index ) → integer | niltrusted

The address of the function in a slot of an object's virtual table. object is the object's address - ent:address() for an entity.

#mem.read( address, type ) → value | niltrusted

Reads a value. type is "bool", "i8", "u8", "i16", "u16", "i32" ("int"), "u32", "i64" ("ptr", "u64"), "f32" ("float"), "f64" ("double") or "vec3". nil if the memory cannot be read.

#mem.write( address, type, value ) → booleantrusted

Writes a value of the same types; false if the memory cannot be written.

#mem.read_bytes( address, count ) → string | niltrusted

count bytes (up to 1 MB) as a string, or nil if any of them cannot be read.

#mem.write_bytes( address, data ) → booleantrusted

#mem.string( address [, max] ) → stringtrusted

The zero-terminated string at an address, up to max bytes (256 by default).

#mem.call( address, returns, ... ) → valuetrusted

Calls native code at address with up to eight arguments, all of the integer kind: integers, booleans, pointers, and strings (passed as pointers that stay valid for the call; nil is 0). returns is "void", "int" (or "ptr"), "bool", "float" or "double". Floating-point arguments are not supported - passing one raises an error.

A fault inside the function does not take the game down: the answer is nil and the message the function faulted.

local get_tick = mem.pattern( "engine2.dll", "..." )          -- some function you found
local tick     = get_tick and mem.call( get_tick, "int" )

There is no checking of what is called: a wrong address, or the wrong number of arguments, is a crash - or worse.

Elysium scripting guide · built 2026-10-03