Preface
2010 was the golden era of arcades. As mobile devices and home consoles became widespread, the arcade industry gradually declined. Although some policies were introduced domestically to encourage the amusement gaming equipment industry, the sector has long been out of favor with investors. The 2020 pandemic dealt an even heavier blow to the arcade business.
Back then, 100 RMB might only buy 50 credits; now 100 RMB can buy 200 credits.
I like racing games. Wangan Midnight, Initial D, and Speed Driver have been the hottest games in arcades in recent years, because they have accounts and thus a social component. Some time ago, when I was cracking the player app for Speed Driver 5, I learned that IGS (International Games System) is the company behind the Knights of Valour arcade game I played as a kid and Journey to the West. IGS’s anti-cracking is top-tier across the entire industry, and the Speed Driver series is even known as “uncrackable.”
Last month, the “Super Player” arcade near my home shut down. I felt that sooner or later the Speed Driver series might disappear, so I decided to challenge myself to crack it.
The release order of the IGS Speed Driver series is as follows:
| Game | Device Model | Year |
|---|---|---|
| Speed Driver: Evolution | 2004 | |
| Speed Driver 2 | E2000 | 2007 |
| Speed Driver 3 | E3000, E3100 | 2010 |
| Speed Driver 4 | E3000, E3100, S3000 | 2013 |
| Speed Driver 5 | S3000 | 2019 |
I planned to start with the E2000 platform. The difficulty should definitely be lower than PGM (PolyGame Master) because it’s PC-based: there’s no need to emulate a sound card or GPU. The hardware has almost no physical protection, the CPU ISA is x86, the OS is Linux, there’s no anti-debugging, and no VMP—this is basically a tutorial level.
In Asia, the Speed Driver series has influence comparable to Wangan Midnight, Initial D, and Storm Racer G (all of which have been cracked). IGS’s anti-cracking is the most successful: throughout the entire product lifecycle, it was never cracked.
Hardware analysis
In Arcade-docs, you can look up the hardware information of each device and the games it supports. Leveraging my “skill” at scavenging e-waste, I managed to get an E2000 host. There are quite a few enthusiasts abroad buying them too, so the price has gone up.
External interface analysis
On the front there are two RS-485 ports, and the CF card can be removed externally.

Back-side ports, with the very “millennium PC” vibe:
- 12V DC
- 2 x DB9 COM
- 4 x USB 2.0
- RJ45 LAN
- 3.5mm Audio Out
- 25pin + 30pin I/O connector

Mainboard analysis
Given that cracking this doesn’t seem hard, I’m not going to analyze the other components whose silkscreen is covered by stickers—it’s too annoying.
The host I bought came with the game Percussion Master 2008 installed, but Speed Driver 2 also runs.
- Mainboard model: I-JOIN E2000-V256 IH-02 (Advantech)
- A - CPU: Celeron M 370 (1.5 GHz)
- B - Northbridge: unknown
- C - GPU: NVIDIA GeForce 6200 (256 MB, GDDR2)
- D - 2 x DDR 333 256MB
- E - Chipset: Intel 852GME (ICH4-M)
- F - PCI9030, a GPIO chip, likely used for transmitting controller signals.
- G - I/O controller with an LPC interface
- H - A11 BIOS chip SST 49LF004B (PLCC32)
- I - IH-C02 ALTERA EPM3032ALC44-10N CPLD (PLCC44), controller; contains ROM, purpose unknown
- J - IGS EV29LV640-90PCR 8MB EEPROM, DIP 48-pin package, an IGS custom chip. This chip has a label with the game name on it, suggesting the BIOS ROM is also game-related. There may be content related to the V21 chip.
- K - V21, IGS036E, maybe an FPGA or ASIC used to encrypt/process the input/output of control signals
- L - 64K x 16 HIGH-SPEED CMOS STATIC RAM x3
- M - CF card: ADATA 2GB (266X)
- N - IDE HDD connector
I have many ways to get a root shell on the hardware, but I’m more interested in the game loading process. So let’s start with software reverse engineering.

I/O board analysis
These boards are also called control boards. They should be used to connect game controllers such as the steering wheel, brake/throttle, coin acceptor, etc.
They communicate via the 25pin + 30pin I/O connector to the mainboard.
The price may even be higher than the host itself.
In theory, you could use a logic analyzer to capture these sensor signals and then implement control functionality. But I’ve heard the control signals are handled by the V21 chip (ASIC) and may be encrypted. I also don’t plan to clone a control board. These arcade racing games aren’t as fun as Forza Horizon 5. Arcades are cheap these days—if you really want to play, just pay to play at an arcade, or buy a steering wheel setup and play at home.


Filesystem analysis
Dump the CF card directly. The file size is 2GB. The file command output is:
DOS/MBR boot sector, LInux i386 boot LOader; partition 1 : ID=0x1, active, start-CHS (0x0,1,1), end-CHS (0x2,63,63), startsector 63, 12033 sectors; partition 2 : ID=0x83, start-CHS (0x3,0,1), end-CHS (0x22,63,63), startsector 12096, 129024 sectors; partition 3 : ID=0x83, start-CHS (0x62,0,1), end-CHS (0x399,63,63), startsector 395136, 3322368 sectors; partition 4 : ID=0x83, start-CHS (0x23,0,1), end-CHS (0x61,63,63), startsector 141120, 254016 sectors
Boot process: LILO is used as the MBR, installed directly in the first sector to boot Linux.
+----------------------------------------+
| Master Boot Record Operating system |
|----------------------------------------|
| LILO ---------------> Linux |
| ---> other OS |
+----------------------------------------+
View the partition information via fdisk. This is a Linux OS with four partitions, contiguous from start to end. Everything after the last partition is 0x00, so there is no hidden partition.
Disk ./percussion_master_2008.img: 1.77 GiB, 1903878144 bytes, 3718512 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x6e4a3fef
Device Boot Start End Sectors Size Id Type
./percussion_master_2008.img1 * 63 12095 12033 5.9M 1 FAT12
./percussion_master_2008.img2 12096 141119 129024 63M 83 Linux
./percussion_master_2008.img3 395136 3717503 3322368 1.6G 83 Linux
./percussion_master_2008.img4 141120 395135 254016 124M 83 Linux
One notable trait of the E2000 platform layout is that partition 3 and partition 4 are in “reverse” order.
Partition 2 and partition 4 cannot be fully extracted by 7z. binwalk can identify some compressed regions, so it is very likely a heavily modified filesystem.
The purpose of each partition is as follows (later I found the original filesystem types for partitions 2 and 4):
Partition 1: Ext2, Bootloader, kernel
Partition 2: IGS CRAMFS, RootFS
Partition 3: Ext3, Log Data
Partition 4: IGS SquashFS, Game Data
Kernel reversing
So far I have not found the rootfs or the game executable; they are most likely located in partition 2 and partition 4. I think reversing the kernel can save a lot of detours. Without relying on the kernel, you could also directly reverse the filesystem format from the raw data, but that’s boring. And whatever extractor you end up writing won’t beat open-source filesystem unpacking tools anyway. First, look at partition 1: it contains a loader and a kernel. The kernel version is shown, but it may not be the correct one.
SD2: LILO (LInux LOder)
SD2-OS-61P: Linux kernel x86 boot executable, bzImage, version 2.4.31-IGS_V0.5a (james@Code_Server.linnet.net.tw) #1 Thu Aug 30 19:06:24 CST 2007, RO-rootFS, root_dev 0X305, Normal VGA, setup size 512*6, syssize 0x3b88, jump 0x230 0xe800040000000000 instruction, protocol 2.3
LILO has been customized by IGS. It shows “IGS Loader v2.0 Boot Menu 2007/04”, which indicates the base version is lilo-22.8.
There’s nothing interesting to analyze here. Start extracting vmlinux. The kernel file is in bzImage format and needs to be decompressed.
7z x ./SD2-OS-61P -okernel.decomped
Next is standard kernel reversing. The first step is to determine the image base. There are many ways to do that; you can refer to my other posts.
Here, after looking twice, you can basically guess it: 0xC0100000.
With the base configured correctly, IDA can automatically identify some xrefs; the rest needs manual recovery. IDA 9.0 changed some APIs, so I had to rewrite some scripts again.
import ida_bytes
import ida_segment
import ida_funcs
import ida_ida
def find_and_make_function(start_ea, end_ea, pattern_str):
image_base = idaapi.get_imagebase()
pattern = ida_bytes.compiled_binpat_vec_t()
err = ida_bytes.parse_binpat_str(pattern, image_base, pattern_str, 16)
if err:
return
current_ea = start_ea
found_count = 0
while current_ea < end_ea:
current_ea, index = ida_bytes.bin_search(
current_ea,
end_ea,
pattern,
ida_bytes.BIN_SEARCH_FORWARD | ida_bytes.BIN_SEARCH_NOSHOW)
if current_ea == ida_idaapi.BADADDR:
break
if current_ea % 4 != 0:
current_ea += 1
continue
if ida_bytes.get_flags(current_ea) & ida_bytes.FF_CODE:
if idc.get_func_flags(current_ea) != -1:
current_ea += 1
continue
seg = ida_segment.getseg(current_ea)
if not seg:
current_ea += 1
continue
if not ida_funcs.add_func(current_ea):
print("Create function failed at 0x{:X}".format(current_ea))
else:
found_count += 1
current_ea += 4
print("Search finished, {} functions created".format(found_count))
find_and_make_function(0xC0100000, 0xC0508000, "55 89 E5")
# You can define function entry opcodes based on your case
Of course, even at this point, not all xrefs are fully recognized. In the data segment, some strings and offsets are not recovered well either, but it doesn’t matter much. If you’re completely stuck, recovering those bits may lead to new discoveries.
Next is to find an entry point for analysis. There’s honestly not much technical content here—I have many approaches, for example starting from filesystem characteristics. But writing everything out would be pedantic and unnecessary.
We can confirm the kernel version is 2.4.31, and the IGS version is v1.0, not v0.5 as shown by the bzImage banner.

From the following strings, we can determine which upstream filesystem versions correspond to IGS’s modified filesystems:
- rofs: cramfs
- shfs: squashfs 2.2

Linux 2.4.31 does not include squashfs. So I directly looked for a squashfs patch. IGS’s modified SHFS is based on squashfs 2.2. https://master.dl.sourceforge.net/project/squashfs/OldFiles/squashfs2.2r2.tar.gz
Linux 2.4 doesn’t support kallsyms, so symbols need to be recovered manually. Some functions like printf, memcpy, str* can be recognized quickly by eye, but I wanted to analyze the system boot process, so I needed to recover some kernel-specific symbols.
For such old devices, cross-compiling on a modern Linux is not feasible. I installed a 32-bit CentOS 5.10 VM, built it successfully, and extracted System.map and vmlinux, making it easier to compare what parts were modified by IGS.

For firmware where you can’t find a ramdisk or rootfs, the first step is definitely to locate sys_mount and the boot parameters in the kernel.
root=/dev/hdc2 ro console=ttyS1,115200
sys_mount("/dev/hdc3", "/PM2008v2", "shfs", flag, data)
sys_mount("/dev/hdc4", "/PM2008v2/pm2_data", "ext3", flag, data)
From this, we can determine the rootfs should be partition 2, and the game data is in partition 3.
The logic for mounting the ramdisk is located here:
prepare_namespace
->rd_load_disk
->rd_load_image
->identify_ramdisk_image
Here we can confirm IGS supports ramdisks in squashfs and IGS ROFS formats, and we can also identify their magic numbers.

I won’t record the full analysis process. They modified the FS header, which makes the analysis somewhat nasty 🤢.
Extracting the filesystem
I analyzed the headers of three custom filesystems on the E2000 platform; the contents are as follows.
IGS SHFS V1 Header
struct squashfs_super_block_22_v1 {
unsigned int s_magic; // 0xD4AA2682
unsigned int block_size_1:16;
unsigned int block_log:16;
unsigned int major_number;
unsigned int minor_number;
unsigned int inodes;
unsigned int bytes_used;
unsigned int uid_start;
unsigned int guid_start;
unsigned int inode_table_start;
unsigned int directory_table_start;
unsigned int flags:8;
unsigned int no_uids:8;
unsigned int no_guids:8;
int mkfs_time /* time of filesystem creation */;
squashfs_inode root_inode;
unsigned int block_size;
unsigned int fragments;
unsigned int fragment_table_start;
} __attribute__ ((packed));
IGS SHFS V2 Header
Using Percussion Master 2008 as an example:
struct squashfs_super_block_22_v2 {
unsigned int s_magic; // 0xD4AA2682
char igs_info[64]; // banner
unsigned int block_size_1:16;
unsigned int block_log:16;
unsigned int igs_fs_version_major; // 59 17 23 96
unsigned int igs_fs_version_minor; // E1 5D 70 00
unsigned int major_number; // 31 64 52 E5
unsigned int minor_number; // 92 2C 03 68
unsigned int inodes;
unsigned int bytes_used;
unsigned int uid_start;
unsigned int guid_start;
unsigned int inode_table_start;
unsigned int directory_table_start;
unsigned int flags:8;
unsigned int no_uids:8;
unsigned int no_guids:8;
int mkfs_time /* time of filesystem creation */;
squashfs_inode root_inode;
unsigned int block_size;
unsigned int fragments;
unsigned int fragment_table_start;
} __attribute__ ((packed));
IGS ROFS V1 Header
struct cramfs_inode {
unsigned int inode_magic;
unsigned int namelen:6, offset:26;
unsigned int size:24, gid:8;
unsigned int mode:16, uid:16;
};
struct cramfs_info {
unsigned int crc;
unsigned int edition;
unsigned int blocks;
unsigned int files;
};
struct igs_rofs_super_block {
unsigned int magic1; // 0x81006e6a
unsigned int future;
char igs_info[64];
unsigned int size;
unsigned int magic2; // 0xD4AA2682
unsigned int flags;
unsigned int padding;
cramfs_info fsid;
char name[64];
cramfs_inode root[2];
};
igs_rofs_super_block File;
By porting the headers above into cramfs-tools and squashfs-tools, you can extract the filesystem perfectly, and you can also detect whether files are corrupted. I have uploaded the code to GitHub: https://github.com/gorgiaxx/igs-toolkits
This version is not hard to extract, but the next version (E3000) introduces encryption.

Player @novacosmic00 previously asked me to help extract files for GoGoBall. He said he had already decrypted files from other games, but GoGoBall was the only one he couldn’t decrypt. I tried extracting with my modified tools and found the file CRC was wrong, but most files could still be extracted.
After I finished extracting the firmware and talked with @novacosmic00, I learned that someone had researched this two years ago and wrote extraction scripts.
https://github.com/batteryshark/igstools/tree/main/scripts
The author may have inferred the structs by analyzing filesystem contents and reverse-engineering address patterns. That’s also a valid approach, but the downside is that extracted files can be messy, and you can’t detect CRC errors.
shadow-
The shadow file clears the root password by default.
root:x:13130:0:99999:7:::
test::13074:0:99999:7:::
This is the original file:
root:$1$qAw/5vcb$x9rPCAwLMdRBQXwlq1zG70:13130:0:99999:7:::
test:$1$JjP1oLAJ$xilZIedv3S3jbs8oTZAad1:13074:0:99999:7:::
Anti-cracking
/PM2008v2/PM2008v2 has stripped symbols, and from string patterns it also looks like a normal loader.

But in reality, if you run it on your own computer, it will overwrite the first 512k of your partition with zeros. I actually ran it back then—thankfully I didn’t run as root, and my disk was NVMe. Who would have thought this 18-year-old game still had this trick 🙈. I had never touched this field before; apparently arcade anti-cracking often includes many “suicidal” logics.

The next post will analyze the in-game protection mechanism.