AegisLoader Docs

Introduction

AegisLoader protects your Roblox Luau and delivers it per run. Your script ships on a custom engine — renamed, string-encrypted, and virtualized — so the original source never leaves readable, and each download is armed and licensed on its own.

How protection works

When you upload a script it runs through four transforms before it ships:

  • Rename — every local, global and field becomes a meaningless token.
  • String encryption — string constants are sealed and decoded at runtime, never shipped in the clear.
  • Number rewriting — numeric literals are re-spelled into equivalent, unreadable forms.
  • Virtualization — your logic is translated into instructions for an engine shipped inside your build, so it never exists as readable Luau.

The first three are near-free at runtime. Virtualization is the strongest layer — and the heaviest to run, which is why hot code gets its own directive.

Directives

A directive is a one-line marker you place in your own source to tune how a piece of it is protected. Two rules hold for every directive:

  • It is identity at runtime — a wrapped value behaves exactly as it would without the marker, so your script still runs when executed raw (unprotected). See Shimming.
  • The AEGIS_ prefix is reserved. Don't name your own locals or globals AEGIS_*; the obfuscator reads those names and rewrites them.

More directives land here as they ship. Each gets its own page with the exact rules and its raw-mode stub.

Integrating

Free, Paid and Ad builds ship a complete loader line — you paste one line and you're done. If you run Custom mode, your own UI fetches and applies the key; the Integration API documents those endpoints.

Directive · Performance

AEGIS_FASTLANEAEGIS_FASTLANE(fn) → fn

Run your hottest functions at native Luau speed — without taking protection off them.

Virtualized code is slow on a hot path: every step is fetched and decoded one at a time, which is invisible once a frame and a freeze sixty times a frame. AEGIS_FASTLANE marks a single function to build on the light tier — it runs as native Luau, but is still renamed and string-encrypted.

i
Light is not “off”. A fast-lane function is still renamed and its strings still encrypted — cheaply protected, never a readable hole.

Tiers

Heavy · default

Virtualized

  • Runs on the engine
  • Opcodes gone — no native Luau
  • Strongest; slowest per call
Light · AEGIS_FASTLANE

Native

  • Runs as native Luau
  • Still renamed + string-encrypted
  • Native speed; for hot paths

Using it

Add the one-line identity stub at the top of your script so it also runs un-obfuscated, then wrap any hot function literal:

local function AEGIS_FASTLANE(f) return f end  -- stub: identity when run raw

-- a per-frame loop — keep it off the heavy tier
local render = AEGIS_FASTLANE(function(dt)
    esp:redraw(); tracers:update(dt)
end)
RunService.RenderStepped:Connect(render)

-- a metamethod fires on every index
proxy.__index = AEGIS_FASTLANE(function(self, key) return resolve(self, key) end)

The wrapper is an expression that returns the function, so use it anywhere a function goes — assign it, connect it inline, store it, or return it.

When to use it

  • RenderStepped / Heartbeat / Stepped handlers and their helpers.
  • __index / __namecall metamethod hooks.
  • Tight no-yield loops — per-frame math, per-tick scans.

Rule of thumb: more than a handful of times per frame → light tier. Once at startup or on a click → leave it virtualized. Mark as few functions as you can; each one is moved off the strongest tier.

Common mistakes

  • Passing a named reference — AEGIS_FASTLANE(myFn) does nothing. Wrap the function literal in place.
  • Forgetting to use the return value — the marker returns the function; assign or connect what it gives back.
  • Reusing the reserved name for your own variable.

Rules

ArgumentExactly one function literal written in place. A named reference (AEGIS_FASTLANE(myFn)) does nothing.
ReturnsThe same function, unchanged — the marker is identity at runtime.
FormA bare call, AEGIS_FASTLANE(fn). An indexed call (t.AEGIS_FASTLANE(fn)) is not the directive.
KeptRename, string encryption, number rewriting.
SkippedVirtualization, control-flow flattening, junk insertion.
No markerYour build is byte-for-byte identical to one built without the feature.
Directive · Anti-tamper

AEGIS_FAULTAEGIS_FAULT()

A kill-switch. When your own tamper check decides something is wrong, call it and the protected build cleanly bricks itself for the rest of the run.

AEGIS_FAULT() is the reaction, not the detector — you write the check, it delivers the response. When reached, it scrubs the current VM frame, trips a session flag, and raises an error. From that point every protected function in the run returns nothing, so the script can't limp on in a tampered state.

i
It's the trigger, not the sensor. AEGIS_FAULT detects nothing on its own — its value is only as good as the check you wire to it.

Using it

Add the one-line stub so the raw script still runs, then call it as a bare statement inside your own detection:

local function AEGIS_FAULT() end  -- stub: no-op when run raw

-- your own checks decide WHEN to brick
if isHooked(game.HttpGet) or detectedDumper() then
    AEGIS_FAULT()
end

When to use it

  • Anti-tamper tripwire — a checksum of a critical function, or a honeypot value, failed.
  • Hook / dump-tool detection — a sensitive function was hooked, or a known logger is present.
  • Environment refusal — wrong executor, a sandbox, Studio, or an unexpected PlaceId.
  • Your own blacklist gate, on top of the platform's licensing.

Rules

FormA bare statement, AEGIS_FAULT(), with no arguments. Using it in an expression (return AEGIS_FAULT()), with arguments, or as call sugar fails the build — never a silent no-op.
EffectScrubs the VM frame, sets a session flag, scrubs the live key, and errors. Every later protected call in the run then halts too.
ScopeThe current run only — it's in-memory, so re-executing the script starts clean. For a persistent, cross-run ban use your key system's revocation, not this.
pcallCall it where the error can unwind. A fault buried inside a pcall your script survives won't stop that frame (the session flag still halts the next protected call).
No markerYour build is byte-for-byte identical to one built without the feature.
!
What it isn't. Not an anti-dump measure — someone who already pulled the decrypted code keeps it. It raises the cost of running a tampered build; it isn't a wall.
Directive · Secrets

AEGIS_SEALAEGIS_SEAL(value) → value

Force a specific string or number out of the shipped payload and into the loader-delivered vault — even when the automatic vault's budget would otherwise leave it in the clear.

AegisLoader already pulls string and number constants into a server-delivered table automatically, so they read nil in an offline dump. That table has a size budget, though — on a large script the budget fills and the overflow literals ship raw. AEGIS_SEAL guarantees a chosen value (a webhook, a token) is vaulted regardless, so it's never baked into the build.

i
Mostly for large scripts. On a small script your strings are already auto-vaulted — SEAL is redundant there. It earns its keep when that budget is exhausted, or for a tiny value the heuristics skip.

Using it

Add the identity stub, then wrap a single literal — assign it to one local and reference that:

local function AEGIS_SEAL(v) return v end  -- stub: identity when run raw

local WEBHOOK = AEGIS_SEAL("https://discord.com/api/webhooks/...")

Rules

ArgumentExactly one string or number literal, written in place. A variable or expression (AEGIS_SEAL(x), AEGIS_SEAL(a..b)) fails the build.
ReturnsThe value, unchanged — identity at runtime.
EffectThe literal becomes a vault lookup; it is not in the shipped build and reads nil on an offline dump.
DeclinesBest-effort: if the enclosing function is at Luau's local cap, SEAL ships the value raw rather than risk the build — it never bricks.
No markerYour build is byte-for-byte identical to one built without the feature.
!
Not at-rest encryption. The vault is delivered to anyone who runs the loader — SEAL keeps the value out of the static build and offline dumps; it doesn't hide it from a live, licensed run.
Guide

Shimming

Keep your script runnable un-obfuscated. A directive is a real name the obfuscator reads, so define a one-line stub and the raw file still runs in Studio.

When you protect a script, the obfuscator recognizes each directive and strips it — AEGIS_FASTLANE(fn) becomes just fn, and so on. But the raw file you test with has no such step: calling AEGIS_FASTLANE there would error with attempt to call a nil value. A shim is a passthrough definition that makes the directive a no-op when run raw.

The pattern

Every directive has a one-line identity stub. Define the ones you use at the top of your script:

-- raw-mode stubs — harmless, stripped when protected
local function AEGIS_FASTLANE(f) return f end
local function AEGIS_FAULT() end
local function AEGIS_SEAL(v) return v end

Each directive's page lists its own stub. The rule is always the same: the stub returns its value unchanged (or does nothing), so your logic is identical whether or not the build is protected.

i
Define it once. The obfuscator removes your stub and replaces the call with the real behavior, so a shimmed directive costs nothing in the protected build.

Reserved names

The AEGIS_ prefix is reserved for directives. Don't name your own locals, globals or fields AEGIS_* — the obfuscator reads those names, and a collision will be rewritten or rejected. Keep your stubs the only AEGIS_ identifiers in the file.

Guide

Performance

Protection has a runtime cost, and most of it is avoidable. Where it matters is the heavy tier on hot code — and that's exactly what the directives are for.

Where the cost is

Rename, string encryption and number rewriting are near-free — they change how your code reads, not how fast it runs. Virtualization is the heavy layer: your logic runs on an engine, so one native operation becomes several engine steps. On code that runs once, you never feel it. On code that runs sixty times a frame, you do.

Keep hot code native

Mark genuinely hot functions with AEGIS_FASTLANE — they run as native Luau while staying renamed and string-encrypted. Reach for it on:

  • RenderStepped / Heartbeat / Stepped loops and the helpers they call every frame.
  • __index / __namecall hooks, which fire on every access.
  • Tight per-tick math and scans.

Mark as few functions as possible — each one leaves the strongest tier. Protect everything by default; fast-lane only what you've measured to be hot.

Optimize before you protect

Because one source operation can expand into many engine steps, sloppy code is far more expensive protected than raw. Cache lookups and cut redundant work first:

-- before: four chained lookups, every frame
a.b.c.d.x = 1
a.b.c.d.y = 2

-- after: resolve once
local t = a.b.c.d
t.x = 1
t.y = 2

Fewer instructions is always cheaper — and the win is larger once the code is on the engine.

!
The one hard ceiling. A function doing heavy math every single frame is costly even native. There's no obfuscator trick for that — profile it, cut the work, and fast-lane what remains.
Guide

Compatibility & limits

What changes once a script is protected. None of this affects normal game code — it matters only if you introspect your own functions or depend on exact runtime details.

The debug library

Protected functions are not native Luau closures, so the debug library sees the engine, not your code. debug.getinfo, debug.getlocal, debug.getupvalue and debug.traceback return engine internals or nothing useful for your own protected functions. Don't build logic on introspecting your obfuscated code.

Errors & line numbers

The original local, upvalue and function names are stripped and can't be recovered, so they won't appear in error messages. Line numbers and error text from protected code won't match the raw file — don't match on exact error strings. Errors still propagate and pcall still catches them.

Very large functions

Luau caps a single function at 200 locals/registers. A function already near that ceiling may be kept native rather than virtualized (never mis-compiled, never dropped). If you have one enormous function, splitting it into smaller ones protects it better and runs faster.

Executors & environment

  • Builds run on mainstream executors. The loader probes for what it needs and fails safe if something is missing — it needs game:HttpGet, loadstring, and standard crypt/base64 functions.
  • loadstring(string.dump(f)) on a protected function won't reproduce it — its bytecode is not your function's.
  • Tricks that depend on a native call-stack level (getfenv/setfenv by level) behave differently through the engine.
i
Mark it native if you must. If a function genuinely needs native semantics (your own debug introspection, exact stack levels), wrap it in AEGIS_FASTLANE — it stays renamed and string-encrypted but runs as real Luau.
Integration

Integration API

For Custom mode: your own in-game UI fetches and applies the key, then runs the loader. These are AegisLoader's licensing endpoints — public, called from your script, no API key.

i
Base URL https://api.aegisloader.com. Replace <id> with your project or key-system id, and compute hwid with RbxAnalyticsService:GetClientId(). Free, Paid and Ad builds don't need any of this — they ship a complete loader line.

The getgenv().Key convention

A Custom-mode loader arms by reading getgenv().Key. Set it to the player's key, then run the loader line — nothing else to wire:

getgenv().Key = key
loadstring(game:HttpGet("https://api.aegisloader.com/loader/<id>"))()

The key stays set for the session, so several Custom scripts sharing one key system each read the same getgenv().Key. You don't send the HWID to the loader — it computes its own.

Endpoints

EndpointReturns
GET /loader/<id>The build. loadstring + run it.
GET /api/validate/<id>?key=&hwid={ ok, mode, expires, reason } — check a key for a device. Doesn't bind it.
GET /api/keysource/<id>{ ok, shared, key_system, mode } — whether a script uses its key system's key or its own.
GET /gate/<id>=<token>The ad-unlock page. Open it in a browser.
GET /gate/<id>=<token>/wait{} while pending, then { key } once earned.

Validate a key

Check a key before you run the loader. ok is the verdict; reason explains a failure (expired, banned, hwid_mismatch, not_found).

local Http = game:GetService("HttpService")
local hwid = game:GetService("RbxAnalyticsService"):GetClientId()
local url = "https://api.aegisloader.com/api/validate/<id>?key="..key.."&hwid="..hwid

local ok, res = pcall(function() return Http:JSONDecode(game:HttpGet(url)) end)
if ok and res.ok then
    getgenv().Key = key
    loadstring(game:HttpGet("https://api.aegisloader.com/loader/<id>"))()
else
    warn("key rejected: "..tostring(res and res.reason))
end

Ad unlock

Generate a token, send the player to the gate page, and poll until they've earned a key. The /wait call long-polls — it returns {} on timeout, so just call it again.

local Http = game:GetService("HttpService")
local token = Http:GenerateGUID(false)
setclipboard("https://api.aegisloader.com/gate/<id>="..token)  -- player opens it

while true do
    task.wait(3)
    local r = Http:JSONDecode(game:HttpGet("https://api.aegisloader.com/gate/<id>="..token.."/wait"))
    if r.key then
        getgenv().Key = r.key
        loadstring(game:HttpGet("https://api.aegisloader.com/loader/<id>"))()
        break
    end
end

Key source

If your script is attached to a key system, /api/keysource tells you whether it validates against the key system (shared: true) or its own licenses (shared: false) — so one UI can drive several scripts and gate each against the right id.

!
Wrap network calls in pcall. HttpGet throws on a dropped request; never let a blip brick a paying player. Treat a failed validate as "try again," not "rejected."