For education purposes only · no game files included · bring your own THUG2
Chapter 5
thug2 · 0694250 · "Main menu scripts, Character animation fixes, balance fixes, bail fixes"

Faceplant

The skater stood up, rolled away and did tricks. For about four seconds the port was a game. Then he bailed and the game crashed. Then he crouched like a mannequin, grabbed when you said grind, sank knee-deep into the floor during a manual and crashed again on a manual gap. This is the chapter where everything that looked the same between THUG1 and THUG2 turned out not to be, one faceplant at a time.

A nose manual in Training: the skater upright on the board, THUG2's vertical balance arc left of him with the arrow on it.
The end of this chapter: a nose manual, standing on the board instead of in it, with THUG2's vertical meter on the left.

1Skip the menu, go straight to the level

Testing in a level meant clicking through the wheel every time. The port learned a quick start: THUG_LEVEL=TR in the environment, and a patched launch_main_menu asks the engine whether to jump straight into Training, in High Score / Free Skate, with the current skater:

diffScripts/patches/levels/mainmenu/mainmenu_scripts.q · 0694250

2The crash that crashed while reporting the crash

Bail, crash, every time. The stack ended in TextureSplat, the blood splat a bail paints on the ground. THUG1's scripts give it a texture name as a string. THUG2's give it a checksum: name = blood_01. So the engine decided there was no name and asserted. And the assert's own message had a %s with nothing passed to print, so formatting the error read garbage and took the game down. The error message was the bug.

diffCode/Gel/Components/skeletoncomponent.cpp · 0694250

A splat whose texture isn't loaded is now skipped with a line in the log, instead of an assert. Bails are painful enough.

3The mannequin crouch

Hold Ollie and a THUG2 skater crouches, loaded like a spring. Here he stood straight up, like a mannequin someone had put on a board. Steering played a pose from a store window too. THUG2's animation scripts pick animations by skating style (ProfileEquals style = crappy, Cool or fat), and THUG1's SkaterEquals had never heard of a style. Every check came back false, so every pick fell through to the default. Teaching it the profile's skatestyle brought the crouch back:

diffCode/Sk/Objects/skater.cpp · 0694250

4Grind button grabs, grab button grinds

On the keyboard, the grind key grabbed and the grab key ground. The port has two keyboard layouts: in menus, keys mean "choose" and "back"; while skating, they mean tricks. And in menus, grind and grab swap pad buttons. So the port thought the skater was in a menu. Why? It asks the game mode: are we in the front end? THUG1's front end game mode says is_frontend = 1. THUG2's game modes don't have that line at all, and THUG2.exe never asks. THUG1's code read the missing value into an uninitialised int and got whatever was on the stack. That day it was "yes".

diffCode/Sk/Modules/Skate/GameMode.cpp · 0694250

The same garbage also made "change camera view" do nothing in every level: it checks for the front end first. One uninitialised integer, two bugs, zero warnings.

5Triggers speak a new language

The Training level's manual gap crashed the game the moment the skater hit it. The trigger script asks GetTriggerArray through, then TriggerType <trigger_array>. Remember chapter 1, where THUG2's script replaced THUG1's C function GetTriggerArray? THUG1's version returned trigger names. THUG2's script returns numbers: grind onto becomes [13, 3, -1]. THUG1's TriggerType wanted names and asserted on numbers.

So what are the numbers? THUG2.exe's TriggerType turns each into a bit and checks that the event has all of them. Its TripTrigger builds the event from a three-number table, and those tables sit next to each other in the executable's read-only data. Dumping them gives the whole vocabulary: skate=1, jump=2, onto=3, off=4, offedge=5, wallplant=6, lip=7, land=8, bonk=9, climb=10, graffiti=11, spin=12, grind=13, walk=14.

excerptTHUG2.exe's event tables, decoded
diffCode/Gel/Components/TriggerComponent.cpp · 0694250

The skater now trips triggers the THUG2 way too: rails as grinds, walking from surface to surface as walking, wallplants as wallplants. Everything except the climbing and spin moves THUG1 doesn't have.

6The manual meter that thought it was a rail

In THUG2, the manual meter is a vertical arc left of the skater. Here the arrow slid sideways, like on a rail, while the arc stood upright. The meter code turns the meter by -90° and, knowing that children don't turn with their parents, pre-rotated the arrow's position by hand. Except they do now, since chapter 3. The arrow got turned twice. The fix was deleting code: place the arrow as on a horizontal meter and let the parent do the turning.

diffCode/Sk/Modules/Skate/score.cpp · 0694250

The first attempt copied THUG2.exe's numbers exactly, a -80/+50 offset. It put the arrow under the arc, because THUG2 turns children about a different point than the port does. Matching the result beat matching the numbers.

7Knee-deep in the floor

Hold a manual, or balance on a rail, and the skater sank into the ground, half his body gone. Only while balancing. Balance animations play through a different path, a "wobble" controller that scrubs the animation back and forth with your lean. Remember flag 0x20000 from chapter 1, translations relative to the neutral pose? The normal animation path hands the decoder that neutral pose. The wobble path never did, so every bone's offset started from zero and the body collapsed onto its hips.

diffCode/Gfx/subanimcontroller.cpp · 0694250

Three lines. The cutscene player had the same hole and got the same three lines.

8Back to the main menu, and boom

Quit a level to the main menu and the game asserted: "item already in hash table", while loading the multiplayer icons. They were never unloaded. THUG2's scripts unload them in level_cleanup_assets, which nobody calls. Searching THUG2.exe for that script's checksum found the one place that runs it: the level cleanup, right before the skate module cleans up, after exit_pause_menu. Same place now:

diffCode/Sk/Scripting/cfuncs.cpp · 0694250

Where it landed

The skater skates, crouches, steers, grinds on the grind key, manuals on top of the floor, bails without taking the game with him, and comes back to the main menu in one piece. Most of the fixes were one line. Finding each line was the job.