Sims 1 MCP - Speed Running Reverse Engineering with GhydraMCP

2026-01-26 · 10 min read

SimsMCP

This blogpost shows how to quickly reverse engineer Sims 1 Legacy using Ghidra, GhydraMCP and CheatEngine. To demonstrate, I built a mod that adds a Model Context Protocol (MCP) to the game, something nobody asked for, but here we are.

At 25 years old, The Sims 1 makes an ideal reverse engineering target: small enough to be approachable, yet still challenging when disassembled. I could have gone for a re-implementation like Simitone, but where's the fun in that? I wanted to work with the original binary.

Sims 1 Legacy and Digital Rights Management (DRM)

I purchased the Sims 1 legally through the EA App. However, even though I own the game, I'm apparently not allowed to tinker with the binary due to Digital Rights Management (DRM) restrictions. I want to focus on modding the game, not battling DRM. To bypass these limitations and facilitate my analysis, I used a DRM-free cracked version of the game, specifically Anadius v1.0.0.158. This version removes the DRM protection and simulates the EA App environment, making it much easier to mod and analyze the game.

Furthermore, I used editbin shipped with Visual Studio to disable Address Space Layout Randomization (ASLR) on the binary. This ensures that the binary is loaded at a consistent memory address every time, which is crucial for accurate reverse engineering and analysis.

cmd /c '"C:\Program Files\Microsoft Visual Studio\18\Community\VC\Tools\MSVC\14.50.35717\bin\Hostx64\x64\editbin.exe /DYNAMICBASE:NO "C:\The Sims 1 - Legacy Collection\sims.exe"'

Setting up Ghidra and GhydraMCP

I've used Ghidra to analyze and disassemble the Sims 1 binary. Ghidra is a powerful open-source reverse engineering tool developed by the NSA. To enhance my reverse engineering experience, I utilized GhydraMCP which is a fork of the original GhidraMCP but with much better support for parallel instances, automatically creating data structures, and better rate limits. I've hooked this to Claude Code with Opus 4.5 to kick-off all kinds of analysis on the disassembled code.

Reverse Engineering sims.exe

Once you open the sims.exe binary with Ghidra and you run the automatic analysis, you will see something like this:

Ghidra Entry

This is a good start, and Ghidra has done a decent job at identifying functions and data sections. It is also able to find cross references to other functions which helps a lot when trying to understand the code flow. However, all function and data types have generic names like FUN_00401234 and DAT_00405678. This is where GhydraMCP comes in.

Finding Important Data Structures

We know, from Simitone, that the Sims 1 uses a custom game engine called the "SimAntics Engine" which is effectively the brain of The Sims. We like to interact with the engine but we don't need to disassemble the engine to use it. We will just read the data structures used by the rendering pipeline.

We start similarly to Using AI to Save Every Sim From Your Cruelty and try to extract the famous rosebud cheat from the binary.

> Analyze sims.exe using GhydraMCP. Find the string 'rosebud', find functions where this is used. Give them meaningful names.

That's it, after running for 6m 36:

Here's a summary of the analysis and renamed functions:

  Summary: "rosebud" Cheat Analysis in sims.exe

  String Location

  - Address: 0x00c64c7c - Contains the "rosebud" string

  Functions Renamed
  ┌────────────┬──────────────┬────────────────────────┬───────────────────────────────────────────────────────┐
  │  Address   │   Old Name   │        New Name        │                        Purpose                        │
  ├────────────┼──────────────┼────────────────────────┼───────────────────────────────────────────────────────┤
  │ 0x0045a0e0 │ FUN_0045a0e0 │ RegisterCheatCodes     │ Registers all cheat codes including rosebud (ID 0x4c) │
  ├────────────┼──────────────┼────────────────────────┼───────────────────────────────────────────────────────┤
  │ 0x004553b0 │ FUN_004553b0 │ InitializeGame         │ Game startup, calls RegisterCheatCodes                │
  ├────────────┼──────────────┼────────────────────────┼───────────────────────────────────────────────────────┤
  │ 0x0044f0b0 │ FUN_0044f0b0 │ CheatCommandHandler    │ Main handler for all cheat commands (switch table)    │
  ├────────────┼──────────────┼────────────────────────┼───────────────────────────────────────────────────────┤
  │ 0x009753f0 │ FUN_009753f0 │ RegisterConsoleCommand │ Registers a command with its callback                 │
  ├────────────┼──────────────┼────────────────────────┼───────────────────────────────────────────────────────┤
  │ 0x00556e50 │ FUN_00556e50 │ GetFamilyFunds         │ Returns current family simoleons (from offset +0x90)  │
  ├────────────┼──────────────┼────────────────────────┼───────────────────────────────────────────────────────┤
  │ 0x00557320 │ FUN_00557320 │ SetFamilyFunds         │ Sets family simoleons and updates UI                  │
  ├────────────┼──────────────┼────────────────────────┼───────────────────────────────────────────────────────┤
  │ 0x00539190 │ FUN_00539190 │ NotifyFundsChanged     │ Notifies UI when funds change                         │
  └────────────┴──────────────┴────────────────────────┴───────────────────────────────────────────────────────┘
  Data Renamed

  - 0x00e06c24 → g_pGameState (pointer to game state, family at +0x1C)

  Rosebud Cheat Logic (at 0x00450e7e)

  // Get current family from game state
  family = g_pGameState->currentFamily;  // offset +0x1C

  // Add 1000 simoleons
  newFunds = GetFamilyFunds(family) + 1000;
  SetFamilyFunds(family, newFunds);

So now we have a much better understanding of the functions that are involved using cheat codes, and we immediately got a named global variable g_pGameState which points to the main game state structure. We don't know the exact structure layout yet, but this is very useful, as we can now use this pointer to find other important data structures in the game.

Next, we use CheatEngine to search for all code that is accessing the GameState structure. We can do this by searching for the address of the GameState structure in CheatEngine and then using the "Find out what accesses this address" feature. This will give us a list of all functions that read or write to the GameState structure. We are especially interested in the functions that access the pointer a lot per second, which is probably the rendering pipeline.

Cheat Engine

Understanding the Rendering Pipeline

Again, GhydraMCP is able to help us, so let's ask Claude to analyze it.

> Analyze the function at 0x00412345 and give a high-level description of what the code does. Give it a meaningful name if you can. Then go up 1 level in the call graph and analyze that function as well. Analyze a maximum of 3 levels up in the call graph and rename at most 10 functions.

We try to keep some constraints on the analysis so that Claude Code doesn't go off the rails. If we don't, it might try to analyze every parent and every sibling in the call tree, and only then make a decision about renaming the function. This can lead to a combinatorial explosion of analysis which is unnecessary and takes a lot of time.

Doing this for a bunch of functions quickly improves readability. For the MCP mod we do not strictly need to understand the rendering pipeline, but carving out more functions and expanding the context helps us, but also helps Claude to make better decisions when creating data structures later on.

img

Using More Cheats to Find Motives

One other cheat that caught my attention was the sim_log begin/end cheat code. It is a simple debugging cheat command that logs the Sims motives to a file. This is very useful for us, as it gives us a way to find the Sim structure and the Motive structure used by the game. Again we just let Claude crunch on it.

> Use GhydraMCP to analyze sims.exe . Analyze the function RegisterCheatCodes and find the cheat code for "sim_log". Then analyze the function that handles this cheat code. Provide a high-level description of what the code does and rename functions and variables as needed to improve readability.

This is a bit of a challenge because both the RegisterCheatCodes and the CheatCommandHandler functions are huge. Claude chokes on the disassembled response, but is still able to store the response in a file, and then selectively read parts to make sense of it.

Cheat Code Registration

The RegisterCheatCodes function at 0045a0e0 registers cheat codes into a table structure. Each entry is 20 bytes
(0x14) containing:
- Offset 0x00: Cheat ID
- Offset 0x04: Name string pointer
- Offset 0x08-0x0C: Additional pointers
- Offset 0x10: Type/flag

The "sim_log" cheat has:
- ID: 6
- Type: 0 (immediate execution type)

The sim_log Handler Function

Found and renamed the handler function:
┌──────────┬───────────────┬────────────────────────────────┐
│ Address  │ Original Name │            New Name            │
├──────────┼───────────────┼────────────────────────────────┤
│ 00606f20 │ FUN_00606f20  │ SimLogCheat_WriteSimulationLog │
└──────────┴───────────────┴────────────────────────────────┘
This function:
1. Opens ./SimLog.txt file
2. Writes column headers: Object, Interaction, Start Time, Total Ticks, Anim Ticks, Routing Ticks, Result, Energy,
Comfort, Hunger, Hygiene, Bladder, Social, Person Running
3. Iterates through simulation data and writes entries
4. Shows success/error message to user

Renamed Data Items
┌──────────┬────────────────────────┬───────────────────────────┐
│ Address  │     Original Name      │         New Name          │
├──────────┼────────────────────────┼───────────────────────────┤
│ 00c6495c │ s_sim_log_00c6495c     │ STR_CHEAT_sim_log         │
├──────────┼────────────────────────┼───────────────────────────┤
│ 00c76f94 │ s_./SimLog.txt_...     │ STR_SimLogFilePath        │
├──────────┼────────────────────────┼───────────────────────────┤
│ 00c77028 │ s_Sim_Logging_...      │ STR_SimLoggingDialogTitle │
├──────────┼────────────────────────┼───────────────────────────┤
│ 00c76fa8 │ s_Logging_completed... │ STR_SimLogSuccess         │
├──────────┼────────────────────────┼───────────────────────────┤
│ 00c76fec │ s_File_SimLog.txt...   │ STR_SimLogFileOpenError   │
├──────────┼────────────────────────┼───────────────────────────┤
│ 00e031cc │ DAT_00e031cc           │ g_CheatCodesTableStart    │
├──────────┼────────────────────────┼───────────────────────────┤
│ 00e031d0 │ DAT_00e031d0           │ g_CheatCodesTableEnd      │
└──────────┴────────────────────────┴───────────────────────────┘

And in SimLogCheat_WriteSimulationLog we can see:

if (*(int *)((int)&iStack_e4 + *(int *)(local_f0 + 4)) == 0) {
    FUN_004e7100(local_e0,"Object\t");
    FUN_004e7100(local_e0,"Interaction\t");
    uVar2 = FUN_004e7100(local_e0,"Start Time",&DAT_00c62cd4);
    FUN_004e7100(uVar2);
    uVar2 = FUN_004e7100(local_e0,"Total Ticks",&DAT_00c62cd4);
    FUN_004e7100(uVar2);
    uVar2 = FUN_004e7100(local_e0,"Anim Ticks",&DAT_00c62cd4);
    FUN_004e7100(uVar2);
    uVar2 = FUN_004e7100(local_e0,"Routing Ticks",&DAT_00c62cd4);
    FUN_004e7100(uVar2);
    uVar2 = FUN_004e7100(local_e0,"Result",&DAT_00c62cd4);
    FUN_004e7100(uVar2);
    uVar2 = FUN_004e7100(local_e0,&DAT_00c76f08,&DAT_00c62cd4);
    FUN_004e7100(uVar2);
    uVar2 = FUN_004e7100(local_e0,"Energy",&DAT_00c62cd4);
    FUN_004e7100(uVar2);
    uVar2 = FUN_004e7100(local_e0,"Comfort",&DAT_00c62cd4);
    FUN_004e7100(uVar2);
    uVar2 = FUN_004e7100(local_e0,"Hunger",&DAT_00c62cd4);
    FUN_004e7100(uVar2);
    uVar2 = FUN_004e7100(local_e0,"Hygiene",&DAT_00c62cd4);
    FUN_004e7100(uVar2);
    uVar2 = FUN_004e7100(local_e0,"Bladder",&DAT_00c62cd4);
    FUN_004e7100(uVar2);
    uVar2 = FUN_004e7100(local_e0,&DAT_00c76f38,&DAT_00c62cd4);
    FUN_004e7100(uVar2);
    uVar2 = FUN_004e7100(local_e0,"Social",&DAT_00c62cd4);
    FUN_004e7100(uVar2);
    uVar2 = FUN_004e7100(local_e0,&DAT_00c76f48,&DAT_00c62cd4);
    FUN_004e7100(uVar2);
    uVar2 = FUN_004e7100(local_e0,"Person Running",&DAT_00c62cd4);
    FUN_004e7100(uVar2);
    FUN_004e7100(local_e0,&DAT_00c5f33c);
    iVar7 = *(int *)(param_1 + 4);
    piVar6 = (int *)(param_1 + 4);
    if (iVar7 != *(int *)(param_1 + 8)) {
        do {
        iVar5 = *(int *)(iVar7 + 0x38);
        if (iVar5 != *(int *)(iVar7 + 0x3c)) {
            do {
            uVar2 = FUN_004fe280(&DAT_00c62cd4);
            uVar2 = FUN_004e7100(local_e0,uVar2);
            FUN_004e7100(uVar2);
            uVar2 = FUN_004fe280(&DAT_00c62cd4);
            uVar2 = FUN_004e7100(local_e0,uVar2);
            FUN_004e7100(uVar2);
            FUN_006076d0(local_e0);
            iVar5 = iVar5 + 0x3c;
            param_1 = local_30;
            } while (iVar5 != *(int *)(iVar7 + 0x3c));
        }
        iVar7 = iVar7 + 0x44;
        } while (iVar7 != *(int *)(param_1 + 8));
        piVar6 = (int *)(param_1 + 4);
    }
    pcVar3 = "Logging completed and written to SimLog.txt in the Sims directory.";
}

Which looks a lot like writing out the motives of each Sim! By analyzing the offsets used here, we can start to piece together the Sim structure and the Motive structure. Again we used Claude to create the data structure for us. It was able to create the following.

img

The initial structure was wrong. Some values were correct but others were showing nonsense. I've used CheatEngine to validate the addresses and values in memory while the game is running to correct the structure, while in the background Claude was running to find cross references with other functions to self-correct the structure. After a few iterations it managed to fix all offsets correctly.

Building the MCP Mod

The mod itself was fairly straightforward once we had the data structures in place. The MCP mod hooks into the game via DLL injection and then reads the GameState structure to access the current Sims and their motives. It then exposes this data via a simple HTTP server following the MCP protocol. A wrapper is built to translate from HTTP to stdio for compatibility with existing MCP clients.

That's it! Speed running a reverse engineering project can be a lot of fun, especially with the right tools and AI assistance. Ghidra, GhydraMCP, and CheatEngine made the process much smoother and allowed me to focus on understanding the game rather than getting bogged down in low-level details taking days to figure out. The key takeaway is to reduce the scope and analyze small parts at a time to prevent the context from being flooded with disassembled code. Once you have a few key data structures and functions understood, the rest falls into place much more easily and Claude can auto-correct its analysis based on the improved context.

If you like this post please star the repository and share it with your friends! https://github.com/pevers/sims-mcp .

img

Peter Evers Peter Evers

Barely updates blog posts but likes a lot of random things