Released · Windows · Open source (MIT)

Reverse Engineering · Binary Patching

Wizardry Gold High-Roll Patch

A legacy x86 Windows game binary, read instruction by instruction. Found a developer-only code path the shipped game keeps switched off, turned it back on with a six-byte patch, and fixed a timing bug that has been in the original release since the 1990s.

Before and after

The problem

In Wizardry 7: Crusaders of the Dark Savant (the “Wizardry Gold” Windows re-release), character creation rolls a pool of bonus points. A Faery Ninja needs a roll around 21 to 23, against a normal range of roughly 5 to 11. Players re-roll for hours to get one.

The obvious approach, editing the value in memory, does nothing. Profession eligibility is computed from the roll at the instant you pick a gender and never re-read afterward. Pinning the value, and even injecting it with a hardware breakpoint, failed for the same reason (the breakpoint version crashed the game). The only fix that works is changing what the generator produces.

What I found

  • A dormant developer roll. Disassembling the roll generator turned up a branch that, when a flag word equals 1, rolls random(10)+21 (21 to 30) instead of the normal range. Elsewhere, the shipped game hard-codes that flag to 0, so the branch never runs.
  • A six-byte patch. Replacing the conditional jump that guards the branch with NOPs makes the elite roll fire every time. The game still rolls with its own code, so everything downstream (eligibility, stats, saves) behaves normally.
  • A timing bug in the original release. About 30 throttles stamp a shared timestamp in 55 ms units (the DOS 18.2 Hz tick), but the main step timer reads it in 128 ms units. One timestamp, two time bases: ambient effects free-run and combat pacing is erratic, even on an untouched install. The fix rewrites both timer stamps to the 55 ms idiom and fits exactly in the original instruction bytes, with no code cave and no changed branch targets.
  • Which exe actually runs. Unpacking the UPX-packed launcher showed that it picks a different executable per resolution setting. That explained why patches seemed to “disappear”: they were landing on an exe the launcher wasn’t running. Both copies now get patched.

Built to be safe on someone else’s machine

  • Signature-located: each patch site is found by byte signature and verified before any write. Unknown builds are refused rather than risking corruption.
  • Reversible: a backup is made before the first patch, and every edit has a revert. Patching then reverting gives back the original file byte for byte, and automated tests check this.
  • Offline: no telemetry, no network calls. It edits a local copy of a game you own, and redistributes no game files.

Tech stack

  • Python CLI patchersStandard library only. Roll range, text dwell, screen-wipe delay and the timer fix, each with status and revert.
  • Rust desktop GUIeframe, a single ~3.5 MB standalone exe with one-click apply and revert. No Python needed.
  • Tauri v2 appRust backend with a vanilla HTML/JS front end. Same patch logic, covered by cargo round-trip tests.
  • Research toolingCapstone-based cross-reference finder and disassemblers, an exe differ, and a read-only call-stack sampler (suspend, read EIP and stack, resume) used to locate the timing code.

Status

All four patches (high rolls, text pacing, screen-wipe delay, timer fix) are released and verified in-game. Source, the full write-up, and a plain-English version are on GitHub under the MIT license.

Links

Disclaimer

For personal use with a single-player, offline game you own. Not affiliated with the rights holders of Wizardry. Use at your own risk.