For education purposes only · no game files included · bring your own THUG2
Chapter 4
thug2 · 6860d54 · "Options Screen, Graphics, Keyboard, Gamepad"

Settings, Bro

The THUG1 port came with four menus nobody in 2003 had: Screen, Graphics, Keyboard and Gamepad. The C++ behind them moved to THUG2 with everything else. The menus didn't: they were written as patches to THUG1's scripts, and THUG2 has its own. So they had to be re-hosted in THUG2's menus, without editing a single one of THUG2's files.

THUG2's GAME OPTIONS menu: Save Game, Load Game, Screen, Graphics, Keyboard, Gamepad, Game Settings, Game Progress, High Scores, Cheat Codes, Game Movies.
Game Options, from the wheel. Four of these weren't here in 2004. They fit in like they were.

1Patch, don't edit

THUG2's scripts come compiled, packed in qb_scripts.prx. Editing them means unpacking, decompiling, changing, recompiling and repacking, for every change, forever. The port's way is a patch: a small script file with the same path as a retail one, parsed right on top of it when that one loads. Same name, newer definition, wins. The patch for the wheel's Game Options rewrites one script, create_setup_options_menu, and adds four items after Load Game:

excerptScripts/patches/levels/mainmenu/mainmenu_options.q · 6860d54

The same four show up in the pause menu's Options, after Save Game, through a second patch of scripts/game/menu/gamemenu_options.qb. That one also holds the four menus themselves, rebuilt with THUG2's menu pieces (round bars, the signs on the right) so they look like they shipped with the game.

2Compile with qb, every build

The patches are text, the game wants bytecode. In between sits qb, the QScript toolchain written for the THUG1 port, which already spoke THUG2's dialect. The build compiles every patch on the way into Game/Data, and finds qb however it's installed, building it from source as a last resort:

diffstage_data.sh · 6860d54

A QScript gotcha worth framing: x = ( some_global ) with a checksum global doesn't evaluate the global, it gives you its name. ( some_array [ index ] ) does evaluate. Of course it does.

3The button that erased itself

First run with a fresh config.ini: the Gamepad menu showed Ollie with no button at all. Every default had been set, then one got wiped. The culprit was the duplicate check. When a button is bound, any other action using it loses it, so two actions can't share a button. On a gamepad, Ollie is A, and so is the menu's Accept. Two actions, one button, one survivor. Except they never meet: Ollie works while skating, Accept works in menus. The check didn't care where an action works. Now it does:

diffWn32/Code/Plat/Sys/SIO/p_bindings.cpp · 6860d54

The conflict popup already knew about contexts. The defaults didn't. Both agree now.

Where it landed

Screen, Graphics, Keyboard and Gamepad, in both Game Options and the pause menu, built from THUG2's own menu parts. Zero retail files touched. One button un-erased.