Under the microscope: LMA Manager 2003–2006 (PlayStation 2, Xbox)

5 min read Original article ↗

The UK-based developer and publisher Codemasters used to run a pay-per-minute phone hotline that gave out "bonus codes" for some of its games. The codes were tied to a player's "unique ID," and couldn't be shared.

The code hotline is no longer in operation. So is there any way to get bonus codes now? For some games, yes: some intrepid hackers have reverse engineered how they work and posted generator scripts online. This page hosts several generators for the Colin McRae Rally series and the TOCA Race Driver series.

I managed to reverse engineer the bonus code schemes used by four more Codemasters titles, the LMA Manager soccer games for 2003 through 2006:

  • LMA Manager 2003, 2004, and 2005: These use a custom obfuscation algorithm that generates codes from unique IDs. I re-implemented it from decompiled code in the game data.

  • LMA Manager 2006: This uses an RSA public key to validate codes for particular unique IDs. This would be a major obstacle, but key is only 64 bits. I factored it and recovered the primary key.

Details are below…

The hotline advertisement screen in LMA Manager 2003, showing a Unique ID.

I started looking at LMA Manager 2003 for PlayStation 2 by putting an easily identifiable string into the Enter Name screen – this is where the game checks for special codes.

"32BITS" is my go-to input for easy searching

Searching for that string in memory yields a couple of hits:

00409a28 "32BITS"
00460d04 "32BITS"

To trace how those memory locations are used, I loaded a memory snapshot from the PCSX2 emulator into Ghidra with the Emotion Engine plugin.

Ghidra finds several references to the first address. The one in the function at 001f38c0 is the most interesting. Stepping through it with the debugger reveals that it’s doing this:

  • Copying the user name into a new buffer (strcpy is at 002e2680).

  • Converting the copy to uppercase (toupper is at 002e2c98).

  • Comparing the uppercase copy to 25 different strings (strcmp is at 002e2728).

By looking at the arguments to the strcmp in a debugger, it’s possible to see all of the codes for a particular Unique ID. Here are a few for 9817:

004097e0 "LMA3FZYORZ"
004097ec "LMA3RWMYMT"
00409804 "LMA3LWHIKX"
00409810 "LMA3PLSPAO"

Where do these come from? Ghidra finds references to the locations in calls to the function at 001958e8. Its arguments are:

  • An output buffer.

  • A length.

  • The Unique ID value.

  • An index.

This is the code generation function, which uses a custom obfuscation scheme. It combines the digits of the unique ID with each code’s index. The resulting bytes are then converted to ASCII values and then scrambled. Finally, the scrambled values are prepended with the string LMA3.

Here’s my Python version of the algorithm:

By calling generate_code for each index, you can get 20 of the 25 codes for a particular unique ID.

Left: Entering a code in LMA Manager 2003. Right: Code accepted.

The other five are portable – not tied to a particular unique ID. They enable these effects:

  • LMA2003MA: Sell Out

  • LMA2003MB: Anytime, Any Place

  • LMA2003MC: That’s All Folks

  • LMA2003A: Loadza Money

  • LMA2003B: Low Gravity Ball

You can get all 25 codes by using my script – supply your unique ID and it will generate them all.

LMA Manager 2004 and LMA Manager 2005 both use variations on this generation scheme. They only differ in the prefix and final scrambling of characters. Here are my scripts for them:

These two games have an All bonuses code, which enables all of the effects in one shot.

Unlocking all bonuses in LMA Manager 2005

LMA Manager 2006 takes things up a notch. Its codes aren’t obfuscated with a simple encoding and scrambling system. Instead, the game embeds an RSA public key and uses it to validate that your entries were generated by the corresponding private key.

This scheme would be exceedingly difficult to crack had Codemasters used sufficiently complex RSA parameters. But the game data reveals that they used these:

  • A public exponent of 65537.

  • A modulus of 18640700846213755993.

The public exponent is fine, but the modulus is laughably small. It can easily be factored into its components.

>>> from more_itertools import factor
>>> list(factor(18640700846213755993))
[1009288019, 18469158947]

And from those components, the private exponent can be computed:

>>> pow(65537, -1, (1009288019 - 1) * (18469158947 - 1))
>>> 13033159791913962041

This gives us most of what we need to be able to generate and validate codes, but not everything – the game processes user-supplied codes in a particular way before performing the RSA operations.

Fortunately, the PlayStation 2 version of the game ships with debug symbols. The core code processing logic is in the function called CMBonus_VerifyBonusCode. Here’s Ghidra’s decompilation of that function’s body with my labels added:

CodeMasters used a "HugeNum" library to do the big integer operations

In brief:

  • The function called CMBonus_ASCIIToCCS32 handles the translation of input characters into RSA-operable bytes. It uses a custom Base32 encoding scheme based on the digits 0–9 and the characters A–Z, but the characters I, O, S, Z are omitted.

  • The game XORs the result of the RSA operation with a static value, 0xCC0DE3A54E7550F4 (which looks a bit like leetspeak?).

  • This new value is checked against another value that’s derived from the code index and 4 bytes of the RSA modulus.

I gave the decompiled code to Gemini 3, and it helped me produce this script. It replicates this process and can generate all 28 bonus codes. The last is an All bonuses code, so you really only need one.

All bonuses enabled

I’ve submitted the generators above to Silent’s Blog, such that they can join the others there. Make sure to read Silent’s writeup on factoring the even smaller RSA key used by TOCA Race Driver 3.

Many thanks to Gemini 3 for interpreting the RSA parameter structs, and for making sense of the post-RSA operations.

For more adventures in retro game reverse engineering, see my archive here at Substack. And to get the next article as soon as it comes out, subscribe to the newsletter version of Rings of Saturn:

Discussion about this post

Ready for more?