A free, open-source Windows game trainer — an offline memory editor and cheat engine for PC games, for when you want a Cheat Engine/WeMod alternative that's transparent about what it's doing, doesn't phone home, and doesn't require a closed-source client to use cheats someone else built. Electron + React + TypeScript over a C++ N-API addon that talks to a target process's memory directly — no server, no telemetry, no always-online requirement.
Two ways to cheat:
- Value cheats — find an address, write it repeatedly (a "freeze"), or
write it once ("one-shot"). A cheat can also be anchored: a small capture
patch records the game object it belongs to, and the cheat writes a field of
that object (optionally following one pointer field first,
derefOffset). That keeps cheats working across restarts in games with no readable metadata, such as Unity IL2CPP and Unreal. - Code patches — rewrite the instruction that writes a value, so the game itself never puts the old value back. NOP it out, replace it, force a fixed result, or skip a method entirely for one object.
Ships with two ready-made cheat sets: Valheim (Mono JIT — 15 cheats,
games/valheim.json) and Elden Ring (native, pointer-chain value
cheats — 14 cheats, games/start_protected_game.json, named for Elden
Ring's EAC-protected executable). The engine underneath isn't tied to
either game — see Sharing cheats below for adding your
own. games/ also holds profiles for Palworld (Unreal) and Aviassembly
(Unity/Mono), and a work-in-progress one for Schedule I (Unity IL2CPP):
its cheats are still being verified in-game, so treat that one as a starting
point, not a finished set.
Windows only. The native addon's injection path is Win32; Linux is stubbed out but not implemented (see
native/src/platform/platform_linux.cc).
Grab the latest Apprentice-Setup-<version>.exe from Releases
and run it — no admin rights needed, it installs to your user profile.
Prefer to build it yourself, or want to hack on it? Keep reading.
Requirements: Node.js 26 to run the tests (they load a TypeScript worker and the compiled
.node addon natively), Node.js 22 to build the addon (the pinned node-gyp can't yet
drive the toolset Node 24+ headers use; the addon is N-API, so a build made under 22 loads
fine under 26), a recent npm, and the
Visual Studio Build Tools
(C++ workload) for compiling the native addon.
npm install # electron postinstall may need approving
cd native && npx node-gyp configure && npx node-gyp build && cd ..
npm run buildRun it with Apprentice.cmd at the repo root, or package a real installer:
npm run distproduces release\Apprentice-Setup-<version>.exe (NSIS, no admin required).
Stop Apprentice before rebuilding the addon — a running instance locks
memory_addon.node and the link fails with permission denied.
npx vitest run # unit + native-harness tests
npx tsc --noEmit # type check
npm run build # production bundle actually compiles.github/workflows/ci.yml runs on every push and pull request: it builds the native
addon, builds the MCP server, type-checks, compiles the production bundle, and runs the
whole test suite (native-harness tests included) on a Windows runner.
To publish a release, bump version in package.json and push it to master. If
there is no release for that version yet, CI builds the installer, creates the tag
v<version> and a GitHub Release (a prerelease when the version has a -, like
0.1.2-beta), attaches Apprentice-Setup-<version>.exe plus a SHA256SUMS.txt, and lets
GitHub generate the notes. Those list the merged pull requests since the previous release
and link the full comparison, so work pushed straight to master shows up only as that
comparison link. A push that doesn't change the version publishes nothing.
To check that a build works without publishing, run Actions → CI → Run workflow on
any branch other than master: it builds the installer and attaches it to the run as an
artifact (kept for 3 days) instead of releasing it.
The installer is not code-signed, so Windows SmartScreen will warn on first run. The toolchain pins (Windows 2022 image, Python 3.11, Node 22 to build the addon and Node 26 to run the tests) each exist for a specific reason, recorded as comments in the workflow.
tests/native/*.test.ts don't touch a real game — they drive
test-harness/harness.exe, a small standalone Windows binary
(test-harness/harness.c) built for exactly this: a real process with known
values at known addresses to scan for, freeze, patch, and watch. Each test
file spawn()s it directly (spawn(path.resolve('test-harness/harness.exe')))
and talks to it over stdin/stdout — npx vitest run handles all of this for
you, there's no separate step to start it yourself.
The protocol is one command per line in, one OK <result> (or an error) per
line out. A few of the commands, for a sense of what's being exercised:
| Command | What it does |
|---|---|
drainloop |
Spin-writes a counter down — patch_ops.test.ts NOPs the write and checks it stops draining. |
forceloop / shieldloop |
Keep re-asserting a value — proves a force/guard-mode injection actually pins it. |
tight_write |
A real movss [reg], xmm store, back-to-back with no slack — the tightest case write_watch has to decode correctly. |
bigalloc / bigcode (+free variants) |
Allocate an 8 MiB data/executable region, replying OK <0xbase> — used to prove chunked region reads never drop a value straddling a 4 MiB chunk boundary. |
loaddll / loaddll2 / unloaddll |
Load/unload a real DLL (probe.dll/probe2.dll) into the harness process — what module_info.test.ts uses to prove module-anchored patches survive a DLL reloading at a different base. |
Full command set and exact reply formats live in harness.c itself — it's
short and worth skimming before adding a test that needs a new one.
Two hazards if you're adding tests around it (see CODEBASE_MAP.md for
the full explanation): cave_ops.test.ts and module_info.test.ts must each
keep exactly one top-level beforeAll — a second one spawning its own
AsyncWorker reliably segfaults the vitest worker — and scans in the native
tests are one-shot, keyed off a field's initial value, so the first test to
touch a given field "wins" it.
If you change harness.c itself, rebuild it from PowerShell, not Bash
(Bash won't run vcvars and fails silently):
& cmd.exe /c 'call "...\vcvars64.bat" >nul 2>&1 && cl.exe /nologo /Fe:test-harness\harness.exe test-harness\harness.c'then delete harness.obj and confirm the timestamp on harness.exe changed.
PRs welcome — bug fixes, new cheats for existing games, or support for a new game's Mono/IL2CPP layout.
- Fork, branch off
master. - Read
CODEBASE_MAP.mdfirst. It's written for someone picking this up cold — the layer map, the four patch modes, the non-negotiable safety rules (never displace a RIP-relative instruction, never guess on an ambiguous signature, always restore on quit), and why each of those rules exists, are all there. Read it before touchingnative/srcorpatchEngine.tsespecially — several of those rules were learned by crashing a live game. - Make the change. Match the surrounding code's comment density and idiom — this codebase explains why, not just what, and PRs are expected to keep that up.
- Run the full test suite (
npx vitest run,npx tsc --noEmit,npm run build) before opening the PR. The native test harness is real but limited — it's a static MSVC binary, not a Mono JIT target, so passing there is necessary but not sufficient for anything touching signatures, injection, or code-cave layout. Say in the PR if you validated a change in-game and against which game/build. - Open the PR against
masterwith a clear "what and why." Small, focused PRs review faster than one that bundles an unrelated refactor with a feature.
Found a bug but not fixing it yourself? Open an issue — include the game, the cheat (if applicable), and what you expected vs. what happened.
mcp-server/ is a standalone, read-only MCP
server that exposes the same native memory-introspection primitives
Apprentice itself is built on — attach, scan, Mono class/field/method
resolution, read, disassemble, write-watch — as tools an AI coding agent
(Claude Code, etc.) can call directly against a live game process. It never
writes to the game's memory by design: it's for finding the
address/offset/signature a new cheat needs, not for installing one. (Its one
tool that writes anything, author_cheats, writes a draft profile file on
disk, and touches game memory only through a scratch buffer; see below.) See
docs/superpowers/specs/2026-08-22-mcp-memory-server-design.md for the full
design rationale.
This is what makes "find this field, resolve that class, watch this write" a conversation instead of a manual Cheat Engine session — useful both for building out a new game's cheat set and for debugging why an existing patch stopped locating.
Setup:
cd mcp-server && npm install # also builds dist/ via the prepare hookThe native addon must already be built (native/build/Release/memory_addon.node
— see Building from source above); this package
require()s it directly rather than shipping its own copy. .mcp.json at
the repo root registers the server (game-memory) pointing at
mcp-server/dist/index.js — an agent working in this repo picks it up
automatically. Full tool list, dependency-pinning notes (there's a real
reason @modelcontextprotocol/sdk is pinned exactly, not ranged), and more
detail live in mcp-server/README.md.
For a Unity Mono or Unity IL2CPP game, author_cheats turns a wishlist of
categories (health, stamina, mana, money, bank, cash, water, curfew, time
freeze, godmode…) into draft cheats and one in-game checklist, instead of a
reverse-engineering session per cheat:
- Mono: enumerates classes and fields, ranks them by name, finds the live singleton root, and keeps the first candidate whose live read is plausible.
- IL2CPP: reads the runtime's class, field and method structs directly
(no per-item remote call), ranks fields by name and type, verifies through
a live singleton or a filtered instance scan, and emits the same pair Tamper
already uses: a
capturepatch on a method prologue (found by a unique signature) plus ananchorvalue cheat. - Output goes to
games/<exe>.draft.json, beside the profile and never over it (a profile is looked up by exact exe name, so a draft is not loaded by accident). Every drafted cheat carries averifiedflag and amultiInstanceRiskflag; confirm each one in-game before promoting it. - Categories that are the wrong shape for a field cheat (continuously decaying stats, rate multipliers, NPC-decided behaviour, item-held currency) come back as manual, with the reason, instead of a draft that looks right and isn't.
When a cheat does nothing, or a mechanism isn't obvious, mcp-server/scripts/
has read-only live-analysis scripts (survey classes, disassemble a method, list
callers and inlined field readers, watch a field change while the game does
something, generate a replace/capture patch with a unique signature). The
method they support is written up in
.claude/skills/authoring-tamper-cheats/SKILL.md and
mcp-server/scripts/README.md. Design docs:
docs/superpowers/specs/2026-09-19-cheat-factory-design.md and
docs/superpowers/specs/2026-09-19-il2cpp-cheat-factory-design.md.
Every cheat lives in a per-game profile at games/<exe-name>.json — plain
JSON, easy to read, easy to hand to someone else.
Apprentice can import a Cheat Engine .CT table directly (Cheats screen →
Import Cheat Table (.CT)) — it recognizes the common "replace one write
with a fixed value" shape most CT entries use, and skips (with a reason)
anything it can't safely translate. Going the other way, Export to Cheat
Table (.CT) turns your cheats into a .CT file anyone with Cheat Engine
can open — not just a select few: nop, replace, and force-mode patches
all export as Auto Assembler scripts, and any value cheat resolved through a
plain module+offset(+pointer chain) address exports as an ordinary Cheat
Engine address entry, no script needed. What's left out is only what
genuinely has no Cheat Engine equivalent — capture/guard/immune/
scale/copy-mode patches (relocated code-cave injections, some with an
object pointer resolved fresh every install from live Mono metadata Cheat
Engine has no way to replicate), a value cheat resolved via Mono metadata or
a capture patch's tracked pointer rather than a fixed address, and a
single-bit cheat (freezing the whole byte in Cheat Engine would clobber
other flags packed into it) — each reported with its specific reason rather
than silently dropped.
This is the fastest way to hand a friend a single cheat or a small set
without either of you touching a games/*.json file by hand.
If you've built out a solid set of cheats for a game — especially a new
game this repo doesn't support yet — consider opening a PR to add or extend
its games/<exe>.json:
- Get your cheats working and verified in-game first. A patch that only
"looks right" in the JSON but was never actually tested against the game
is worse than no PR — see
docs/superpowers/follow-ups/2026-07-28-valheim-session.mdfor exactly how many ways a code patch can look fine and still be wrong. A*.draft.jsonproduced byauthor_cheatsis a starting point, not a verified profile: toggle each entry in-game and check what it does before promoting it. - Keep the file schema-2 shaped (
{ schema, exe, modules, cheats }) — every cheat you add should be something the app itself saved, not hand-typed from scratch, so it's already validated against the app's own types. - Name cheats the way the existing ones are named: short, in-game terminology ("Infinite Weapon Durability," not "InfDur" or "cheat_12").
- If a cheat is a code patch anchored to a module (not Mono-resolved), note in the PR description which game version/build it was captured against — module-anchored patches verify a fingerprint before trusting their saved address, but that only helps if someone knows what build to expect it against in the first place.
- Mention any cheat that's build-specific or known to break on other difficulty/mode settings, so the next person doesn't have to rediscover that the hard way.
A game update can shift a patch's exact bytes even when nothing about the cheat changed — that's expected, not a sign something's broken. Apprentice re-verifies and re-locates on every attach, and Mono-anchored cheats (resolved by class/method name rather than a byte signature) mostly ride through updates without needing any of this.
Apprentice never leaves a game modified after it closes: patches are restored on cheat disable, on detach, and on app quit, and a hardware write-watch breakpoint is always cleared before Apprentice exits — this is also true if the process closes unexpectedly, e.g. via Task Manager. If you see something patched that shouldn't be, that's a bug — please report it with repro steps.
This tool touches only the process you explicitly attach it to, and does nothing without you turning a cheat on. It has no network calls of its own beyond an optional Cheat Table search/import feature you invoke by hand.
GPL-3.0. Free to use, study, modify, and redistribute — including commercially — as long as anything you distribute that's built on this code stays open source under the same license.