TL;DR: Go to “TL;DR” below for a quick way to generate a patched EXE.
Legal Crime was developed by Byte Enchanters, and released in 1997. It was moderately successful, being featured in many review sites, magazines, and shareware collections, but at some point the game and the company basically disappeared from the web. Today, with link rot corroding the web, and search engines “forgetting” the past, even its Wikipedia page is quite short and unremarkable.
The game is a mob simulator, in which the player controls thugs which go into businesses to sell “protection”, extracting money from them and even converting them into façades for illegal activities. The goal is to reach a certain level of income, while defending against rival gangs. There is also a sort of “technology tree”, which is made available by assigning money to bribe politicians, local police, and military. Overall it is a fun idea, and the game is still playable even with its outdated graphics.
At the time of release it even supported online multiplayer mode, and according to the reviews back then that was its best feature. Not surprisingly, the servers have not existed for quite a long time and therefore that part of the experience will likely never be available anymore.
To finish the description, I must add that back in the day I remember reading that the demise of the company was a bit strange, there was even some kind of threat message circulating online from someone who promised the company would go down and the game would never be published again, something teenage me read as “maybe they anger the wrong person”, but that current me believes was simply that they didn’t pay the sysadmin, or something like that. In any case I’ve been unable to find a page telling that part of the story, so it might be just my headcannon.
Today, the game can be found online in two versions:
v1.0 - v1.1which is the version most people who played the game is likely to have encountered, with not only a much wider distribution but also some translated variants available. A valid key for this version can be found at https://megagames.com/download/268812/0.
v1.2.3, the last version ever published
v1.0 can be downloaded from the Internet Archive, while
v1.2.3 can be found in the Wayback
Machine or directly downloaded from Chip Magazine
There is seemingly no license code available for
v1.2.3 anywhere on the Web. This mean this
version will only run in Demo mode: fully functional but only the first 8 scenarios of the
“campaign” can be played, and no custom scenarios can be added. The latter is not a huge loss
considering it is not possible to find any custom maps in the current web, but the former means only
about a quarter of the full game is available.
It is worth mentioning that apart from the game itself, the Internet Archive also has a download for a scenario editor, at https://archive.org/details/LegalCrimeMapEditor_1020.
Anyhow, the reason why I’m writing this is because I remember playing Legal Crime a lot, and it is part of a very happy time in my life which later came to an end due to circumstances that don’t matter right now.
Because of that, it is a real shame for me that the game cannot be played in full. I had to try something to fix that.
I’m not going to go into details, mainly because my knowledge is not enough to do it properly. But, as you might know, deep below all programs are just instructions for the processor to run, and this means all programs, regardless of which language they are written in, eventually must become a series of instructions.
Instructions for a CPU are binary code. Buried below the layers and layers of abstractions which compose the modern Operating Systems and software, the processor simply has some wires with voltage and others without, and the combination tells it whether it has to do a sum, a comparison, or to copy some value somewhere else. I recommend Ben Eater’s excellent Building an 8-bit breadboard computer series in YouTube to learn more about how computers work.
Just a layer above binary is Assembly. In Assembly, binary instructions are written as codes which manipulate CPU registers (very fast memory inside the processor) and RAM directly. This is the lowest level which is practical for a human to learn and work with, and as mentioned before, ALL programs eventually become a series of operations which can be represented in Assembly, regardless of whether they are written in Java, C++, Rust, Python, or any other language.
So, if all programs eventually become a series of instructions, then anything a program does, including for example checking if a valid license is available, can be seen as instructions being executed, and potentially changed.
Of course, to do this, we need proper tools. These tools are called decompilers, and what they do is convert an executable (a program file) to a series of instructions in Assembly. As the C language is very close to Assembly, most of these tools can also attempt to generate C code matching the Assembly code, thus helping analyze the program flow by organizing function calls visually and allowing us to assign useful names to the variables found in the program.
I am not very involved in the community, so I do not know which tools are “popular”, or best regarded. The ones I found easily were IDA Pro and Ghidra. I ended up being successful with Ghidra, so that’s the one I’m using from now on.
Before starting an analysis of the executable to try to find a way around its license check, I wrote
down what I knew, from observing a correct install of
LegalCrime.EXE, found in the installation directory.
LCData.binis created in the same directory.
LCData.binis removed the game reverts to Demo mode
It is reasonable to assume that at some point in the program flow the file is read and some calculations are made with the contents, and that these calculations result in the game validating whether it is licensed or not.
The goal, then, is to find the place in the program where the file is read, and either figure out the algorithm which checks the numbers so valid keys can be generated (creating a keygen), or just bypass the check altogether.
First I installed Ghidra, created a new Project, opened the Code Browser tool, and finally used
File -> Import File to load
LegalCrime.EXE from the game directory.
This resulted in a window with a tree view at the left, a Listing panel in the middle, and a Decompiler panel at the right, with generated C code (if I select the Analyze option)
Once the code is available, I searched for
LCData.bin. If the file name is known by the program,
then for sure it must be written somewhere in its code. Indeed, selecting to Search, Program Text,
for “LCData.bin” in the Listing Display, yields 4 results:
Selecting each one shows the generated C code for the “function” where it is found within the Assembly code. And within the Assembly code, selecting a line shows the corresponding line in the C code.
What comes below is my analysis of what I was seeing, in more or less the level of detail I understand, and only describing the “happy path”, as if I had just opened the file once and found the solution right away. That cannot be farther from the truth: I’m not an expert on this so it took me a lot of time to understand what I was doing and get to a satisfactory result. In summary: this is not a guide, instead it is just what I hope is a moderately entertaining story.
The first result is listed as
FUN is a short for function, and
0040c3dd is the memory location within the program where the function code begins.
The first thing the code does is check if
DAT_0047f994 is equal to zero, which seems to always
be the case considering the memory location in the file (which appears if we click the variable
name) just says
00000000h. But I guess it could be modified at execution time by some other
routine within the program.
Next, a function is called and its value is assigned to
uVar2. Notice that this function is the
same as the second search result!
uVar2 is not zero,
FUN_00476884 is called, with what seems to be the file name as first
parameter, and a second parameter
DAT_0047f9f7. This function returns something on
Then, if the result of calling that function is not zero,
FUN_004759cc is called with
as first parameter, and a format string (
%ld) as second, and the result is assigned to
Following the reference
DAT_0047f9f7 shows its value is “
r“. So, this call looks a lot like
a call to fopen(), and so I assume at this point is where the file is opened.
I found it useful at this point to rename those references to match what I thought were their
purposes, so I right-clicked
FUN_00476884 and renamed it to
fopen, and then did the same for
DAT_0047f9f7, renaming it to
If the assumption is correct regarding that first call being
fopen, then since the second call
takes a file pointer, a format string, and returns a number, it should be fscanf() trying to read
a string from the file.
The next function call takes a file pointer and sole parameter and returns nothing, so I assume it is a call to fclose().
In summary, what this code does is try to open the file and read a number from it, and if it is
successful it returns
local_c, which is a location from the stack. This is most likely a value
set by the call to
fscanf, as the function may take an additional number of arguments to store
the values it reads from the file. So,
local_c is the value read, but since I don’t know which
code calls this routine, this doesn’t give me any useful information at this point.
The second result is
FUN_0040c442, and as mentioned above, this is a function called from the
first match. Thanks to the small work spent renaming globals, the initial analysis is quite easy.
This snippet also opens the file and runs
fscanf, this time with a different format string. It
now expects to read 7 numbers from the file.
FUN_0040c312 is called, with what should be the values read by
fscanf. If the result
of that function is not zero, this function returns 1, otherwise it returns 0.
FUN_0040c312 doesn’t match any of the other results for “LCData.bin”, but the code snippet seems
to be doing some checks on the values. In any case, apparently the only thing that matters from that
function is whether it returns 0 or something else.
My general assumption regarding these checks is that I want the function flow to run as many steps as possible, in the understanding that the license checks have many fail conditions (file doesn’t exist, file can’t be opened, file is empty, file has the wrong format, file has good format but wrong values, etc), but only one success condition, it being everything is OK with the file and the calculations match.
Given that, it seemed to me that a first attempt at cracking the license check could be to switch the return values of this second function, so if I could make it return 1, its caller (the first match, described above) would continue its “good” flow.
Checking the third and fourth results shows quite long routines, which I assume are the actual checks on the numbers read and therefore what validates them as a valid license code.
I didn’t spend time analyzing these routines, simply because I first tried to bypass the checks with what I knew at this point.
Also, in the third result (
FUN_0042a395) the code is writing to the file (there is a call to
w as second parameter), so most likely it is related to the code which stores a
valid license code after it is entered in the game.
From the analysis of the second snippet where “LCData.bin” is used, I thought I had found the place where the game tries to determine if it has a valid license.
If that assumption was correct, then the license check could be bypassed by reversing the function’s return values, or in other words, making it return 1 when it originally returned 0, and 0 when it originally returned 1.
To test if that would work, I right-clicked the
JZ (Jump if Zero) at
0040c4ae and used the
“Patch Instruction” option to replace it with a
JNZ (Jump if Not Zero).
After making the change, I saved the new
.EXE using File -> Export Program.
The new executable should returns success when the license check fails, but it still requires the
LCData.bin to exist and contain 7 numbers. So I created the file and wrote seven zeroes to it:
0 0 0 0 0 0 0
Then launched the game… and it worked! The “DEMO” sign did not appear on the title screen, it didn’t ask me for a license code, and it let me start any of the scenarios. Mission accomplished :)
There is only a minor inconvenience. Because the program was modified, its digital signature breaks, and that causes Windows to detect it as being from an “unknown developer” and ask for confirmation every time the game starts. Nothing that would affect me, really, since I intend on playing it using Lutris.
A more interesting and satisfactory solution, which wouldn’t require patching the binary, would be to figure out the algorithm used by the program to validate the license numbers from the file, and using that knowledge to generate a valid key.
Finding the code for the algorithm is not hard, it seems to be the function from the fourth result listed above, or maybe the function whose return value the program is now ignoring.
Unfortunately (for me, of course), I lack the experience required to reverse-engineer the algorithm. Especially since every time I launch the game in a debugger it shows an error and exits, and this prevents me from validating my assumptions of how it uses the values from the file.
So, I decided to not go down this particular rabbit hole, and instead use the time actually playing the game :)
I have been reading about assembly, debuggers, decompilers, and code modification for a long time, but I had never attempted it. This was a learning experience to me.
In particular, I found it quite interesting to discover patterns in the Assembly code and how they
translate to the generated C code. For example, the function calling convention is always to
PUSH each parameter in reverse order, and then
CALL the location in memory of the desired function.
Other examples are things like
TEST EBX,EBX, which in C translates to
if (variable == 0) or
if (variable == 1) depending on which kind of jump instruction follows the
TEST. The value
variable is usually set by a
MOV immediately before the
Not being an expert on Assembly, I noticed that it is easier to follow once I started identifying
some of the “idioms” and patterns. In the same spirit, I noticed how the generated C code changed
when I modified the
JNZ, it reversed the condition of the
if block. I found that
amusing and spent some time doing other changes elsewhere in the code, to see how the C generator
Anyhow, I’m happy to have learned a bit more about how things work, happy that I can now play this game I played more than 20 years ago, and even happier that this experiment sent my mind back to very good memories of my teen years.
If you have made it here, I thank you for reading. I hope what I wrote here is useful and that by playing this old forgotten game you can remember some good old times and, why not, maybe even create new ones.
All of the above is nice and good (I hope!) but if you just want to create a working game there is no need to install Ghidra or deal with Assembly. At the end of the day the original and patched files differ only by a single instruction, so getting a working binary is relatively easy:
Ensure the MD5 sum of your copy of
not continue if it is not the same. The file should match if the game is version 1.2.3 downloaded
from Chip Magazine
LCData.bin in the game directory, with contents “0 0 0 0 0 0 0” (seven zeroes
separated by a space)
LegalCrime.EXE just to be safe if the patch fails.
Modify the .EXE
In Linux or any system with
printf '\x75' | dd of=LegalCrime.EXE bs=1 seek=43694 count=1 conv=notrunc
In Windows, create the following script in PowerShell:
$bytes = [System.IO.File]::ReadAllBytes("LegalCrime.EXE"); $bytes = 0x75; [System.IO.File]::WriteAllBytes("LegalCrime.EXE", $bytes);
The Linux solution is a modification of a response from StackOverflow. It uses
dd to write a
single byte in the right location within the file, that byte corresponds to the instruction modified
in the assembly code.
The Windows snippet is also from StackOverflow, and is the same idea, only more explicit: we instruct the script to modify the exact byte we want changed and write the result.
The modification can be validated as correct by checking the md5 sum of each file:
8045f00ce394a7315cc0e16b72e08dc6 LegalCrime.EXE (original) 0626ef6ed085be866ce820738ce9f35e LegalCrime.EXE (patched)