AOBMaker - AOBMaker: An Automated AA Scripting Utility for Advanced Pattern Generation and Relocation
AOBMaker helps you create AutoAssembler (AA) scripts from Cheat Engine disassembly. It can auto generate AOB pattern, mask registers or offsets, check injection safety, and help in game-update AOB relocation. You don't need to do manual AOB hex mask work. A .ct table stability check tool is included. Run it when CE is attached to the same process without any script enabled will be perfect.
Also include auto-script for multiplier / address capture / clamp asm code generation. Create basic structure of script with criteria.
AOBMaker — Adaptive AA Script Generator for Cheat Engine
Paste CE disassembly in, get a working AutoAssembler script out.
Windows / .NET 10 Native AOT. Single exe, no installer, no runtime to install.
Free and open source: https://github.com/bbfox0703/AOBMaker
1. What it does
Writing an AA script is not hard. The time goes to the boring part:
- Pick the AOB by hand, type the
??by hand, guess which bytes will change after game update. - Count the NOP padding. Double check that nothing jumps into the middle of your 5-byte JMP.
- Game patches. Whole table breaks. Re-find every injection point one by one.
AOBMaker decodes the instruction with Iced.NET instead of doing text or byte guesswork, so the wildcards land exactly on the register-dependent bytes. Then it emits the full
[ENABLE] / [DISABLE] script for you.
On top of that it ships the whole toolbox around CE: game-update relocation, whole-table health check, a CE-independent memory scanner, and two-way plugin IPC.
Example: Unique AOB pattern auto calculation with masks. (Final: F3 0F 5C ?? F3 0F 5E ?? EB ?? 0F 28 ?? F3 0F 11 ?? 68)
Spoiler
2026-07-16 22:35:36.395 +08:00 [INF] AOB Scan: Pattern=F3 0F 5C C6, Segs=182, Total=209.1MB (ExecutableOnly=true) 2026-07-16 22:35:36.402 +08:00 [INF] AOB scan complete: 9 matches (early stop) 2026-07-16 22:35:36.472 +08:00 [INF] AOB Scan: Pattern=F3 0F 5C C6 F3, Segs=182, Total=209.1MB (ExecutableOnly=true) 2026-07-16 22:35:36.483 +08:00 [INF] AOB scan complete: 7 matches (early stop) 2026-07-16 22:35:36.556 +08:00 [INF] AOB Scan: Pattern=F3 0F 5C C6 F3 0F, Segs=182, Total=209.1MB (ExecutableOnly=true) 2026-07-16 22:35:36.578 +08:00 [INF] AOB scan complete: 5 matches (early stop) 2026-07-16 22:35:36.648 +08:00 [INF] AOB Scan: Pattern=F3 0F 5C C6 F3 0F 5E, Segs=182, Total=209.1MB (ExecutableOnly=true) 2026-07-16 22:35:36.666 +08:00 [INF] AOB scan complete: 3 matches (early stop) 2026-07-16 22:35:36.737 +08:00 [INF] AOB Scan: Pattern=F3 0F 5C C6 F3 0F 5E C1, Segs=182, Total=209.1MB (ExecutableOnly=true) 2026-07-16 22:35:36.764 +08:00 [INF] AOB scan complete: 2 matches (early stop) 2026-07-16 22:35:36.834 +08:00 [INF] AOB Scan: Pattern=F3 0F 5C C6 F3 0F 5E C1 EB, Segs=182, Total=209.1MB (ExecutableOnly=true) 2026-07-16 22:35:36.861 +08:00 [INF] AOB scan complete: 2 matches (early stop) 2026-07-16 22:35:36.933 +08:00 [INF] AOB Scan: Pattern=F3 0F 5C C6 F3 0F 5E C1 EB 03, Segs=182, Total=209.1MB (ExecutableOnly=true) 2026-07-16 22:35:36.961 +08:00 [INF] AOB scan complete: 2 matches (early stop) 2026-07-16 22:35:37.032 +08:00 [INF] AOB Scan: Pattern=F3 0F 5C C6 F3 0F 5E C1 EB 03 0F, Segs=182, Total=209.1MB (ExecutableOnly=true) 2026-07-16 22:35:37.049 +08:00 [INF] AOB scan complete: 2 matches (early stop) 2026-07-16 22:35:37.121 +08:00 [INF] AOB Scan: Pattern=F3 0F 5C C6 F3 0F 5E C1 EB 03 0F 28, Segs=182, Total=209.1MB (ExecutableOnly=true) 2026-07-16 22:35:37.138 +08:00 [INF] AOB scan complete: 2 matches (early stop) 2026-07-16 22:35:37.211 +08:00 [INF] AOB Scan: Pattern=F3 0F 5C C6 F3 0F 5E C1 EB 03 0F 28 C7, Segs=182, Total=209.1MB (ExecutableOnly=true) 2026-07-16 22:35:37.229 +08:00 [INF] AOB scan complete: 2 matches (early stop) 2026-07-16 22:35:37.299 +08:00 [INF] AOB Scan: Pattern=F3 0F 5C C6 F3 0F 5E C1 EB 03 0F 28 C7 F3, Segs=182, Total=209.1MB (ExecutableOnly=true) 2026-07-16 22:35:37.317 +08:00 [INF] AOB scan complete: 2 matches (early stop) 2026-07-16 22:35:37.389 +08:00 [INF] AOB Scan: Pattern=F3 0F 5C C6 F3 0F 5E C1 EB 03 0F 28 C7 F3 0F, Segs=182, Total=209.1MB (ExecutableOnly=true) 2026-07-16 22:35:37.405 +08:00 [INF] AOB scan complete: 2 matches (early stop) 2026-07-16 22:35:37.477 +08:00 [INF] AOB Scan: Pattern=F3 0F 5C C6 F3 0F 5E C1 EB 03 0F 28 C7 F3 0F 11, Segs=182, Total=209.1MB (ExecutableOnly=true) 2026-07-16 22:35:37.494 +08:00 [INF] AOB scan complete: 2 matches (early stop) 2026-07-16 22:35:37.566 +08:00 [INF] AOB Scan: Pattern=F3 0F 5C C6 F3 0F 5E C1 EB 03 0F 28 C7 F3 0F 11 80, Segs=182, Total=209.1MB (ExecutableOnly=true) 2026-07-16 22:35:37.582 +08:00 [INF] AOB scan complete: 2 matches (early stop) 2026-07-16 22:35:37.654 +08:00 [INF] AOB Scan: Pattern=F3 0F 5C C6 F3 0F 5E C1 EB 03 0F 28 C7 F3 0F 11 80 68, Segs=182, Total=209.1MB (ExecutableOnly=true) 2026-07-16 22:35:37.673 +08:00 [INF] AOB scan complete: 1 matches 2026-07-16 22:35:37.673 +08:00 [INF] Mode D Phase 1 END: raw unique FOUND at [4621,4639), 18 bytes 2026-07-16 22:35:37.673 +08:00 [INF] Mode D Phase 2 START: mode "A" masking from [4621,4639), maxExpansion=512 per direction 2026-07-16 22:35:37.743 +08:00 [INF] AOB Scan: Pattern=F3 0F 5C ?? F3 0F 5E ?? EB ?? 0F 28 ?? F3 0F 11 ?? 68, Segs=182, Total=209.1MB (ExecutableOnly=true) 2026-07-16 22:35:37.762 +08:00 [INF] AOB scan complete: 1 matches 2026-07-16 22:35:37.762 +08:00 [INF] Mode D Phase 2 END: mode "A" masking unique FOUND at [4621,4639), 18 bytes 2026-07-16 22:35:37.762 +08:00 [INF] Mode D Phase 3 START: tail trimming, current=18 bytes, usedModeF=false 2026-07-16 22:35:37.834 +08:00 [INF] AOB Scan: Pattern=F3 0F 5C ?? F3 0F 5E ?? EB ?? 0F 28, Segs=182, Total=209.1MB (ExecutableOnly=true) 2026-07-16 22:35:37.850 +08:00 [INF] AOB scan complete: 2 matches (early stop) 2026-07-16 22:35:37.924 +08:00 [INF] AOB Scan: Pattern=F3 0F 5C ?? F3 0F 5E ?? EB ?? 0F 28 ?? F3 0F, Segs=182, Total=209.1MB (ExecutableOnly=true) 2026-07-16 22:35:37.951 +08:00 [INF] AOB scan complete: 2 matches (early stop) 2026-07-16 22:35:38.024 +08:00 [INF] AOB Scan: Pattern=F3 0F 5C ?? F3 0F 5E ?? EB ?? 0F 28 ?? F3 0F 11, Segs=182, Total=209.1MB (ExecutableOnly=true) 2026-07-16 22:35:38.049 +08:00 [INF] AOB scan complete: 2 matches (early stop) 2026-07-16 22:35:38.049 +08:00 [INF] Mode D Phase 3 END: 18 -> 18 bytes (no change) 2026-07-16 22:35:38.049 +08:00 [INF] Mode D safety check input: 1365 instructions, range=[0x5FAB4..0x60FD0], injection=0x60CC2, backward=4622 bytes, forward=782 bytes, jumps/calls=220 2026-07-16 22:35:38.049 +08:00 [INF] Mode D extended safety check: SAFE (no conflicts found) 2026-07-16 22:35:38.049 +08:00 [INF] Mode D: success, AOB=F3 0F 5C ?? F3 0F 5E ?? EB ?? 0F 28 ?? F3 0F 11 ?? 68 (5 instructions, injOffset=0, requestedMode="A", effectiveMode="A", fallback=null)
2. Why use it
| Instruction-level masking | Re-encodes the instruction with a same-group register, diffs the bytes, and wildcards only what actually moves. Covers REX / VEX (AVX) prefix, ModR/M, SIB. Not string replace. |
| Survives game update | 5 masking modes (A/B/C/E + Mode D shortest-unique). Trade "resilient" against "precise" per case. |
| Self-heal after update | Relocation Engine takes your old-version disassembly, does semantic matching against new-version memory, and returns scored candidates. Jump to each one inside CE by hotkey. |
| Whole-table health check | .CT Stability scans every AOB in a cheat table in one shot and marks each entry Healthy / Warning / Broken. Run it once after a patch instead of enabling scripts one by one. |
| Auto-generated injection logic | Conditional gate, multiplier, clamp, sibling write, pointer capture, multi-level pointer chain — all emitted into the code cave for you. |
| Injection safety check | Scans ±128 bytes for JMP/CALL/Jcc landing inside your patch bytes, and suggests a safer injection point nearby. |
| x64 + x86 both supported | The whole decode / generate pipeline is bitness-aware. Live mode detects 32/64-bit target automatically. |
| Deep CE integration, but optional | Plugin gives Ctrl+Shift+A send, create-record-in-CE, and memory viewer navigation. Works fully offline w/o the plugin. |
| 3 languages, runtime switch | Traditional Chinese / English / Japanese. No restart needed. |
| Light, no dependency | Native AOT single file. No .NET runtime install. Single-instance guard. |
3. Feature summary
3.1 Script generation (main window)
Masking modes
| Mode | Behavior |
|---|---|
Auto | AOBMaker picks A / B / C for you. |
A | Keep opcode + displacement, wildcard register-dependent bytes. Most resilient to update. |
B | Keep destination register, wildcard source. |
C | In-place NOP. No code cave. |
E | Baseline wildcards only (relative branch, RIP-relative, absolute [disp32], address immediates). Tightest match. |
Mode D | Dynamic shortest unique AOB. Scans the live process, extends 1–10 instructions, stops at the shortest pattern that matches exactly once. Stacks on top of A/B/C/E. |
One extra mode that emits a standalone helper script instead of an injection:
- SymbolScan — turns a RIP-relative instruction into a named CE symbol pointing at the data it reads.
Custom display type moved out of the Mode dropdown into CE Tools → Custom type: it emits a registerCustomTypeAutoAssembler script (Scale / Offset / Mask, plus a 16-byte .NET System.Decimal reader) and needs no disassembly at all. .NET Decimal generates in one click. (not fully tested yet)
Other options: Far Jump (14-byte), auto NOP padding, readmem restore (or hardcode db), $process instead of exe name, custom author byline, comment block expandable to full subroutine.
Output buttons: Copy · Copy & Clear · Copy Comments (ORIGINAL CODE block only) ·
Create in CE (creates the AA script record in CE directly, left inactive).
3.2 Auto Script — generated injection logic
All toggles combine. The generated code runs before the relocated original instruction, so it can see and modify the value the original is about to use.
| Feature | What you get |
|---|---|
| Conditional Gate | Whole patch runs only when a struct field passes a compare (== != > >= < <=). Options: editable threshold symbol, on/off flag, signed/unsigned, pushf/popf guard. |
| Multiplier | Scales the delta the instruction is about to apply — damage, EXP, gold. 16 delta rules (R1–R13b), templates P1–P6. Options: neg-delta check, zero skip, zero floor, XMM/flags save, Friend/Foe dual path, Packed Friend/Foe (one vmulps computes both lanes, branch only selects), Directional (lane picked by delta sign). |
| Clamp | Min/max on the injected value. Int via cmp+jcc, float via maxss/minss (SSE fallback on x86). Bound can be a constant, or derived from a sibling field at runtime, e.g. min = 0.5 x maxHP. |
| Sibling Write | Mirror one struct field into another. Classic usage: copy maxHP back into HP. |
| Addr Capture | Capture the runtime pointer into a CE registersymbol. Single mode, or List mode w/ Dedup / Ring / Keyed strategy, capacity, reset flag, capture-once. Indexed operand captures the full effective address. |
| Pointer Chain | Walk a multi-level deref chain at inject time, capture the leaf address. Null guard per hop. |
| Field Clamp | Clamp several sibling fields under one base to a common min/max in one block. |
| Save/restore values | Save/load flags over up to 3 offsets. Typical usage: X/Y/Z teleport. |
| Offset Symbols | Emit base+offset registersymbol for instructions whose displacement got wildcarded. |
3.3 Relocation Engine — re-find the point after game update
- Semantic fingerprint: mnemonic sequence, addressing mode, displacement delta.
- Two-layer scan: wide AOB search, then context disassembly verification.
- Feed up to 3 old-version contexts. Consensus matching kills decoys much better than 1 context.
- Result sorted by total score, w/ sub-scores (mnemonic / addressing / delta / register).
- Press
Ctrl+Shift+Rin CE to jump through the candidates one by one. - 3 opt-in tuning toggles to trade precision against recall when a table keeps failing.
Relocation Engine log output example (technical data)
Spoiler
=== Phase 1: Reference Fingerprint ===
Mnemonic count: 21
InjectionIndex: 10
Mnemonics: [jna, call, test, jne, lea, call, mulss, movss, movss, movaps, addss, comiss, jb, movaps, jmp, xorps, maxss, ucomiss, movss, jp, jne]
=== Phase 2a: Multi-Instruction REX Variants ===
Single-instruction literal bytes: 3, isShort: True
afterCount=1: 4 variant patterns
Pattern: F3 0F 58 ?? 0F 2F
Hits: 2 (after module filter: 2)
Pattern: F3 ?? 0F 58 ?? 0F 2F
Hits: 1 (after module filter: 1)
Pattern: F3 0F 58 ?? ?? 0F 2F
Hits: 4 (after module filter: 4)
Pattern: F3 ?? 0F 58 ?? ?? 0F 2F
Hits: 0 (after module filter: 0)
After afterCount=1 merge: 58 total
After Phase 2a merge: 58 total candidates
=== Phase 2b: Mode A Masked AOB ===
Pattern: ?? 0F 28 ?? F3 0F 58 ?? 0F 2F
Instructions: 3, beforeBytes: 4
After offset + module filter: 0
=== Phase 2b: Mode B Masked AOB ===
Pattern: 41 0F 28 C9 F3 0F 58 CE 0F 2F CA
Instructions: 3, beforeBytes: 4
After offset + module filter: 0
After Phase 2b merge: 58 total candidates
=== Phase 2.5: Multi-Anchor Scan ===
Anchor patterns: 15
Anchor [-4] mulss xmm6,xmm0 (Stable, byteOff=-19, lead=0): 119 hits, pattern=F3 0F 59
Anchor [-4] mulss xmm6,xmm0 (REX, byteOff=-19, lead=0): 18 hits, pattern=F3 ?? 0F 59
Anchor [-3] movss xmm9,[rbx+44] (Stable, byteOff=-15, lead=0): 8 hits, pattern=F3 0F 10 ?? 44
Anchor [-3] movss xmm9,[rbx+44] (REX, byteOff=-15, lead=0): 4 hits, pattern=F3 ?? 0F 10 ?? 44
Anchor [-2] movss xmm2,[rbx+40] (Stable, byteOff=-9, lead=0): 4 hits, pattern=F3 0F 10 ?? 40
Anchor [-1] movaps xmm1,xmm9 (Stable, byteOff=-4, lead=1): 183 hits, pattern=0F 28
Anchor [-1] movaps xmm1,xmm9 (REX, byteOff=-4, lead=0): 183 hits, pattern=?? 0F 28
Anchor [0] addss xmm1,xmm6 (Stable, byteOff=0, lead=0): 57 hits, pattern=F3 0F 58
Anchor [0] addss xmm1,xmm6 (REX, byteOff=0, lead=0): 11 hits, pattern=F3 ?? 0F 58
Anchor [1] comiss xmm1,xmm2 (Stable, byteOff=4, lead=0): 153 hits, pattern=0F 2F
Anchor [1] comiss xmm1,xmm2 (REX, byteOff=4, lead=0): 153 hits, pattern=?? 0F 2F
Anchor [2] jb GAME.exe+10BD5F (ShortBranch, byteOff=7, lead=0): 8 hits, pattern=72 05
Anchor [3] movaps xmm0,xmm2 (Stable, byteOff=9, lead=0): 183 hits, pattern=0F 28
Anchor [3] movaps xmm0,xmm2 (REX, byteOff=9, lead=0): 183 hits, pattern=?? 0F 28
Multi-anchor raw estimates: 3205
After dedup (±16): 212
=== Final Results (20) ===
GAME.exe+11A1C6: total=0.6163 (mnem=0.6667, addr=0.9267, disp=0.0000, reg=0.7167)
GAME.exe+11B7D9: total=0.5188 (mnem=0.2857, addr=0.8643, disp=0.3333, reg=0.7857)
GAME.exe+11A404: total=0.5107 (mnem=0.2381, addr=0.9125, disp=0.3333, reg=0.7500)
GAME.exe+11D8CA: total=0.4949 (mnem=0.0476, addr=0.9333, disp=0.6667, reg=0.6250)
GAME.exe+11B3A5: total=0.4856 (mnem=0.2857, addr=0.9500, disp=0.0000, reg=0.8636)
GAME.exe+11A1B0: total=0.4852 (mnem=0.2381, addr=1.0000, disp=0.0000, reg=0.9000)
GAME.exe+11BF40: total=0.4842 (mnem=0.3333, addr=0.9500, disp=0.0000, reg=0.6591)
GAME.exe+11B1E1: total=0.4798 (mnem=0.0476, addr=1.0000, disp=0.5000, reg=0.6071)
GAME.exe+118F35: total=0.4798 (mnem=0.0952, addr=1.0000, disp=0.3333, reg=0.7500)
GAME.exe+11A1D9: total=0.4788 (mnem=0.3810, addr=0.9214, disp=0.0000, reg=0.5000)
GAME.exe+11AAA0: total=0.4699 (mnem=0.1905, addr=1.0000, disp=0.0000, reg=0.9375)
GAME.exe+11D8B9: total=0.4655 (mnem=0.1429, addr=1.0000, disp=0.2500, reg=0.5833)
GAME.exe+11B45B: total=0.4621 (mnem=0.2381, addr=0.9313, disp=0.0000, reg=0.8750)
GAME.exe+11A46E: total=0.4612 (mnem=0.2381, addr=0.9450, disp=0.0000, reg=0.8250)
GAME.exe+11E7FA: total=0.4606 (mnem=0.1905, addr=1.0000, disp=0.0000, reg=0.8438)
GAME.exe+11BBA2: total=0.4555 (mnem=0.2381, addr=0.8417, disp=0.1429, reg=0.7917)
GAME.exe+1197B5: total=0.4548 (mnem=0.0952, addr=0.9500, disp=0.2000, reg=0.9167)
GAME.exe+11C861: total=0.4490 (mnem=0.1905, addr=0.9571, disp=0.0000, reg=0.8571)
GAME.exe+11C2A3: total=0.4470 (mnem=0.1905, addr=1.0000, disp=0.0000, reg=0.7083)
GAME.exe+11C9DD: total=0.4445 (mnem=0.1429, addr=0.8786, disp=0.3333, reg=0.5714)
3.4 .CT Stability — cheat table health check
- Load a
.CT, extract everyaobscan/aobscanmodule/aobscanregion. - Scan each one against the running game: found or not, unique or not, landing on the right instruction or not.
- Verdict: Healthy / Warning / Broken / Unresolvable, w/ concrete issue list and suggestion.
- Sub-scores: overall context similarity, register consistency, displacement consistency.
- Export report to clipboard. Read-only — it never touches your table.
Note: disable all active scripts in CE before you analyze. An enabled script already replaced the original bytes w/ a jmp, so the analyzer would score the patched bytes and report false Broken.
3.5 Memory Scanner (Alt; CE not required, but CE is the first choice)
This is only used when CE is blocked and you do not have any alternative solution. It's just a AP level scanner.
- Value Scan — Byte / Int16 / Int32 / Int64 / Float / Double / String, w/ the full CE-style scan type set: Exact, Greater, Less, Between, Unknown, Changed, Unchanged, Increased, Decreased, Increased By, Decreased By. First scan / next scan / compare-to-first-scan.
- Sequence Scan — Finds an ordered value chain when you know 4–16 IDs but not the stride. Variable or uniform stride, instant Recompose when you change the parameter (no memory rescan), Auto type detection (read once, scan many types), and order-independent matching when you are not sure of the
layout order. - AC-safe mode — Denuvo-class AC returns zeroed buffers for concurrent
ReadProcessMemory, so a normal scan finds 0 chain even though the data is there.Issue:per-value hits show[N,0,0,0]— only the most common value gets partial hits.Root Cause:AC intercepts concurrent reads from many threads on 16MB chunks.Workaround:tick AC-safe — single-threaded serialized read, 64KB chunk. ~ 17x slower
(13GB in ~ 90s vs ~ 6s) but reliable. Verified against Denuvo on a real title.
- Probe / Scan here — diagnostics. Tells you whether the region is hidden (
VirtualQueryExfails), the read is blocked, or the data is substituted.
3.6 RIP Reference Scanner
Give it an absolute address, it returns every instruction in code whose RIP-relative target equals it. Static version of "Find out what accesses this address" — no breakpoint, no AC trace, result in seconds.
Limit: static references only. It cannot see dynamic / indirect access.
3.7 Cheat table productivity tools
- List Gen — Generates a Lua-driven AA script that walks a base pointer array and builds one collapsible CE record per item, w/ your own field list (offset, type, hex display, bit field, string size/unicode, per-field color, deref chain), alternating row color, and optional ID/quantity filter. Round-trips through
Import. - Auto-Refresh Value — Load a
.CT, tick the records, set a per-record condition, get acreateTimerscript that rewrites the values periodically. Write modes: keep-at-least, cap-at, freeze, only-if-equal, always. Sources: fixed value, read from offset (w/ multiplier), mirror another record, or interval→set ranges. Includes settle gate, world-pointer anchor pause, valid min/max guard, readability guard. Live current-value preview through the plugin. - AA Toggle — One-click master toggle. Enable goes shallowest-first w/ step delay (parent AOB scan must finish before children run); disable turns off every toggleable record deepest-first. Runs purely by record ID, so it survives address changes.
- Cheat List — Distills the whole table into a forum-ready indented checklist. Unlike CE's built-in generator it tells AA scripts from data nodes: every AA script is kept, static data dumps and pointer dissect trees are collapsed. A 10,000-node table becomes ~ 20 lines.
- CT ID Renumber — Renumbers
<ID>sequentially and is reference-aware. Structured references (recordConfigs,mirrorId, literalgetMemoryRecordByID(N), numeric DropDownListLink) can be auto-fixed. Freeform bare-number lists and the table startup script are never silently rewritten — they are reported w/ line number so you double check by hand. Writes in place after a timestamped.bak, and keeps CRLF / declaration / no-BOM so the diff stays minimal.
3.8 Light Unity IL2CPP support
- Parses the game's own
global-metadata.dat+GameAssembly.dll. - Resolves the injection point to its managed method, then anchors the scan w/
aobscanregionbounded to that method instead of scanning the whole module. Faster, and much less chance to collide w/ an identical byte pattern elsewhere. - IL2CPP Browser — browse and filter methods (RVA, namespace, type, method, token, assembly) and instance fields (offset, kind, type, field, token). Generate symbol script, dump, or field-offset define block from your selection.
- Auto-detects metadata / GameAssembly path. For method names CE's tokenizer rejects (e.g. comma inside a generic type), there is an opt-in
{$lua}symbol alias path. - Verified on Unity 2019.4 → 6.1, metadata v24 → v39.
Limit:GameAssembly.dllparsing is PE64 only. A 32-bit IL2CPP game falls back to plainaobscan.
3.9 CE plugin (optional)
Ctrl+Shift+A— right click in CE memory viewer, send the disassembly context to AOBMaker. Injection markers and Module+Offset are already resolved for you.Ctrl+Shift+R— jump to the next relocation candidate.- Reverse IPC — AOBMaker queries CE for disassembly, attached process, active script count, and batch record values.
- Pipe API for external tools — send
module + address, get back a unique AOB; or import a whole CheatTable XML record tree.
4. Install and use
Steps:
- Extract the archive. Run
AOBMaker.exe. No installer. - (Optional) Copy
AOBMaker_CEPlugin.dllinto your Cheat Engine folder, then add it from CE → Settings → Plugins. - Shortest flow w/o plugin:
- In CE memory viewer, select around 10–16 lines around your target instruction. Copy.
- In AOBMaker, click Paste.
- Fill the injection address, or mark it w/
// INJECTING HEREin the text. - Click Generate, then Copy, then paste back into CE.
- Flow w/ plugin: right click in CE → Send to AOBMaker → click Generate. Context is automatic.
Note: reading protected game memory (Mode D / Scanner / .CT Stability) normally needs administrator rights. If Mode D unchecks itself, that is the attach failing — run as admin.
Full documentation: docs/User-Guide.md in the repo.
5. Notes
- Platform: Windows x64. Works on both 32-bit and 64-bit game processes.
- License: open source, see repo.
- This is a tool for single-player game modding and reverse-engineering practice. Please do not use it on online or competitive games.
Notice for UnrealEngine games: in some case: if R15, R14, R13... is used in AA code, even with push/pop protection -- It may cause game crash. In this case, use RAX..RDX, RSI, RDI will be fine. This is based on personal experience, not tool's bug.
Measurement Report for AOB maker, relocation and RIP scanner analysis, technical document:
Spoiler
AOBMaker — Measurement Report, 2026-07-26 to 2026-07-31
Purpose: report what changed in accuracy over these 6 days, with sample size and before/after numbers for every claim.
Scope: this is a measurement report, not a feature announcement. Most of the work was measurement. Where a number did not move, this report says so.
Note: all numbers come from offline replay against real shipped game binaries. Offline replay does not prove live correctness in a running game. See Limit: at the end.
1. Summary table
| Item | Before (07-26) | After (07-31) | Type |
|---|---|---|---|
| RIP Scan divergence rate | 4.50 % (1 in 22) | 0.00 % (1 in 114,198) | Real fix |
| RIP Scan, targets in exact agreement | 197 / 400 (49.25 %) | 398 / 400 (99.50 %) | Real fix |
| Relocation, truth visible in result list | 89.6 % / 92.1 % | 92.1 % / 94.2 % | Real fix |
| AOB generation output | not measured | byte-identical | No change |
| Relocation top-1 accuracy | not measured | unchanged | No change |
| Mode D uniqueness + resolve | not measured | 99.94 % (non-UE) | Now known |
| Relocation top-1, size-matched | not measured | 68 – 93 % (see §7.3) | Now known |
| Relocation top-1, raw pooled | not measured | 51.3 % Unreal Engine / 69.3 % non-Unreal | Now known |
Three of the four headline metrics did not improve. They were never measured before.
Calling them "improved" would be wrong.
2. Sample size
| Measurement | Sample |
|---|---|
| RIP Scan accuracy | 400 target addresses, 913,581 references, 1 shipping binary |
| Relocation, Unreal | 12 build pairs, 7,847 cases (192,728 generated) |
| Relocation, non-Unreal | 13 build pairs, 3 games, 17,550 cases |
| Mode D AOB generation, non-Unreal | 28 binaries, 8 engine families, 1,680 cases |
| Mode D AOB generation, Unreal | 1 binary, 60 cases |
| AOB pattern index upper bound | 166 real hand-written AOBs from published tables |
| Engine miss classification | 4,592 misses (986 Unreal + 3,606 non-Unreal) |
| Codegen regression sweep | 1,627 generated lines, before vs after diff |
| Unit tests | 2,083 -> 2,084, all green |
Binary corpus: 28 non-Unreal binaries / 3,047 MB across 8 engine families, plus 218 Unreal Engine binaries for the pattern index.
3. Real improvement 1 — RIP Scan
Issue: RIP Scan reported some instruction start addresses one byte early.
Root Cause: the backward decode walks from far to near and stops at the first decode that resolves to the target. Two decodes that resolve to the same target always end at the same address, so they can only differ at the start. The earlier start carries an extra prefix byte. Example: a movapd ends with byte 40, and 40 48 8B 05 disp32 is a legal 8-byte encoding, so the scanner picks it.
Fix: derive authoritative instruction boundaries from the exception directory, then pick the accepted start that the boundary oracle confirms. The oracle reports yes / no / undecidable, and undecidable falls back to old behavior instead of dropping the hit.
Result, same 400 targets / 913,581 references:
| Stage | Targets in exact agreement | Divergent references |
|---|---|---|
| Original | 197 / 400 (49.25 %) | 41,101 (4.50 %) |
| + boundary oracle | 394 / 400 (98.50 %) | 151 (0.02 %) |
| + 4 more fixes | 398 / 400 (99.50 %) | 8 (0.00 %) |
Error rate: 1 in 22 references -> 1 in 114,198 references.
The 4 extra fixes came from the non-Unreal corpus:
- Oracle guessed inside
.pdatagaps. Correctness fix, not accuracy. - Oracle decode desync went undetected. Correctness fix, not accuracy.
- Dead prefix elimination, safe once semantic compare exists.
- FS/GS segment-override memory access is not a RIP reference at all.
Note: items 1 and 2 are correctness fixes. They do not show up as an accuracy gain, but they remove two ways to report a confident wrong answer.
4. Real improvement 2 — Relocation result list cap
Issue: the correct address is sometimes absent from the Relocation result list.
Measurement: 99.8 % of top-1 misses happen with a full list. Of the misses where the truth is absent, 100 % have a full list. So "anchors never found it" and "found it, then discarded it at the cut" look identical at the shipping setting.
Root Cause: the result cap is applied after scoring and sorting. It never changes any rank. It only decides what the user can see.
Measurement with the cap raised to 200: 120 of 236 (50.8 %) truth-absent misses were really ranked 21 or lower.
Action: cap raised 20 -> 50.
Why 50 and not 200: rank distribution over 2,000 cases, for the 95 rows past rank 20.
| Rank band | Share of past-cut rows |
|---|---|
| 21 – 50 | 48 % |
| 51 – 100 | 28 % |
| 101 – 200 | 24 % |
Nobody picks the right row out of rank 150 by eye, and a longer list is not free: the CE plugin copy-and-advance flow cycles the whole result set, so 200 makes that flow 10x longer. 50 takes the usable half of the tail at 2.5x.
Result, 2 build pairs:
| Pair | Truth visible, cap 20 | Truth visible, cap 50 | Top-1 |
|---|---|---|---|
| Stellaris 06-10 -> 06-22 | 89.6 % | 92.1 % | 727 -> 727 |
| DW Origins 02-03 -> 02-14 | 92.1 % | 94.2 % | 779 -> 779 |
Top-1 is byte-identical, because the cut happens after ranking. This property is now pinned by a unit test, verified to fail when the cut is moved before the sort.
5. Correctness fixes with no accuracy number
These three do not move any percentage. They remove ways to produce a confident wrong answer, which is the worse failure mode.
| Issue | Symptom before |
|---|---|
| Offline AOB shortener rejected nibble wildcards | Shortener stopped silently, no warning, no error |
| Live scanner on a CE-legal nibble form | Uncaught format exception |
| One relocation tuning toggle compared two different address units | Toggle never fired, and 3 green tests could not see it |
Note: Cheat Engine already supports nibble wildcards such as 4?. The gap was on the tool side, not the CE side.
6. What did NOT change
Result: AOB generation output is byte-identical before and after.
Evidence: full regeneration sweep over the sample set, 1,627 generated lines diffed.
| Output | Result |
|---|---|
| Mask mode A / B / E / F | All byte-identical |
| Anchor patterns (939 lines) | All byte-identical |
| Relaxed / extended variants | 20 lines changed |
The 20 changed lines belong to relocation-internal generators, not to the AOB the user copies out.
Result: Relocation top-1 accuracy is unchanged. The result-cap change does not alter any rank, and the tuning-toggle fix ships default-off.
7. Accuracy that was always there, now measured
These are not improvements. They are the first measurement of behavior that already existed.
7.1 AOB generation (Mode D)
| Corpus | Cases | Unique AND resolves to correct point |
|---|---|---|
| Non-Unreal, 28 binaries, 8 engine families | 1,680 | 1,679 (99.94 %) |
| Unreal, 1 binary | 60 | 60 (100 %) |
Zero cases of "unique but resolves wrong". Zero cases of "reported unique but is not".
The single failure is not a budget problem: the two candidate regions stay byte-identical for 4,288 bytes. Raising any expansion budget cannot separate them.
Result: the 4 expansion budgets are not the limiting factor. Do not tune them.
7.2 Relocation top-1
| Corpus | Cases | Raw top-1 | Twins | Decidable top-1 | Empty result |
|---|---|---|---|---|---|
| Unreal | 7,847 | 51.3 % | 36.1 % | 80.3 % | 0 |
| Non-Unreal | 5,864 | 69.3 % | 5.0 % | 73.0 % | 0 |
Definition: a "twin" is a function whose whole body decodes identically except for relocatable values. No fingerprint method can separate twins, and neither can a person.
Per game, non-Unreal:
| Game | Engine | Cases | Raw top-1 |
|---|---|---|---|
| DYNASTY WARRIORS ORIGINS | Omega Force | 977 | 77.0 % |
| Stellaris | Clausewitz | 4,000 | 68.8 % |
| Crimson Desert | Pearl Abyss | 887 | 63.5 % |
Note: version distance does NOT predict accuracy. One Unreal patch pair scores 42.6% raw, while a major version jump ranges 9.7 % to 92.6 % across modules of the same game.
7.3 The number above is confounded by function size — read this before quoting it
Root Cause: a relocation test case only exists where the function body did not change. So every case set is a survivor set, and the more the build changed, the more the survivors skew toward small, generic functions. Small functions have many byte-identical twins. Twins are unresolvable by any fingerprint method. So raw top-1 measures the surviving population as much as it measures the tool.
Measured median function size per case set:
| Corpus | Median function size | Under 128 bytes |
|---|---|---|
| Unreal, pooled 192,728 cases | 118 bytes | 54.6 % |
| Non-Unreal, per game | 615 – 897 bytes | 6 – 21 % |
The two worst Unreal modules have median function size 48 bytes, 96 % under 128 bytes, 84 – 87 % twins, and 9.7 – 11.3 % raw top-1. Same cause.
Control: filter both corpora to functions of 512 bytes or more, then re-measure.
| Corpus | Pair | Raw top-1, all sizes | Raw top-1, >= 512 B | Twins |
|---|---|---|---|---|
| Unreal | DropIn 4.24 -> 4.24.2 | 42.6 % | 68.2 % | 31.3 % -> 8.0 % |
| Unreal | DropIn 4.24.2 -> 4.27.2 | 51.5 % | 81.5 % | 22.0 % -> 5.5 % |
| Unreal | Satisfactory major jump | 88.0 % | 93.0 % | 6.1 % -> 0.0 % |
| Non-Unreal | Crimson Desert, adjacent | 63.5 % | 65.2 % | 3.9 % -> 1.2 % |
| Non-Unreal | Crimson Desert, 4 months | 35.2 % | 86.3 % | 30.8 % -> 0.3 % |
Result: size-matched, Unreal and non-Unreal are comparable — 68 – 93 % vs 65 – 86 %.
Correction: an earlier draft of this report said "Unreal is the hard case, the user-visible number is 18 points better off Unreal". That is wrong. The gap is dominated by function-size distribution, not by engine family.
Practical reading: a real script targets a real gameplay function, not a 48-byte thunk. For functions of that size, relocation top-1 is roughly 68 – 93 %, on both Unreal and non-Unreal, on both patch-level and major version jumps.
7.3 Pattern index upper bound
166 real hand-written AOBs from published tables, scanned against the matching real binaries: zero violations of the predicted hit-count upper bound.
Limit: the Unreal index upper bound does not hold for non-Unreal targets. Measured counter-example: 26 real hits against a predicted bound of 15. A separate non-Unreal index holds on 681 out-of-index patterns with zero violations.
8. Proposals measured and rejected
Measurement also stops bad changes. Four proposals were dropped on data.
| Proposal | Measured result | Decision |
|---|---|---|
| Nibble wildcards in generated AOBs | 99.70 % reliability vs 99.9 % gate; median hit ratio 1.0000 | Rejected |
| Widen relocation compare unit to whole method | +3.9 pp, but 2 of 8 pairs get worse, 174 fixed / 118 broken | Rejected |
| Twin disambiguation as top priority | Valid for Unreal (36.1 %), but only 5.0 % on non-Unreal | Re-ranked |
| Raise Mode D expansion budgets | Only failure is a 4,288-byte identical twin | Rejected |
Note on the method-scope test: the originally proposed experiment was circular. The corpus defines ground truth as function equivalence, so the similarity it was going to measure is 1.0 by construction. Verified at 965 / 965. The honest version had to run on a different population.
9. Large version jump
Question: does relocation hold up across a big update?
Sample: earliest vs latest build per game. Stellaris 9 months, DW Origins 12 months, Crimson Desert about 4 months.
| Game | Adjacent builds | Large jump |
|---|---|---|
| Stellaris | 72.7 % | 76.6 % |
| DYNASTY WARRIORS ORIGINS | 77.1 % | 81.3 % |
| Crimson Desert | 72.9 % | 82.0 % |
Warning: this is survivorship bias, not capability. Every relocation test case requires an unchanged function body, because instruction-level ground truth needs the correspondence. So both columns only measure functions that survived the update untouched. Case yield shows it directly: Stellaris drops from 33,517 cases over 2 weeks to 9,258 over 9 months.
Correct reading: if your script is still valid and only the address moved, relocation still finds it after a big update.
Limit: functions that were rewritten contribute zero cases here, by construction. For that regime the function-level baseline is 70.6 %.
9.1 One apparent regression, traced to the same size artifact
Crimson Desert, large jump, twin-inclusive sampling, looked bad:
| Metric | Adjacent | Large jump |
|---|---|---|
| Raw top-1 | 63.5 % | 35.2 % |
| Twins | 3.9 % | 30.8 % |
| Truth present in list | 83.4 % | 48.8 % |
Root Cause: median surviving function size collapses from 916 bytes to 93 bytes as the gap widens. Small functions carry many twins.
Distance sweep across 5 Crimson Desert build pairs shows top-1 tracks surviving function size, not calendar distance:
| Pair | Median function size | Raw top-1 | Twins |
|---|---|---|---|
| 03-22 -> 06-04 | 96 B | 36.0 % | 31.5 % |
| 06-04 -> 06-18 | 201 B | 32.0 % | 20.2 % |
| 06-18 -> 07-03 | 215 B | 38.0 % | 15.5 % |
| 07-03 -> 07-15 | 451 B | 43.5 % | 13.8 % |
| 07-03 -> 07-07 | 897 B | 63.5 % | 3.9 % |
Control, same size band on both sides:
| Crimson Desert, >= 512 B | Raw top-1 | Twins | Truth in list |
|---|---|---|---|
| Adjacent, 4 days | 65.2 % | 1.2 % | 88.5 % |
| Large jump, 4 months | 86.3 % | 0.3 % | 99.0 % |
Result: there is no regression. On realistically sized functions the large jump scores higher, not lower. The apparent collapse was entirely a population artifact.
Also checked and rejected: the scan surface. Crimson Desert ships 2 executable sections per build with randomised names, one about 70 MB holding 89 % of all function starts, one about 250 - 290 MB holding 11 %. Wrong answers land in the dense section 88 - 100 % of the time, matching the function distribution. Restricting the scan would buy speed, not accuracy.
10. Miss classification
4,592 top-1 misses classified. Non-Unreal breakdown, 3,606 misses:
| Class | Share |
|---|---|
| Ranking problem (truth in list, rank 2+) | 78 % |
| Anchor problem (truth not found at all) | 22 % |
| Wrong answer is a near-twin of the right one | 42.3 % |
| Top-1 lands inside the correct function, wrong instruction | 5.4 % |
| Wrong answer is more than 64 KB away | 63.3 % |
Definition: near-twin = structurally identical once immediates and displacements are dropped. The strict twin test requires byte-exact bodies, so it does not label these as twins. Measured per game: DW Origins 55.7 %, Stellaris 42.5 %, Crimson Desert 22.6 %.
Example: in one game, a single wrong address won 31 of 71 misses. The anchors were print_string, print_decimal, print_boolean, assert, spaced exactly 160 bytes apart, every score 1.0000. That is a script-registration stub table.
11. Limit
- All numbers are offline replay. They do not prove live correctness in a running game with anti-cheat present.
- Relocation cases only cover functions whose body did not change. Rewritten functions have no offline ground truth.
- Large-jump numbers cover 3 games. Adjacent-build numbers cover 3 games, 10 pairs.
- Unreal relocation numbers come from 2 games. Twin density varies 1.0 % to 87.1 % by module, so do not quote a single pooled figure.
- The result-cap change is verified offline only. Whether 50 rows reads well in the window, and whether copy-and-advance still feels right at that length, needs a real session with a real game. Not yet done.
12. Bottom line
- 1 real accuracy fix, large: RIP Scan, 1 in 22 -> 1 in 114,198, on 913,581 references.
- 1 real usability fix, small: relocation result cap, +2.1 to +2.5 pp on answer visibility, top-1 provably unchanged.
- 3 correctness fixes that remove silent wrong answers, with no percentage attached.
- 0 change to generated AOB bytes and to relocation top-1.
- 4 proposals rejected on data before shipping.
- The rest is knowing where the tool actually stands. On functions of the size a real script targets (512 bytes or more), relocation top-1 is 68 - 93 %, on both Unreal and non-Unreal, on both patch-level and major version jumps.
Note on raw pooled figures: 51.3 % on Unreal and 69.3 % elsewhere are not comparable to each other, and neither describes real usage. Both are dominated by tiny surviving functions. Quote the size-matched band instead, or state the median function size next to any raw figure.
