Docs / Lua VM content written by ai
A. What this VM is (and is not)
Full handbook for the overlay Lua VM. Read it like a tutorial the first time. After that, jump the table of contents. Every function the VM exposes is listed. If it is missing from here, it is not part of the public script API.
This is Lua 5.4 running inside Vexor, the overlay process on your PC. It is not Roblox Lua. It is not Luau. It is not injected into RobloxPlayerBeta. You cannot write:
game.Players.LocalPlayer.Character.HumanoidRootPart.CFrame = ...
Instance.new("Part")
workspace:FindFirstChild("x")
Those APIs do not exist here. There is no game global in the Roblox sense.
What you do have is the same toolkit the C++ cheat already uses:
- Read and write Roblox process memory (
vexor.mem,vexor.part,vexor.player) - The live player/entity cache (
vexor.players.list) - VisualEngine world-to-screen (
vexor.world.to_screen) - Overlay drawing on the transparent window (
vexor.draw/vexor.esp) - ImGui windows, sliders, colour pickers (
vexor.gui) - Offsets, place detection, PF camera look-at, MM2 roles
Roblox process <--memory driver--> Vexor C++ <--Lua C API--> your script
|
+-- overlay HWND (draw + GUI)
If you can imagine a feature as “read positions, project to screen, draw boxes, maybe write a position” you can write it here. That is how every overlay cheat works. The VM just lets you do it in Lua instead of C++.
vexor.host is the string "vexor-menu-1". If you ever see a script calling vexor.exec.inject, that path was removed. This host only runs in the menu.
B. First 60 seconds in the IDE
- Attach Vexor to Roblox as usual.
- Click the code icon on the overlay top bar. The Lua VM frame opens.
- Type in the big editor. Tabs along the top are separate buffers. Click a tab name to rename it.
+adds a tab.xcloses one. - Bottom-right of the frame, three buttons:
| Button | What it does |
|---|---|
| Clear | Empties the console under the editor. Does not stop a running script. |
| Terminate | Stops the running script. Clears overlay drawings and Lua GUI windows. Restores journaled vexor.cfg / cheats / FOV. If the script ignores the stop hook for ~2 seconds, the VM is hard-reset. |
| Execute | Compiles the active tab and runs it on a background thread. Only one script can run at a time. |
The console under the editor is where print(), errors, and tracebacks go.
| Colour | Tag | Source |
|---|---|---|
| Grey | [SYS] | engine messages |
| White | [INFO] | print, log, “Executing…” |
| Yellow | [WARN] | vexor.warn, “already running” |
| Red | [ERR] | compile errors, runtime errors, assert |
The IDE still takes mouse when the main Vexor menu is closed, as long as the cursor is over the Lua VM frame (or over a Lua GUI window you created).
Click Execute. You should see:
Executing "yourscript.lua" (N bytes) ...
If compile fails, you get a red line with a line number. That number is the Lua source line in the editor, not a C++ line.
Paste this first. If you see the text and a small window, the VM is live:
print("lua vm ok", vexor.host, vexor.game.place_name())
vexor.debug.info()
while true do
vexor.draw.clear()
local w, h = vexor.world.screen_size()
vexor.draw.text_ex(20, 20, "Vexor Lua VM", 255,255,255,255, 18, "ui", 1)
vexor.draw.crosshair(w * 0.5, h * 0.5, 8, 255,255,255,180, 1)
vexor.gui.begin("Lua VM", 40, 80)
vexor.gui.text("drag this window — input capture works")
local r,g,b,a = vexor.gui.color("c", "Accent", 120, 180, 255, 255)
vexor.gui.colored_text("picker live", r, g, b, a)
vexor.gui.finish()
vexor.sleep(1)
end
Then press Terminate. Draws and the window disappear. That is the full cycle.
C. The one loop you must always write
Scripts that draw or show GUI must loop. A script that runs to the end and returns will flash one frame (or nothing) and then the VM clears everything.
while true do
vexor.draw.clear() -- start a new overlay batch
-- queue draw calls
-- declare gui widgets
vexor.sleep(1) -- PUBLISH overlay + GUI, then wait 1 ms
end
| Call | What it does |
|---|---|
| vexor.draw.clear() | Throw away overlay commands queued since the last present. The last published frame stays on screen until the next present. Call this at the top of every tick so you do not stack 8000 lines. |
| vexor.sleep(ms) | Publishes the overlay batch, publishes the GUI widget tree, then sleeps ms milliseconds. ms = 0 still publishes, then returns immediately. 1 ms is the normal overlay-frame rate. |
| vexor.present() | Same publish as sleep, but no wait. Use only if you also sleep yourself. Prefer vexor.sleep(1). |
| vexor.draw.present() | Overlay only. Does not publish GUI. Almost always wrong if you also have vexor.gui windows. |
| vexor.gui.present() | GUI only. Same warning. |
| vexor.draw.clear_all() | Wipes pending and the last published overlay. The screen goes blank until you present again. |
| vexor.stop() | Raises the same kill error Terminate uses. Script-side off switch. |
A while true with no sleep never presents (or presents so rarely the overlay looks frozen), and Terminate has to wait for the line hook. Always sleep. A one-shot draw without a loop is also useless: the script ends and the overlay is cleared.
D. How drawing actually appears on screen
Your Lua thread cannot touch DirectX. It queues commands into a lock-free batch. The render thread paints the last published batch every overlay frame.
Lua tick N: clear, line, text, rect, gui.begin...gui.finish, sleep(1)
sleep presents batch N
Render thread: paints batch N on the overlay (behind Vexor menu windows)
Lua tick N+1: clear, ... sleep presents batch N+1
Render thread: paints batch N+1
Coordinates are overlay-client pixels. (0,0) is the top-left of the overlay window, which covers the game. vexor.world.screen_size() is the VisualEngine resolution, which is what world-to-screen uses. For HUD you usually want those same dimensions.
Draw order: first command is underneath later commands in the same batch. Lua overlay is drawn on the background draw list so your vexor.gui windows sit on top of ESP boxes. The main Vexor menu still sits above that.
8000 overlay commands per published frame. Extra commands are dropped. vexor.draw.dropped() returns how many were discarded this session. If ESP flickers or parts go missing on busy servers, you hit the cap — draw less (skip off-screen, skip dead, skip NPCs).
E. How GUI windows actually take clicks
vexor.gui is ImGui, but Lua does not run on the render thread. The pattern is “declare every tick, values live in named slots”.
vexor.gui.begin("ESP", 60, 80) -- title, x, y (w,h optional)
local on = vexor.gui.checkbox("box", "Boxes", true)
local r,g,b,a = vexor.gui.color("col", "Colour", 255, 80, 80, 255)
if vexor.gui.button("tp", "Teleport") then
-- true the tick AFTER the user clicked
vexor.player.set_pos(0, 50, 0)
end
vexor.gui.finish()
- The first argument of almost every widget is a string id. It must stay the same every frame or the widget forgets its value. Bad:
vexor.gui.checkbox("box"..i, ...)with a newievery tick. Good:vexor.gui.checkbox("box", "Boxes", true). - Default values (the last args) apply only the first time that id is seen. After that, the user’s clicks/drags own the value.
- Buttons are one-frame delayed. Click → render thread stores clicked → next Lua loop returns true → you consume it (it will not stay true).
- You must call
beginandfinishevery tick you want the window visible. Skip a tick and the window vanishes. w,h = 0(or omitted height) means auto-resize to content.pinPosBool(6th arg of begin) true = force x,y every frame (cannot drag). false/omitted = SetNextWindowPos once.- When the main menu is closed, the overlay is click-through except: cursor over the Lua VM IDE, cursor over a Lua GUI window, or you are dragging a Lua slider/picker. If clicks fall through to the game, move the cursor onto the window.
- Colour widgets return 0–255, same as draw calls. Keep them in locals and pass those locals into
vexor.draw.*that same tick.
F. Lua 5.4 rules that will bite you
This is standard Lua 5.4, not Luau.
1. Reserved words cannot be field names with a dot
p.local -- ILLEGAL error: <name> expected near 'local'
p["local"] -- legal
p.is_local -- legal (alias we also set)
Lua reserved words: and break do else elseif end false for function goto if in local nil not or repeat return then true until while. Never write p.end, p.function, p.repeat. Use p["end"] if a field is ever named that. The “is this you?” flag is is_local / ["local"] for this reason.
2. Arrays are 1-based
names = { "a", "b", "c" }
names[1] == "a"
vexor.gui.combo returns 1, 2, 3 — not 0, 1, 2.
3. Multiple returns and nil
If the point is behind the camera, to_screen returns a single nil. Then sx is nil and sy is nil. Always check:
local sx, sy = vexor.world.to_screen(x, y, z)
if sx then
vexor.draw.circle(sx, sy, 4, 255,255,255,255)
end
Same for vexor.players.screen_box (four numbers or one nil) and vexor.players.local_player() (table or nil).
4–10
- No
continue. Skip with a nested if, orgoto. ~=is not-equal.!=does not exist.- String concat is
..not+. Example:"hp " .. tostring(p.health) math.atan(y, x)is atan2 in Lua 5.3+.math.atan2also exists asvexor.math.atan2.- pcall to swallow errors from a missing part:
local ok, x, y, z = pcall(vexor.part.pos, part_addr) - Only one script runs. Execute while running prints “A script is already running.” Hit Terminate first.
- Do not paste smart/curly quotes from chat apps. Lua wants
"and'. A curly quote turns a string into junk and you get bizarre parse errors on the next line, often near'local'.
G. How to read the console / debug anything
Compile error
Red line, no traceback, points at a line. <name> expected near 'local' → you wrote .local or a broken string so the parser hit a keyword. unexpected symbol near X → extra comma, missing end, bad quote. Fix the line. Execute again. Nothing is “stuck”.
Runtime error
Red lines with a traceback (file:line in function). “HumanoidRootPart not available” → character not loaded yet. Guard with if vexor.player.hrp_address() ~= 0. “attempt to index a nil value” → you did p.parts.Head without checking p, p.parts, or that Head exists. Wrap risky calls in pcall, or nil-check every step.
Print like a debugger
| Call | Use |
|---|---|
| print(a, b, c) | one-level tables are expanded |
| vexor.debug.dump(p) | nested tables, also returns the string |
| vexor.debug.inspect(p.address) | name, hp, every part address |
| vexor.debug.hex(addr, 64) | classic hexdump into the console |
| vexor.debug.info() | pid, module base, datamodel, camera, ve |
| vexor.debug.traceback() | stack at that moment |
| vexor.debug.type(x) | "table", "number", … |
| vexor.debug.assert(cond, "msg") | errors if cond is false |
| vexor.log / vexor.warn / vexor.notify | log, warn, overlay toast |
When you have no idea why ESP is empty:
print("alive", vexor.game.is_alive(), "place", vexor.game.place_name())
print("players", #vexor.players.list())
local me = vexor.players.local_player()
vexor.debug.dump(me)
local x,y,z = vexor.player.get_pos()
print("pos", x, y, z)
local sx,sy = vexor.world.to_screen(x,y,z)
print("w2s", sx, sy)
If players is 0, the C++ cache has not populated yet (lobby, loading, or not attached). Wait in the loop; do not error.
H. How to handle nil, missing players, and attach state
Always write defensive Lua. The cache rebuilds. People leave. You die. HumanoidRootPart vanishes for a frame.
Safe player loop:
local me = vexor.players.local_player()
local list = vexor.players.list()
for i = 1, #list do
local p = list[i]
if p and not p["local"] and p.address then
local pos = p.position
if pos then
local sx, sy = vexor.world.to_screen(pos.x, pos.y, pos.z)
if sx then
-- draw
end
end
end
end
Safe part:
local head = p.parts and p.parts.Head
if head and head.on_screen then
vexor.draw.circle(head.sx2, head.sy2, 4, 255,255,255,255)
end
Safe local character:
local hrp = vexor.player.hrp_address()
if hrp ~= 0 then
local x, y, z = vexor.player.get_pos()
vexor.player.set_velocity(0, 0, 0)
end
Attach:
if not vexor.mem.alive() then
vexor.draw.text(20, 20, "waiting for roblox", 255,80,80,255)
vexor.sleep(16)
-- continue loop, do not call set_pos
end
I. Colours
Draw calls take r,g,b,a as 0–255 integers after the geometry. Alpha defaults to 255 if you omit it.
vexor.draw.line(x1, y1, x2, y2, r, g, b, a, thickness)
Some calls have extra numbers after rgba (thickness, rounding, segments). Count carefully. For line, thickness is argument 9 because rgba occupies 5–8.
GUI colour widgets also speak 0–255:
local r,g,b,a = vexor.gui.color("box", "Box colour", 255, 80, 80, 255)
| Helper | Returns |
|---|---|
| vexor.color.pack(r,g,b,a) | ImU32 integer |
| vexor.color.unpack(packed) | r,g,b,a |
| vexor.color.lerp(r1,g1,b1,a1, r2,g2,b2,a2, t) | t 0–1 |
| vexor.color.luminance(r,g,b) | luminance |
| vexor.color.invert_rgb(r,g,b) | r,g,b |
| vexor.color.hsv_to_rgb255(h,s,v) | h 0–360, s/v 0–1 |
| vexor.color.lighten / darken(r,g,b, t) | r,g,b |
| vexor.color.with_alpha(r,g,b,a) | u32 |
Team colour pattern:
local r, g, b = 255, 80, 80
if me and p.team ~= 0 and p.team == me.team then
r, g, b = 80, 180, 255
end
J. Fonts and text
Font names (string) or ids (number):
| Id / name | Font |
|---|---|
| 0 / "default" | ImGui default |
| 1 / "ui" | menu body (Inter) |
| 2 / "code" | Consolas (same as the Lua editor) |
| 3 / "esp" | current ESP font (user can import a font in visuals) |
| 4 / "qilka" | qilka |
| 5 / "tahoma" | tahoma |
vexor.fonts.list() → { "default","ui","code","esp","qilka","tahoma" }
vexor.draw.text(x, y, "hello", r,g,b,a)
vexor.draw.text(x, y, "hello", r,g,b,a, size, font, outline, or,og,ob,oa)
vexor.draw.text_ex(x, y, "hello", r,g,b,a, size, font, outline, or,og,ob,oa)
w, h = vexor.draw.measure("hello", size, font)
size— 0 = that font’s default pixel sizefont— name or idoutline— pixel outline width, 0 = noneor,og,ob,oa— outline colour, default black
vexor.draw.text_ex(20, 40, "ESP", 255,255,255,255, 16, "esp", 1, 0,0,0,220)
vexor.draw.text_ex(20, 64, "dump", 180,255,180,255, 14, "code", 0)
local label = p.display_name
local tw, th = vexor.draw.measure(label, 13, "esp")
vexor.draw.text_ex(box_x + (box_w - tw) * 0.5, box_y - th - 2, label, 255,255,255,255, 13, "esp", 1)
K. Overlay drawing API
vexor.draw and vexor.overlay are the same table. Use either name. Rebuild every tick. Coordinates are overlay pixels.
| Call | Notes |
|---|---|
| line(x1,y1,x2,y2, r,g,b,a [, thickness]) | |
| line_dashed(..., thickness, dash, gap) | |
| rect / rect_filled(x,y,w,h, r,g,b,a [, rounding]) | |
| rect_gradient(...) | four corners, 4 channels each, order TL, TR, BR, BL |
| circle / circle_filled(cx,cy,radius, r,g,b,a [, segments, thickness]) | |
| polyline({ {x,y}, ... }, r,g,b,a [, thickness, closedBool]) | |
| convex_fill({ {x,y}, ... }, r,g,b,a) | |
| triangle / triangle_filled / quad / quad_filled | |
| ngon / ngon_filled(cx,cy,radius, r,g,b,a, segments, thickness) | |
| bezier_cubic(x1,y1, x2,y2, x3,y3, x4,y4, r,g,b,a, thickness [, segments]) | |
| crosshair(cx,cy,size, r,g,b,a [, thickness]) | |
| grid(x,y,w,h, cols, rows, r,g,b,a [, thickness]) | |
| clip_push(x,y,w,h [, intersectBool]) / clip_pop() | |
| clear / clear_all / present / dropped | |
| text / text_ex / measure | see Fonts |
ESP helpers. These are only drawing. They do not enable Vexor’s built-in ESP.
| Call | Notes |
|---|---|
| vexor.esp.box(x,y,w,h, r,g,b,a [, thickness]) | |
| vexor.esp.filled(x,y,w,h, r,g,b,a) | |
| vexor.esp.corner(x,y,w,h, r,g,b,a [, thickness]) | |
| vexor.esp.healthbar(x, y, bar_w, bar_h, hp, maxhp) | vertical bar, green→red by fraction, black backing |
| vexor.esp.tracer(x, y, r,g,b,a [, thickness]) | line from bottom-center of the screen to (x,y) |
| vexor.esp.skeleton({ {x1,y1}, {x2,y2}, ... }, r,g,b,a [, th]) | consecutive pairs of points are line segments |
| vexor.esp.r15_bones() | { {"Head","UpperTorso"}, {"UpperTorso","LowerTorso"}, ... } |
| vexor.esp.r6_bones() | { {"Head","Torso"}, {"Torso","Left Arm"}, ... } |
local bones = vexor.esp.r15_bones()
local segs = {}
for i = 1, #bones do
local a = p.parts[bones[i][1]]
local b = p.parts[bones[i][2]]
if a and b and a.on_screen and b.on_screen then
segs[#segs+1] = { a.sx2, a.sy2 }
segs[#segs+1] = { b.sx2, b.sy2 }
end
end
if #segs > 0 then
vexor.esp.skeleton(segs, 255,255,255,180, 1)
end
If the rig is R6, Head/Torso exist and UpperTorso does not — use r6_bones(). You can try r15 first and fall back if segs is empty.
L. World-to-screen and camera
| Call | Notes |
|---|---|
| sx, sy = vexor.world.to_screen(x, y, z) | nil if behind camera or VisualEngine is missing |
| ok = vexor.world.on_screen(sx, sy [, margin]) | takes screen pixels, not world. margin extra pixels outside |
| w, h = vexor.world.screen_size() | VisualEngine dimensions (HUD and tracers) |
| dist = vexor.world.distance3(x1,y1,z1, x2,y2,z2) | |
| fx,fy,fz, rx,ry,rz, ux,uy,uz = vexor.world.camera_axes() | forward, right, up of the camera rotation matrix |
| x1,y1, x2,y2, ok1, ok2 = vexor.world.line_to_screen(...) | projects two world points; ok flags say if each landed |
Camera writes the live camera instance, same as C++:
| Call | Notes |
|---|---|
| vexor.camera.get_pos / set_pos(x,y,z) | |
| vexor.camera.get_fov / set_fov(fov) | |
| get_rotation_matrix / set_rotation_matrix(m00..m22) | 9 numbers, row-major 3×3 |
| get_subject / set_subject(addr) | |
| get_type / set_type(t) | 0 fixed, 1 attach, 2 watch, 3 track, 4 follow, 5 custom, 6 orbital, 7 scriptable |
local w, h = vexor.world.screen_size()
local sx, sy = vexor.world.to_screen(wx, wy, wz)
if not sx then
-- behind you: skip
elseif not vexor.world.on_screen(sx, sy, 8) then
local cx, cy = w * 0.5, h * 0.5
-- projected but outside the monitor: clamp for arrows
else
vexor.draw.circle(sx, sy, 3, 255,255,255,255)
end
M. Local player (vexor.player)
These helpers talk to your character. They error if HumanoidRootPart is missing — wrap in a check. get_pos returns 0,0,0 if missing (no error).
| Call | Notes |
|---|---|
| get_pos / set_pos / teleport(x,y,z) | set_pos is the same as teleport |
| set_velocity(x,y,z) | |
| hrp_address() | 0 if missing |
| child_address("Head") / child_primitive("Head") / child_pos / set_child_pos | |
| child_velocity("HumanoidRootPart") | |
| list_children() | names |
| humanoid_health / humanoid_max_health | |
| get_walkspeed / set_walkspeed(n) |
-- soft freeze (hold F)
if vexor.input.key_down(0x46) then
vexor.player.set_velocity(0, 0, 0)
end
-- hop pulse (SPACE)
if vexor.input.key_pressed(0x20) then
vexor.player.set_velocity(0, 70, 0)
end
N. All players — write ESP / aim from scratch
Data comes from the same entity cache the C++ ESP uses. It is a snapshot, typically tens of milliseconds old. That is normal.
| Call | Notes |
|---|---|
| vexor.players.list() | array of player tables |
| vexor.players.local_player() | nil if not resolved |
| vexor.players.get(address) | nil if they left the cache |
| set_pos / set_velocity(address, x,y,z) | writes that player’s HRP |
| part(address, "Head") | instance address or nil |
| set_walkspeed / walkspeed / set_jump_height | |
| screen_box(address) | axis-aligned screen box of all parts, or nil if none project |
Player table fields
name, display_name, user_id, address (player instance — use this as the id), team (0 if none), health, max_health, armor, distance (studs vs local), country, gender, tool, tool_address, platform, os, rig_type, humanoid_state, humanoid, mm2_role ("Murderer" / "Sheriff" / "Innocent" / ""), is_local (also p["local"]), position { x, y, z } from HRP (may be 0s if no HRP), parts map keyed by part name:
p.parts.Head = {
address, primitive,
x, y, z, -- world
sx, sy, sz, -- size
vx, vy, vz, -- velocity
on_screen, -- bool
sx2, sy2 -- screen of the origin (not a box)
}
Lighter tables from vexor.game.get_players() / vexor.game.local_player(): name, display_name, user_id, address, team, health, max_health, distance, mm2_role, humanoid, position (no parts map).
How to handle “is this me?”:
-- WRONG: if not p.local then -- Lua keyword, will not compile
-- RIGHT: if not p["local"] then
-- RIGHT: if not p.is_local then -- after current builds
local function closest(me, list)
if not me or not me.position then return nil, 1e9 end
local best, best_d = nil, 1e9
for i = 1, #list do
local p = list[i]
if p and not p["local"] and p.position then
local d = vexor.world.distance3(
me.position.x, me.position.y, me.position.z,
p.position.x, p.position.y, p.position.z)
if d < best_d then
best, best_d = p, d
end
end
end
return best, best_d
end
O. Instances / DataModel tree
Everything in Roblox is an instance with an address. You walk it like a tree. Addresses are Lua integers, or hex strings "0xDEADBEEF". Both work anywhere we take an address.
| Call | Notes |
|---|---|
| vexor.inst.name(addr) | |
| vexor.inst.class_name(addr) | |
| vexor.inst.parent(addr) | |
| vexor.inst.find(addr, "HumanoidRootPart") | 0 if missing |
| vexor.inst.find_class(addr, "Humanoid") | |
| vexor.inst.children(addr) | array of { address, name, class } |
| vexor.inst.set_parent(addr, parent_addr) |
Root pointers: vexor.game.datamodel_address(), workspace_address(), camera_address(), visualengine_address(), local_player_address().
Dump Workspace (first 40) to understand a game:
local kids = vexor.inst.children(vexor.game.workspace_address())
local n = math.min(#kids, 40)
for i = 1, n do
print(i, kids[i].class, kids[i].name, kids[i].address)
end
Then filter by class_name == "Part" / "MeshPart" / "Model", take part.pos, world-to-screen, draw. That is world ESP from scratch.
P. Parts and primitives
A visible part in memory is an instance + a Primitive (physics). Most position writes go to the primitive. Pass a BasePart/MeshPart instance address:
| Call | Notes |
|---|---|
| vexor.part.primitive(part_addr) | |
| pos / set_pos / velocity / set_velocity | pos is nil if no primitive |
| size / set_size | |
| rotation / set_rotation | 9 numbers |
| transparency(part_addr) | |
| color(part_addr) | r,g,b 0–1 engine colour |
| prim_pos / prim_set_pos(prim_addr, x,y,z) | when you already have p.parts.Head.primitive |
| vexor.humanoid.walkspeed / set_walkspeed / health | from p.humanoid or inst.find_class |
Q. Memory — same path as the external
Addresses: number or "0xDEADBEEF". Writes use the same driver as C++.
When to use mem vs helpers: need player pos quickly → vexor.player.set_pos / players.set_pos. Need a field we did not wrap → vexor.offsets.get + vexor.mem.write_*. Research unknown memory → hexdump, read_bytes, chain.
Reads
read_float / read_double / read_int8 / read_uint8 / read_int16 / read_uint16 / read_int32 / read_uint32 / read_int64 / read_uint64 / read_bool
read_string(addr [, maxLen])— raw C string, default 64, cap 512read_rbx_string(addr [, maxBytes])— Roblox string objectread_ptr(addr)— uint64read_vec3(addr) → x,y,z— three floats (Position layout)read_bytes(addr, n) → binary string— cap 4096valid(addr) → boolhexdump(addr [, n]) → text— 16-byte rows, cap 512chain(addr, {off1, off2, ...})— pointer walk
Writes
write_float / write_double / write_int8 / write_uint8 / write_int16 / write_uint16 / write_int32 / write_uint32 / write_int64 / write_uint64 / write_bool / write_ptr / write_vec3 / write_bytes / write_string(addr, str [, nullTerminateBool])
Process
vexor.mem.base() Roblox module base, pid(), module_size(), alive() false if Roblox closed.
Offsets are generated constants, not hardcoded features: vexor.offsets.Primitive.Position, vexor.offsets.get("Primitive.Position"). Explore with for k,v in pairs(vexor.offsets) do print(k,v) end.
local p = vexor.players.local_player()
if p and p.parts and p.parts.HumanoidRootPart then
local prim = p.parts.HumanoidRootPart.primitive
local off = vexor.offsets.get("Primitive.Position")
vexor.mem.write_vec3(prim + off, 0, 50, 0)
end
if vexor.mem.valid(addr) then
print(vexor.mem.hexdump(addr, 64))
else
print("not readable", addr)
end
Do not write random addresses. A bad write can crash Roblox, not Vexor.
R. Game / place helpers
| Call | Notes |
|---|---|
| game_id / place_id / live_game_id / live_place_id | live_* re-read from DataModel |
| is_alive / place_name | "phantom_forces" | "mm2" | "rivals" | "arsenal" | "unknown" |
| is_phantom_forces / is_mm2 / is_rivals / is_arsenal | |
| mm2_role(player_address) | "Murderer"|"Sheriff"|"Innocent"|"" |
| job_id / server_ip / creator_id |
Phantom Forces camera LookAt (same primitive C++ silent uses). This is a camera write, not a packaged aimbot. You pick the world point:
vexor.game.pf_silent(true, worldX, worldY, worldZ)
vexor.game.pf_silent(false)
if vexor.game.is_mm2() then
local role = vexor.game.mm2_role(p.address)
-- also p.mm2_role on the entity table
end
S. Optional built-in cheats / cfg
You do not need these. From-scratch scripts should draw and write memory themselves. These talk to the compiled C++ toggles if you want a hybrid.
vexor.cheats.set_aimbot(bool)
vexor.cheats.set_esp(bool)
vexor.cheats.set_noclip(bool)
vexor.cheats.set_speed(number)
vexor.cheats.set_fov(number)
v = vexor.cheats.get_setting("key")
vexor.cheats.set_setting("key", v)
v = vexor.cfg("visuals.box") -- get
vexor.cfg("visuals.box", true) -- set
list = vexor.list_cfg() -- every registered path
n, note = vexor.api_count()
Journaled settings are restored when you Terminate. If you only use vexor.draw, Terminate does not need to undo anything except your overlay/GUI. If you flip cfg/cheats, Terminate puts them back.
T. Interactive GUI API
vexor.gui.begin(title, x, y, w, h [, pinPosBool [, extraImGuiFlags]])
-- widgets
vexor.gui.finish() -- alias: end_window()
Widgets (id string first):
| Call | Notes |
|---|---|
| checkbox(id, label [, defaultBool]) | |
| slider / slider_int(id, label, min, max [, default]) | |
| drag_float(id, label, speed, min, max [, default]) | |
| color / color_edit / color_picker(id, label [, r,g,b,a]) | returns r,g,b,a |
| button / small_button(id, label [, w, h]) | |
| input(id, label [, default]) | text |
| combo(id, label, {"A","B","C"}, defaultIndex) | 1-based |
| radio(id, label, value [, current]) | |
| selectable(id, label [, selected]) | |
| hotkey(id, label [, defaultVk]) | click, press a key; Esc cancels; returns VK |
| collapsing / tree / tree_pop |
Layout: text, colored_text(str, r,g,b [,a]), bullet, label, separator, same_line([offset, spacing]), spacing, new_line, dummy(w, h), progress(fraction [, w, h, overlayText]), tooltip(str) (attaches to the previous widget), push_width / pop_width, begin_child / end_child, tab_bar / tab_item / end_tab_bar, hovered(id), present().
Two windows in one tick is fine. Call begin/finish twice with different titles. Keep ids unique across the whole script (“box” in two windows will share one checkbox state).
U. Keyboard and mouse
Virtual-key numbers are Win32 VK_*:
| VK | Key |
|---|---|
| 0x01 / 0x02 / 0x04 | LBUTTON / RBUTTON / MBUTTON |
| 0x08 / 0x09 / 0x0D | BACK / TAB / RETURN |
| 0x10 / 0x11 / 0x12 | SHIFT / CONTROL / MENU (Alt) |
| 0x1B / 0x20 | ESCAPE / SPACE |
| 0x25–0x28 | LEFT / UP / RIGHT / DOWN |
| 0x30–0x39 | 0–9 |
| 0x41–0x5A | A–Z |
| 0x70–0x7B | F1–F12 |
| Call | Notes |
|---|---|
| key_down / key_pressed / key_released(vk) | held this overlay frame / edge down / edge up |
| mouse_pos / mouse_delta / display_size | |
| mouse_down / mouse_clicked / mouse_released(button) | 0 left, 1 right, 2 middle |
| mouse_wheel / mod_ctrl / mod_shift / mod_alt | |
| point_in_rect(x,y, rx,ry,rw,rh) |
vexor.bind — named VK slots from lua_api_extensions (optional helper). For a hold-to-freeze feature, key_down is the right call. For a one-shot teleport, key_pressed (or a gui.button / gui.hotkey).
V. Math, time, files
vexor.math (also usable next to Lua’s own math.*): abs acos asin atan atan2 ceil clamp cos cosh cross cbrt deg dist2 dist2_sq dist3 dist3_sq dot2 dot3 angle_between2 exp floor fmod fract hash2 inv_lerp len3 lerp log log10 max min noise2 norm3 pi pingpong pow rad reflect3 remap round saturate sign sin sinh smoothstep smootherstep sqrt step tan tanh tau wrap.
vexor.vec / vexor.ease / vexor.util — extra helpers (easing, vector sugar). vexor.clock() seconds (GetTickCount64 / 1000). vexor.now_ms().
Files are sandboxed to %APPDATA%\Vexor\scripts (see vexor.scripts_dir): vexor.loadfile("rel.lua") / loadfile, vexor.dofile("rel.lua") / dofile, vexor.exec.status(). vexor.exec.inject / inject_file were removed on this host.
Example scripts in the repo (copy into the IDE, they are not auto-loaded):
project/vexhook/vexor/scripts/examples/01_atlas_field_kit.luaproject/vexhook/vexor/scripts/examples/02_probe_inspector.luaproject/vexhook/vexor/scripts/examples/03_horizon_radar.lua
W. What you can rebuild from scratch
You do not flip Vexor ESP and call it a day. You have the primitives to recreate (and invent) features:
- ESP — boxes, corners, filled, names, distance, health, tracers, skeletons, team colours, on_screen culling, off-screen ticks
- Aim visuals — FOV circle, snaplines, target name
- Aim logic — closest / FOV target from players.list + to_screen, camera rotation write, PF silent look-at, part positions
- Movement — set_pos, set_velocity, walkspeed, jump, freeze, hop
- World ESP — inst.children(workspace), filter class, part.pos + w2s
- Waypoints — Lua table of {x,y,z}, draw at w2s, teleport on hotkey
- Colour pickers — vexor.gui.color_picker driving draw colours
- Config UI — checkbox/slider ids persist for the script session
- Game specific — mm2 roles, PF silent, arsenal/rivals place checks
- Debugging — hexdump, inspect, DataModel walk, debug.info
Built-in C++ visuals still run independently. Turn them off if you only want Lua drawings.
X. Limits, sandbox, Terminate
Limits
- 8000 overlay commands per published frame (
dropped()counts overflows) - ~4000 GUI widget commands per frame
- read_bytes / write_bytes cap 4096
- hexdump cap 512 · read_string cap 512
- one running script
- player cache is a snapshot (same as C++ ESP)
Sandbox
os.execute/os.remove/io.popen/package.loadlibare stripped- loadfile/dofile only under the Vexor scripts folder
- cannot inject into Roblox
- can read/write Roblox memory because that is the external’s job
Terminate
IDE button, or vexor.stop() in script. Cooperative: debug hook raises __vexor_kill__ on the next Lua line. If the script is stuck in a C call for >2s, the state is rebuilt. Overlay + GUI cleared. Journaled cfg/cheats/FOV restored.
When the main menu is closed, the overlay is click-through except Lua IDE, Lua GUI, or an active drag.
Y. Full API index
| Namespace | What |
|---|---|
| vexor.mem.* | memory r/w, vec3, bytes, hexdump, base, pid, alive |
| vexor.game.* | datamodel addrs, place, light player list, PF/MM2 |
| vexor.player.* | your character: pos, vel, children, walkspeed |
| vexor.players.* | full cache, teleport any, screen boxes, parts |
| vexor.inst.* | name, class, parent, find, children, set_parent |
| vexor.part.* | primitive pos/vel/size/rot/colour |
| vexor.humanoid.* | walkspeed / health by address |
| vexor.camera.* | pos, fov, matrix, subject, type |
| vexor.world.* | to_screen, screen_size, on_screen, axes, distance3 |
| vexor.draw.* / overlay.* | overlay primitives, text_ex, measure, present, clear |
| vexor.esp.* | box/corner/healthbar/tracer/skeleton/bone tables |
| vexor.gui.* | windows, pickers, sliders, hotkeys, tabs |
| vexor.fonts.list | font names |
| vexor.color.* | pack/lerp/hsv/lighten |
| vexor.math.* | numeric |
| vexor.input.* | keys/mouse |
| vexor.cheats.* / vexor.cfg | optional compiled settings |
| vexor.offsets.* | field offsets |
| vexor.debug.* | dump, hex, inspect, traceback, info, assert |
| sleep / present / stop / clock / now_ms / log / warn / notify / help | |
| vexor.host / scripts_dir / print / dofile / loadfile | host is "vexor-menu-1" |
Z. Copy-paste recipes
If Execute prints lua vm ok and you see overlay text plus a window, you are done with setup. Everything else is the same loop: clear, read cache or memory, draw or write, sleep, repeat. Terminate when finished.
1. Smoke test
print("lua vm ok", vexor.host, vexor.game.place_name())
vexor.debug.info()
print("pos", vexor.player.get_pos())
while true do
vexor.draw.clear()
local w,h = vexor.world.screen_size()
vexor.draw.text_ex(20, 20, "Vexor Lua VM", 255,255,255,255, 18, "ui", 1)
vexor.draw.crosshair(w*0.5, h*0.5, 8, 255,255,255,180, 1)
vexor.gui.begin("Lua VM")
vexor.gui.text("if you can drag this, input capture works")
local r,g,b,a = vexor.gui.color("c", "Accent", 120, 180, 255, 255)
vexor.gui.colored_text("picker live", r,g,b,a)
vexor.gui.finish()
vexor.sleep(1)
end
2. ESP from scratch (no vexor.cheats.set_esp)
while true do
vexor.draw.clear()
local me = vexor.players.local_player()
local list = vexor.players.list()
for i = 1, #list do
local p = list[i]
if p and not p["local"] then
local x, y, w, h = vexor.players.screen_box(p.address)
if x then
local r, g, b = 255, 80, 80
if p.team ~= 0 and me and p.team == me.team then
r, g, b = 80, 180, 255
end
vexor.esp.corner(x, y, w, h, r, g, b, 220, 1.5)
vexor.esp.healthbar(x - 5, y, 3, h, p.health, p.max_health)
local name = p.display_name
if name == nil or name == "" then name = p.name end
vexor.draw.text_ex(x, y - 16, name, 255,255,255,255, 13, "esp", 1)
end
end
end
vexor.sleep(1)
end
3. GUI teleport
while true do
vexor.gui.begin("Movement", 40, 90)
local x = vexor.gui.drag_float("tx", "X", 1, -10000, 10000, 0)
local y = vexor.gui.drag_float("ty", "Y", 1, -10000, 10000, 50)
local z = vexor.gui.drag_float("tz", "Z", 1, -10000, 10000, 0)
if vexor.gui.button("go", "Teleport") then
vexor.player.set_pos(x, y, z)
print("teleported", x, y, z)
end
vexor.gui.finish()
vexor.sleep(1)
end
4. Inspect whoever you are looking at
local me = vexor.players.local_player()
print("me", me)
if me then
vexor.debug.inspect(me.address)
if me.parts and me.parts.HumanoidRootPart then
vexor.debug.hex(me.parts.HumanoidRootPart.primitive, 64)
end
end
5. MM2 roles
if vexor.game.is_mm2() then
for _, p in ipairs(vexor.players.list()) do
print(p.name, vexor.game.mm2_role(p.address), p.mm2_role)
end
end