Learning Reverse Engineering by Rebuilding the Windows 7 Games
- 1. Why the Windows 7 games?
- 2. Starting with the simple tools
- 3. Learning not to trust the decompiler
- 4. The debugger is where the pieces come together
- 5. Where AI fits into reverse engineering
- 6. Rebuilding it in Rust
- 7. What I learned
- 8. The result
- 9. That turned out to be a much more useful project than simply following another reverse-engineering tutorial.
I wanted to learn reverse engineering, and I was looking for a project that was complex enough to be interesting without immediately throwing me into malware, anti-cheat systems or some 20-year-old enterprise application with no idea where to start.
The Windows 7 games turned out to be a surprisingly good target.
The collection includes Solitaire, Spider, FreeCell, Hearts, Minesweeper and Chess. They are familiar applications, the behaviour is easy to observe, and you can immediately tell when your understanding of the original is wrong. That makes them particularly useful for learning: you can take something apart, form a hypothesis, implement it, and compare the result against the original.
So that’s what I did.
The result is win7_games, a Rust implementation of the games for Linux.
Why the Windows 7 games?
There are already plenty of alternatives on Linux, so the goal wasn’t simply to make another collection of games.
I wanted something that would force me to learn how real Windows binaries work.
The Windows 7 games are interesting because the game rules themselves aren’t particularly complicated. The interesting parts are everything around them: input handling, state management, rendering, animations, timers, resources and the little pieces of UI behaviour that make the originals feel polished.
A card doesn’t simply appear in another pile. It moves there. An invalid drop doesn’t just change a variable; the card animates back. Minesweeper has its own interaction and rendering behaviour, while Chess introduces a completely different state model.
That gives you a lot of observable behaviour to investigate without needing to understand an enormous application first.
It felt like a good reverse-engineering laboratory.
Starting with the simple tools
I started with the basic tools rather than immediately diving into a full reverse-engineering environment.
strings was particularly useful at the beginning. It gave me names, UI text, DLL references, resource information and other clues about what was inside the binaries.
From there I used other tools such as objdump, readelf and debuggers, gradually building a picture of the applications before moving deeper into the code.
Eventually Ghidra became the main tool for navigating the binaries and understanding their structure.
That progression was important because reverse engineering is much easier when you have a question to answer.
Instead of looking at a function and asking, “What does this random block of assembly do?”, I could start with something more specific: “What handles this interaction?” or “Where is this animation state stored?”
The difference is enormous.
Learning not to trust the decompiler
Ghidra’s decompiler is incredibly useful, but it is still reconstructing high-level code from machine code.
It guesses types, structures and variable relationships. Sometimes those guesses are correct; sometimes they are close enough to be useful; sometimes they are completely wrong while looking perfectly reasonable.
That means the decompiler became a way of navigating the binary rather than an authority on what the original source code looked like.
If Ghidra suggested that a particular memory offset represented a field, I checked where that memory was read and written. If it suggested a type, I looked at how the value was actually used. If something didn’t make sense, I went back to the assembly.
A lot of reverse engineering ended up being exactly this: form a hypothesis, find evidence, test it, and adjust the hypothesis when the evidence disagrees.
The debugger is where the pieces come together
Static analysis can take you surprisingly far, but the debugger is where things become much easier to understand.
If I wanted to know what happened when a card was dropped, for example, I could set a breakpoint, perform the action in the original game and watch which functions executed and which values changed.
The same applied to animations, timers and other state transitions.
Something that looks ambiguous in a decompiler can become obvious when you watch the actual program change state.
It also revealed something I didn’t expect: much of the polish in these games comes from relatively simple mechanisms. Animation isn’t necessarily some giant framework with complicated mathematics behind it. A lot of the behaviour can come down to state, coordinates, timers, durations and carefully chosen constants.
The hard part isn’t necessarily implementing the mechanism. It’s discovering which mechanism the original developers chose.
Where AI fits into reverse engineering
AI became another useful tool during the process, but not as a replacement for the traditional tools.
I found it most useful when I already had evidence and needed help connecting the dots.
For example, I could provide a decompiled function together with relevant assembly, strings, memory offsets and observations from the debugger, then ask what interpretations were consistent with that evidence or what I should investigate next.
That can be surprisingly effective.
AI is very good at recognising patterns across information that would otherwise be scattered across several screens of analysis. It can suggest relationships, explain unfamiliar assembly patterns, help reconstruct likely structures and generate alternative hypotheses.
It can also confidently make things up.
That is probably the most important thing to understand when using AI for reverse engineering.
If an AI tells me that a field is an animation state, that is a hypothesis. I still need to verify it against the binary. If the debugger disagrees, the debugger wins. If the assembly disagrees, the AI is wrong.
The workflow I ended up with was essentially:
1 | Observe |
AI made the process faster, but the evidence still came from the binary.
Rebuilding it in Rust
Once I understood a piece of behaviour, I needed to implement it.
Rust worked well for this because the six games have very different rules but share a lot of infrastructure: cards, decks, boards, input handling, dragging, animation, rendering and state management.
Instead of implementing everything six times, I built common components and put the individual game logic on top.
That also made the reverse-engineering process easier. If several games appeared to use similar behaviour in the original binaries, I could implement that behaviour as shared infrastructure and see whether it worked across the different games.
The compiler was useful here too. Changing a shared type and immediately finding all the places affected by it is a much nicer failure mode than discovering much later that one game quietly diverged from the others.
The project ended up at roughly 26,000 lines of Rust, which is significantly more code than the original idea of “I’ll rebuild the Windows 7 games” makes you expect.
What I learned
The biggest thing I learned wasn’t a particular Ghidra feature or an assembly instruction.
It was how to approach an unfamiliar binary.
You don’t need to understand everything at once. Start with observable behaviour, narrow the question, gather evidence and build your understanding incrementally.
Traditional reverse-engineering tools give you the evidence. The debugger lets you test your theories. AI can help interpret the evidence, connect information and suggest where to look next. Your job is to keep all of those things grounded in what the program actually does.
That makes AI particularly interesting for reverse engineering. Used properly, it doesn’t remove the investigation; it reduces the amount of time spent staring at unrelated pieces of information and helps you explore hypotheses faster.
The important distinction is that AI should be part of the reasoning loop, not the source of truth.
The result
The project now contains Rust implementations of:
- Solitaire
- Spider
- FreeCell
- Hearts
- Minesweeper
- Chess
The source is available at git.nemanja.uk/nemanja/win7_games and is released under the Unlicense.
For me, the interesting part of the project was never simply reproducing six Windows games. It was having a concrete, understandable target through which I could learn reverse engineering, experiment with different tools, and figure out how AI can actually be integrated into the workflow without blindly trusting it.