> For the complete documentation index, see [llms.txt](https://docs.hungquan99.lol/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hungquan99.lol/macro-documentation/centurion-macros.md).

# Centurion Macros

The CTN\_\* macros of Centurion, the obfuscator HQ99 Auth uses to protect your scripts.

Centurion is the obfuscator HQ99 Auth uses to protect your scripts. Its macros are special names you write in your Lua source. When you obfuscate, Centurion replaces each one with generated code, so macros work at **build time**, before your script runs. Nothing needs to be enabled. A few simple rules apply:

* Write each macro exactly as shown (a bare name, or a call).
* Where a **literal** is required (a string, number or function), you must write it directly. Variables and expressions are rejected.
* Don't name your own variables `CTN_*`. The prefix is reserved, and the build fails on unknown `CTN_` names.

{% hint style="warning" %}
**Running the file without obfuscating it?** In raw source every macro is an undefined global. Info macros read as `nil`, but **call macros error** with `attempt to call a nil value`. Wrap them in `if CTN_OBFUSCATED then ... end`.
{% endhint %}

## Quick reference

| Macro                                                     | Type  | Purpose                                                    |
| --------------------------------------------------------- | ----- | ---------------------------------------------------------- |
| [`CTN_LINE`](#ctn_line)                                   | value | Current source line number.                                |
| [`CTN_OBFUSCATED`](#ctn_obfuscated)                       | value | `true` once obfuscated, `nil` in raw source.               |
| [`CTN_VERSION`](#ctn_version)                             | value | Centurion engine version.                                  |
| [`CTN_CRASH`](#ctn_crash)                                 | call  | Crash the VM on purpose.                                   |
| [`CTN_CLEAR_STACK`](#ctn_clear_stack)                     | call  | Wipe local variables so secrets can't be dumped.           |
| [`CTN_PRECHECK`](#ctn_precheck)                           | call  | Startup check that must pass before the script runs.       |
| [`CTN_NO_VIRTUALIZE`](#ctn_no_virtualize)                 | call  | Run a function natively, outside the VM.                   |
| [`CTN_JIT`](#ctn_jit)                                     | call  | Speed up a function while keeping it in the VM.            |
| [`CTN_INLINE`](#ctn_inline)                               | call  | Inline a function at the call site.                        |
| [`CTN_ENCRYPT_STRING`](#ctn_encrypt_string)               | call  | Add an extra encryption layer to one string.               |
| [`CTN_ENCRYPT_NUMBER`](#ctn_encrypt_number)               | call  | Add an extra encryption layer to one number.               |
| [`CTN_ENCRYPT_FUNCTION`](#ctn_encrypt_function)           | call  | Encrypt a function body with your own key.                 |
| [`CTN_HTTP_ENCRYPT_FUNCTION`](#ctn_http_encrypt_function) | call  | Encrypt a function body with a Centurion key (Enterprise). |

## Info macros

These are bare names. Don't add `()`, because `CTN_LINE()` is a compile error.

### CTN\_LINE

The source line number where it appears. It still works when the `disable_line_information` option removes line info, so it is handy for error messages.

### CTN\_OBFUSCATED

`true` in the obfuscated build and `nil` in raw source. It is resolved at build time, so any branch that can't run is **removed from the output** (including `else` arms and the strings inside them).

### CTN\_VERSION

The engine version as a string, such as `"6.0"`, or `nil` in raw source. Useful in bug reports.

```lua
if not CTN_OBFUSCATED then
    error("Obfuscate this script before running it.")
end

print(("build: centurion %s | line %d"):format(CTN_VERSION, CTN_LINE))
```

{% hint style="info" %}
Errors inside a removed branch are never reported, so keep testing the raw source too. To refuse raw execution use `error()`, not `CTN_CRASH`, which would only fail with `attempt to call a nil value`.
{% endhint %}

## Protection macros

### CTN\_CRASH

```lua
CTN_CRASH()
```

Crashes the VM at that point. It **can't be caught**: `pcall` doesn't help, no error is shown, and on Roblox the thread simply hangs. Use it as the failure branch of your own tamper checks.

```lua
if CTN_OBFUSCATED and GetScriptHash() ~= ExpectedHash then
    CTN_CRASH()
end
```

### CTN\_CLEAR\_STACK

```lua
CTN_CLEAR_STACK()
```

Sets every local the current function has declared so far to `nil`, so a used secret doesn't stay in memory. **Parameters, upvalues, globals and table fields are kept.**

```lua
local Key = DeriveKey()
UseKey(Key)

CTN_CLEAR_STACK() -- Key is now nil
```

* Use it as its own statement.
* It can't be used inside `CTN_NO_VIRTUALIZE`.
* Anything you still need must be a parameter, an upvalue, or stored in a table.

### CTN\_PRECHECK

```lua
CTN_PRECHECK(function()
    return 6 * 7
end, 42)
```

Registers a check that runs once at startup, before your script. If the result doesn't match the expected value (or the check errors), startup **aborts**.

| Part           | Rule                                                                          |
| -------------- | ----------------------------------------------------------------------------- |
| Function       | Anonymous, no parameters. It can't use locals from outside its body.          |
| Expected value | An integer literal, or a table of integer literals like `{1, 2, 3}`.          |
| Result         | Only the first return value counts. Return one table to check several values. |

{% hint style="warning" %}
This is an environment check, not authentication. Anyone who reproduces the expected result passes it. Make the function test real behavior or host state, not return the answer directly. For server-controlled access use [`CTN_ENCRYPT_FUNCTION`](#ctn_encrypt_function).
{% endhint %}

## Performance macros

All three take exactly **one function literal**.

| Macro               | What it does                                                                    | Trade-off                                                          |
| ------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `CTN_NO_VIRTUALIZE` | Runs the function as normal Lua, outside the VM.                                | Fastest, but less protected. No other macros inside.               |
| `CTN_JIT`           | Speeds the function up while it stays in the VM. Nested functions are included. | Somewhat less protection. Small scripts gain little.               |
| `CTN_INLINE`        | Pastes the body into the surrounding code when you call it right away.          | Only a request: it falls back to a normal call if it can't inline. |

### CTN\_NO\_VIRTUALIZE

```lua
local dot = CTN_NO_VIRTUALIZE(function(ax, ay, bx, by)
    return ax * bx + ay * by
end)
```

Use it for hot loops that don't contain sensitive logic.

### CTN\_JIT

```lua
local SumSquares = CTN_JIT(function(N)
    local Total = 0
    for I = 1, N do
        Total = Total + I * I
    end
    return Total
end)
```

Benchmark first and only mark functions that take a real share of run time.

### CTN\_INLINE

```lua
local Sum = CTN_INLINE(function(A, B) return A + B end)(2, 3) -- inlined
local F   = CTN_INLINE(function(A, B) return A + B end)       -- normal function
```

Add `()` right after the macro to run the body now. Without it you just get an ordinary function back. A `return` inside an inlined body only leaves that body.

{% hint style="danger" %}
**Changed from older versions.** `local Total = CTN_INLINE(function() return 5 end)` now stores a function, not `5`. Add `()` to get the result.
{% endhint %}

## Encryption macros

### CTN\_ENCRYPT\_STRING

```lua
local Token = CTN_ENCRYPT_STRING("super-secret-token")
```

Every string is already encrypted at build time. This adds a **separate** decryption routine for one string, so use it only for the few that matter. The argument must be a single string literal: no variables and no `..`.

### CTN\_ENCRYPT\_NUMBER

```lua
local Threshold = CTN_ENCRYPT_NUMBER(1337)
```

Hides one number constant, such as a licence check value or an opcode. It adds a small cost, so don't wrap every number. The argument must be a single finite number literal. Negative and hex values like `-33` or `0xDEADBEEF` are fine.

### CTN\_ENCRYPT\_FUNCTION

```lua
local Protected = CTN_ENCRYPT_FUNCTION(function(UserId)
    return ComputeEntitlement(UserId)
end, "1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f809", RequestKeyFromServer())
```

Encrypts a function body with a key **you choose**. The script must provide the same key at runtime to unlock it.

| # | Argument       | Rule                                                                              |
| - | -------------- | --------------------------------------------------------------------------------- |
| 1 | Function       | An inline `function ... end`.                                                     |
| 2 | Build-time key | A string literal of exactly **64 hex characters**.                                |
| 3 | Runtime key    | An expression that fetches the key, such as a call to your server. Not a literal. |

The runtime key is checked when the macro line runs. A wrong key fails right there, before the function can be used. `ComputeEntitlement` and `RequestKeyFromServer` are placeholders you write yourself.

{% hint style="warning" %}
Keep the runtime key **on your server**. Hiding it in a string, an encrypted literal or split pieces doesn't protect it, because it can be recovered from the output.
{% endhint %}

### CTN\_HTTP\_ENCRYPT\_FUNCTION

{% hint style="info" %}
**Enterprise only.** Enabled on request for your Centurion API key, through their Discord server.
{% endhint %}

```lua
local Admin = CTN_HTTP_ENCRYPT_FUNCTION(function(Command)
    return RunAdminCommand(Command)
end, "admin", FetchKeyFromServer("admin"))
```

Same idea as `CTN_ENCRYPT_FUNCTION`, but **Centurion generates the key** for you. The second argument is a unique name for the function (each name can be used once per script).

{% stepper %}
{% step %}
Obfuscate your script. The response contains a `keys` object, such as `{ "admin": "7c4b91e0..." }`.
{% endstep %}

{% step %}
Save those keys on your server right away. Centurion **doesn't store them**, and rebuilding creates new keys.
{% endstep %}

{% step %}
Make your script request the matching key from your server before it calls the function.
{% endstep %}
{% endstepper %}

The macro doesn't make any HTTP request itself, and `FetchKeyFromServer` is a placeholder you write.

***

Condensed from the official Centurion documentation at [docs.centurion.best](https://docs.centurion.best/), which has the latest details.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.hungquan99.lol/macro-documentation/centurion-macros.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
