Lost Connection to GameSpy
GameSpy shut down in 2014, and out of the box THUG2's online mode has nothing to talk to. Its favourite line says it all: You have lost connection to GameSpy. Check your network cables and reconnect. The cables were fine. GameSpy was the one that left. The THUG1 port already had its own online service, with lobbies, homies and stats, and a client inside the engine that talks to it. THUG2 runs on that engine. So how hard could it be? (Every chapter of this story starts with that question. Every chapter answers it.)
"I have read the Terms of Use for THUG 2 Online Play at www.thug2online.com and I agree to abide by these Terms of Use."THUG2's own scripts, still asking, twenty years later
1A service for the sequel
The THUG1 port's service, thugonline, is one Go program behind nginx with PostgreSQL behind it. It does
what GameSpy's half dozen servers used to: message of the day, profiles, lobbies with chat, the game list, homies, NAT
traversal, ranked games and top lists. THUG2 speaks the same protocol (it's the same client code), so the plan was: copy
it, rename everything, point THUG2 at the copy. Two services, one server, nothing allowed to collide.
| THUG1 | THUG2 | |
|---|---|---|
| HTTPS | online.epicmaxim.net | online2.epicmaxim.net |
| NAT (UDP) | nat.epicmaxim.net:27901 | nat2.epicmaxim.net:27902 |
| Database | thug_online | thug2_online |
| Profiles start at | 100000 | 200000 |
The only thing that really had to change was the game's data. THUG1 ranks 12 levels, from New Jersey to School II. THUG2's Online Stats screen lists its own, and it says so in plain QScript:
Fifteen levels. The service keeps them as checksums of those load scripts, the game sends the same checksum when it reports a match, and the rating scale grows from 2900 to 500 + 15 × 200 = 3500. Same numbers on both ends:
Spot the survivor: 0x991b5359 is in both lists. It's load_SE. THUG1 called
that level Hotter than Hell, THUG2 calls it Pro Skater. Same name in the scripts, different level, same checksum. The
service doesn't mind: each game has its own database.
2Ready, then nothing
First run: Multiplayer, Online Play, pick a skater, THUG 2 Online, Ready. The menu disappeared, the skate park stayed, and nothing else ever happened. The log had one new line:
THUG1 goes straight from Ready to the message of the day. THUG2 asks first whether GameSpy's services are up: GameSpy
had an "availability check" by then, a little server that could say "we're down for maintenance" (or "we're down
forever", which nobody wrote a dialog for). The missing function returns false, the IF skips everything,
and the script ends quietly:
The service has no maintenance mode to report. If it's down, the message of the day fails, and THUG2 already has a dialog for that. So the answer is yes, always:

return true later. "Game Experience May Change During Online Play." It sure did.3Ask the oracle for the rest
One missing function is a bug. Several are a pattern. A small script listed every word THUG2's net scripts use as a command and crossed out the ones the port knows: a C function, a script, a global. A few hundred survivors, nearly all of them parameter names and text keys. Eight were real commands, all new in THUG2:
| Command | Where THUG2 calls it | Here |
|---|---|---|
ServicesAreAvailable | Ready | yes |
CancelMotdDownload | Cancel on "checking for the message of the day" | fails the download, as a timeout would |
CancelLobbyConnect | Back on "Connecting to GameSpy..." | drops the answer on its way |
ResetConnectionTimer | OK on the lost connection dialog (PS2 only) | nothing to reset |
IsAlreadyMyBuddy | a message from someone | the homie list |
SendBuddyMessage | Send message, Reply | new endpoint |
AnyBuddyMessagesLeft | the pager, the pause menu | the inbox |
ShowNextBuddyMessage | the pager | the inbox |
The scripts say when each one is called, not what it hands back. For that, THUG2.exe. Its function table maps
each name to an address, and ShowNextBuddyMessage turns out to be short and chatty: it puts four checksums
into the calling script's parameters, one after the other.
So the port's version does exactly that, oldest message first:
CancelMotdDownload in THUG2.exe sets one byte. The message-of-the-day loop checks that
byte next to a cmpl $0x3a98: 15000 milliseconds. Fifteen seconds was how long 2004 waited for a text file.
4Beep beep: the pager
THUG1 had homies. THUG2 gave them a Motorola pager (product placement was a whole genre in 2004). The service had nowhere to send a message, so it got one more endpoint and one more event:
On the game's side the event lands in a small inbox (twenty messages, oldest out first) and runs THUG2's own script
flash_buddy_message_icon. The icon shows up next to the GameSpy logo, and View Messages opens the pager:
The first message didn't arrive. The game said homie message not sent: 405, and for a minute
that looked like a deep protocol mystery. It was the local test server, still running the build from before the endpoint
existed. Method Not Allowed, because the method didn't exist yet. Restarting it also tested the next screen for free:
"You have lost connection to GameSpy", on cue, on both instances.
5Host a game, crash a game
Host Game, Boston, Ready. The level started loading and the game asserted:
Network games load a lighter version of each level, BO_net. The file is right there in
Data/levels/BO/, named BO_net.scn.xbx. The port's file lookup indexes everything under
Data, lowercase, then strips .xbx from the name it's asked for, because the THUG1 port's loose
files don't have it. THUG2's do. Asked for bo_net.scn, the index only knew bo_net.scn.xbx.
Story mode never noticed, because its levels come out of .prx archives, not loose files.


letting 127.0.0.1:5151 through.6The two-minute ranked game
Start Game, Trick Attack, two minutes. The test hook held forward and mashed ollie and flip on both instances, which is roughly how I played in 2004 too.

When the clock hit zero, the host reported the scores, the service checked that both players had confirmed they were
in the game, and the database got its first THUG2 rows: level 0x2a179957 (load_BO), 2300 and
1994, ratings 210 and 196. Online Stats in the game, back in the menus:

7The login that ate itself
Personal Stats were still empty after that game. That's how retail works: they're read once, at login. Fine: Change
GameSpy Login, same email, Login/Create… and "Please Wait..." forever. The log said session lost,
which was true, and the whole problem.
Logging in again with the same profile makes the service end the old session, and the old session's events poll (always open, waiting for chat and homies) comes back with "closed". If that answer arrives before the login's, the game handles a lost session: forget the profile, bump the request counter, so answers to old requests get dropped. The login's answer arrives next, sees an old counter, and gets dropped. The new session drowned the old one, and the old one took it down with it.
Ending the old session ourselves moves the client to a new generation first, so the old poll's "closed" is ignored. Log in again, and Personal Stats say Boston, 2,300.

8Bonus level: the address that wouldn't leave
Everything committed, build script run… and the game still called online.epicmaxim.net, THUG1's
service. The source said online2. The binary said online. CMake said nothing, because CMake
never says anything: the address was a cache variable, and every build folder made before this chapter remembered the
old default. A changed default never beats a cached value. So the variable got a new name, and the old one gets thrown
out on the next configure:
Where it landed
THUG2's online menus, all of them retail, talk to online2.epicmaxim.net: profiles, the four regions and
Beginners, chat, homies with their status, the pager, hosting and joining through NAT, ranked games on the 15 levels and
the Online Stats boards. How to use it is in the Online play guide.

The screen still says "Powered by GameSpy". It isn't. It's powered by a Go program, a PostgreSQL database and a lot of goodwill. The logo stays anyway: some things you just don't paint over.



