Dawn Documentation
Dawn 2.0.0 · engine build 9ac8595712b447a7 · October 21, 2026
Overview
Dawn is a Roblox script executor in three layers: Dawn.exe, a host that embeds WebView2 for the interface; DawnInjector.exe, a console shell that loads the engine; and DawnModule.dll, the engine itself — injection, Luau compilation and signing, the execution environment, and a local HTTP bridge the UI talks to.
Dawn.exe ──exec POST──▶ DawnModule.dll (HTTP bridge, 127.0.0.1:6969)
│ compile Luau source
│ solve bytecode signature
│ zstd/RSB1 container
▼
RobloxPlayerBeta.dll ◀──loaded code (level 8 VM)
The engine ships as tea_entry inside the DLL. The shell only boots a console, maps the DLL with LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR, and hands over.
Getting started
Dawn.exe (in Final EXE\) is the user-facing entry. It stages files, launches the engine against a --pid target, and presents the editor, script hub, accounts, and settings pages.
- Run
Dawn.exe. - Open Roblox and join a game (the engine gates on the map name
Ugc— on the home screen you get told to join a game first). - Inject. The console window narrates attach and then hides itself for the session.
- The UI reads
bridge_session.txtto learn the per-session token and which client is attached. - Execute scripts from the editor or script hub; run
TrUnc\TrUnc-Ultimate.luauto verify the environment.
DawnInjector.exe can also run standalone for debugging: visible console, waits on system("pause"), and stops on the same map-name gates.Execution pipeline
Every payload — typed in the editor, pulled from the script hub, or recursed through loadstring — travels the same road:
- Source rewrite. The engine parses the script with the vendored Luau parser and rewrites
game.HttpGet/game.HttpGetAsyncaccess to the executor's own HTTP path, preserving positions and line breaks (bound locals that aliasgameare resolved solocal g = game; g:HttpGet(...)works too). - Compile. Source compiles to Luau bytecode inside the injected module with the same options ranges Roblox uses:
debugLevel 1,optimizationLevel 1, mutable globalsGame, Workspace, game, plugin, script, shared, workspace, _G, _ENV, and the Vector3 constructor declared for the vector lib. - Encode. A custom
BytecodeEncodermultiplies every opcode byte by227— the same scramble Roblox's loader applies — so the client accepts the stream. - Sign. The signer appends Roblox-format bytecode signatures (see Bytecode signer). Without tables present, the engine still sends unsigned and warns.
- Container. The signed blob is packed:
"RSB1"magic + 4-byte size + zstd-compressed payload, encrypted with an XXH32-derived rolling XOR key — byte-compatible with Roblox's own script container format. - Load.
luavm_loadis called on the client's reallua_Stateat identity 8; the wrapper raises thread capabilities to0x3FFFFFFFFFFFFFFFand every nested proto's userdata walks to full capabilities. The closure is then queued on the client scheduler viatask.deferand runs on Roblox's own thread — no foreign threads inside the VM.
Environment reference
The environment is installed by executing env.luau through the client's own VM at attach time. It wraps the sandbox env metatable, keeps the original __index, and exposes the executor globals below. Verified with the bundled TrUnc/TrUnc-Ultimate suites.
getgenv & the tea table
local tea = getgenv() -- shared executor environment
tea.getgenv = tea -- alias, for suites that call it as getgenv()
All executor globals are mirrored onto the script env via rawset: identifyexecutor, getexecutorname, httpget, loadstring, request, getgenv.
identifyexecutor / getexecutorname
return "Tea", "2.0.0" -- (name, version) — engine codename retained
request(options)
Full HTTP through your own executor session. Validated fields:
| Field | Type | Limits |
|---|---|---|
Url | string, required | 1–4096 bytes, http:// or https:// |
Method | string | defaults to "GET", uppercased |
Headers | table of string→string | max 128 entries, ≤60,000 bytes total |
Body | string | ≤10 MiB |
Requests are framed (length-prefixed fields over the /http/ route) and resolved through HttpService:RequestInternal into the local bridge, so external HTTP happens from inside the client — same thread, same session cookies. Deadline 120 s.
Returns a response table:
{ Success = true if 2xx, StatusCode, StatusMessage, Headers, Body }
httpget(url)
GET convenience around request: attaches the executor fingerprint headers (Exploit-Guid and Fingerprint) that upstream ecosystems expect, errors on non-2xx with the status code, returns the body as a string.
game.HttpGet / game.HttpGetAsync
game properties HttpGet/HttpGetAsync are intercepted at compile time; game:HttpGet(url) and game.HttpGet(url) both route into httpget (self-asserted DataModel), and other __index stays on the real game. The rewrite also matches class-first style game.HttpGet(url) calls by regenerating the enclosing expression — round-trip identical source semantics.
loadstring(source, chunkname)
Compiles on the injected side, returns a function in the caller's environment:
local fn = loadstring("print('hi')", "=example")
fn()
- Source ≤10 MiB; chunkname ≤256 bytes, no NUL; defaults to
"=loadstring". - Transport looks like HTTP but is actually a socket RPC:
POST /load/<token>/<guid>/<hexchunkname>viaRequestInternal, then the bridge deliverstea.__load_pending[guid] = { fn = ... }and the waiting thread setssetfenvand yields the function. - Timeouts at 120 s both directions; a bench-dropped wrapper returns
"compiled function was not delivered"rather than a stale one.
Bridge protocol
The bridge is a hand-rolled `Ws2_32` HTTP-over-TCP server, loopback-only, bound with SO_EXCLUSIVEADDRUSE on port 6969. Tokens are 16 random bytes from BCryptGenRandom, hex-encoded (32 chars). A session restarts on a fresh token — never persisted, only published in bridge_session.txt.
| Route | Shape | Body | Semantics |
|---|---|---|---|
POST /exec/<token>/<hexchrome> |
chunkname as hex in the path | Luau source | Direct execution. Compiles, then wraps in pcall(function() ... end) and defers — UI action scripts. No tea.__load_pending involvement. |
POST /load/<token>/<guid>/<hex> |
guid = 36-char, chunkname hex |
Luau source | loadstring RPC: validates guid+hex, deposits { fn = ... } into tea.__load_pending[guid]. Body must match the pending table to resolve. |
POST /http/<token> |
token only | framed request (below) | Outbound HTTP from inside the client; returns a framed response. 10 MiB payload cap (+64 KiB framing headroom). |
Wire framing (tea.request)
Length-prefixed fields: <decimal byte-length>\n<bytes>, order: url, method, header-count, [name, value]…, body. Response answer fields: status, message, header-count, [name, value]…, body. Mismatched length or trailing bytes abort validation rather than playing it loose — frame tolerance is a security surface, not a convenience.
Raw protocol for local tools
A connect that sends anything not starting with POST is interpreted as the length-prefixed direct protocol:
0 4 bytes network-order uint32 script length (1 .. 10 MiB)
4 length bytes Luau source, executed directly (no pcall wrapper)
1-byte receive timeout for the probe, 15 s for full HTTP bodies.
On-disk files
| File | Purpose |
|---|---|
DawnModule.dll | The engine: injection, execution, signer, bridge. |
DawnInjector.exe | Console shell that hosts the DLL (LoadLibraryExW → tea_entry). |
bridge_boot.txt | Append-only engine trace, timestamped to the ms — everything decisive in attach lands here. |
bridge_session.txt | Session token and attached PID, cleared on exit. Read by Dawn.exe and local tooling. |
bridge_boot.txt | Written by both shell and engine; a spawn that dies leaves the reason behind. |
env.luau | Environment bootstrap delivered first through the client's own VM. |
tables\signature-*.bin | Picked model tables for the bytecode signer: constant, linear-a, linear-b, quadratic. |
Injection internals
Dawn's injection path is deliberately not the classic CreateRemoteThread+LoadLibrary signature. Pipeline:
SeDebugPrivilegeis enabled when the token can hold it (warned, not fatal — same-user medium-IL clients don't need it).- Roblox's PID is located and a full handle (
PROCESS_ALL_ACCESS) opened. Module base is resolved from the map path. - Every handle 4–0x10000 steps 4 through
DuplicateHandle+NtQueryObjectuntil the type name matches I/O Completion; that's Roblox's own completion port, and we keep the duplicated handle. - Committed
PAGE_READWRITEuser-space regions are scanned for a zero-fill code cave ≥ the payload size, chunked reads with overlap-aware stride. - Shellcode is
VirtualAllocEx'dPAGE_EXECUTE_READWRITE, a direct-inject stub stamped with its address is written into the cave, andZwSetIoCompletionfires on Roblox's completion port with the cave as the completion key — the stub executes on a thread that belongs to the client; nothing foreign is created.
Exit codes: 0 attached and bridge live, 2 unknown/gated map (rejoin), 3 uncaught std::exception, 4 non-standard exception. Everything posts to bridge_boot.txt first; inject_trace.txt with Error 3 is the classic "no handle to Roblox" box when you run the shell before launching a client.
Bytecode signer
Since Roblox verifies Luau bytecode signatures, executing unsigned bytecode is a mutual-exclusion gate: the client turns validation errors into a VM crash. Dawn solves it as a fitted model instead of a cracked constant:
- The bytecode is capped/zero-padded to ≥256 bytes (64 MiB max) and hashed BLAKE3, first 32 bytes taken from offset 4, then truncated to 12 bytes.
- A solver — assembled from four lookup/interpolation tables (
signature-constant,signature-linear-a,signature-linear-b,signature-quadratic) over a 128-bit two-word value — maps the 12-byte digest to Roblox's 16-byte signature. - A 24-byte trailer is appended: the signature, then
sig[i] ^ 0x87187601 (key_value LE)for bytes 16–19 andsig[i] ^ marker[0x32C46A…]for bytes 20–23. - The signer self-verifies every signed blob locally before it leaves the process — a signature that wouldn't pass Roblox's checker never goes into a container.
tables\ is missing the engine still sends unsigned with a console/trace warning — degraded mode for dev builds, not the run path.Offsets & updates
Roblox versions shift everything. What currently maps to build 9ac8595712b447a7 (oct 21 dump):
| Symbol | Current | Model of thinking |
|---|---|---|
print | 0x1D071A0 | console route for chunk errors |
task_defer | 0x43C38D0 | queue a wrapper run on the client scheduler |
get_global_state | 0x425F7A0 | script context → lua_State |
luavm_load | 0x42560B0 | compile-and-load container into the real VM |
fake_data_model | 0x8F59810 | DataModel pointer, +0x1f8 → real |
script_context | DataModel +0x440 | upstream of lua_State |
| Luau VM layout | see offsets.h | lua_top, g_registry, proto userdata, udatadirect slots (stride 0x48, 130 slots) |
Update procedure: re-dump the module against the new build, update src/roblox/offsets.h, rebuild the module (below), then attach and run TrUnc-Ultimate. On a mismatch the engine refuses to trust itself — the registry probe and hooks probe print specific field failures (mainthread.global=…, registry tvalue.tt=…) so you know exactly which deref lies.
get_name(data_model) decides the mode: LuaApp means you're on the home screen ("join a game first"), Ugc means a real place is loaded. Anything else is a rejoin.Building
build.ps1 drives MSBuild for the native pair and dotnet build for the app:
# fresh Dawn-Module.dll + DawnInjector.exe + Dawn.exe (Release)
.\build.ps1
# or skip steps:
.\build.ps1 -SkipModule -SkipInjector
Fresh binaries stage from the module/injector output dirs into Final EXE\tools\ (existing files are .bak'd with a timestamp). Uplift to shipping is a manual checklist — the script prints it:
- Back up
Final EXE\tools\Dawn-Module.dll+DawnInjector.exe. - Copy fresh
Dawn-Module.dll+DawnInjector.exeintoFinal EXE\tools\. - Copy fresh
Dawn.exe(and.config, WebView2/Newtonsoft DLLs when changed). - Rename
Final EXE\uiaside, copy freshui\over. - Launch, pick the theme, inject, run
TrUnc-Ultimate.luau.
Testing
TrUnc and TrUnc-Ultimate (in TrUnc\) are the environment acceptance suites. They check every executor global the ecosystem relies on, print per-test ✅/⛔/⚠️ lines, and end with a TrUNC rate. Run them after every update — TrUltimate raising one alias means one rawset forgotten, not a broken engine.
Security model
- Loopback only. The bridge binds
127.0.0.1; nothing else can even complete a handshake. - Token per session. 16 bytes of OS RNG, hex; invalid token ⇒ HTTP 401 before parsing anything further.
- Size-capped everything. 10 MiB scripts/HTTP payloads, 8 KiB HTTP header line, 60 KB total request headers, 4096-byte URLs — every field has a ceiling, not a hope.
- Bind exclusivity.
SO_EXCLUSIVEADDRUSEprevents relay binding hijack of port 6969 while the session is live. - Self-verifying output. Signed bytecode validates before delivery; malformed frames abort rather than approximate.
FAQ
"join a game first" on launch. The DataModel is the home screen (LuaApp). Join a real place and re-run.
"Error 3" in inject_trace.txt. OpenProcess failed — the client wasn't running, or it's a UWP/elevated mismatch. Launch Roblox first.
Port 6969 already held at bind. Something else on this machine grabbed the loopback — the trace says so. Kill it or move this session to a fresh boot.
Every script errors with a stack-top string. That's the compiler returning a message instead of a closure — read it; usually the script itself, occasionally a stale offsets build.
Executor name reads "Tea". Internal codename; the export surface is the shipped one. Affects nothing but trivia.
Questions past this page: Discord.