Baggs provides a masterclass in reverse engineering by deconstructing how a single logic error in state persistence can permanently break a game's architecture. It is a rare, high-quality technical autopsy that values deep structural analysis over superficial bug fixing.
Deep Dive
Prerequisite Knowledge
- No data available.
Where to go next
- No data available.
Deep Dive
Why Saving This Game Breaks It ForeverAdded:
Imagine, if you will, playing a game, saving your progress, and then when you come back to it, it no longer works. No crashes, no error messages, just a puzzle that suddenly refuses to work.
When players complained to the developers about this game-breaking bug, the official response was, "You can't expect patches for a 12-year-old game.
That's just not feasible."
Infeasible, maybe. Fun, certainly.
Because deep inside the game's engine, hidden behind custom binary formats and encrypted save files, the cause of this unfixable bug is quite simple. So, let's reverse engineer this game, patch its engine, and fix the bug the developers gave up on. Let's take a step back. I get a lot of emails and messages about broken games, but this one caught my eye. It's about Deponia, a point-and-click adventure game, and the email reads, "There's this board with magnets on it, which you need to move to solve one of the puzzles. If you save the game and then reload, you can suddenly no longer move the magnets.
This describes a soft lock, where the game is in such a state that no meaningful can be made. This differs to the usual crash box I look at. I like a good crash. I can attach a debugger, inspect state, and generally start pulling on threads until the mystery unravels. But a soft lock is a different beast. The game is running, but the state is somehow invalid given the game's rules and restrictions. So, we'll need to peer deep into the innards of the game to see what's what. So, I purchased it for the low low price of free. I was also linked to some Steam community posts where other people have been complaining about the problem, and the developers even responded, "You can't expect patches fixes for 12-year-old game. That's just not feasible for no one."
Well, that might be fair given that the game is free, but 2 days after I purchased it, the price went up to ยฃ6.99. So, you are still making money off this. To their credit, they do provide save files on their website, which would mean you can skip the puzzle, but none of that sits right with me. Let's see if we can help the developers out with their 12-year-old bug. I was kindly given a save file which jumps me straight to the issue.
So, let's slap that in and fire up the game. So, the first thing is the splash screen for the Visionaire Studio, an engine for making point-and-click adventure games. If the bug is deep inside this engine, then I can understand why the game devs weren't able to fix it. Can confirm, magnets do not move. Let's start low-tech, poking around the game files. There's a config file which suggests all logging is enabled, and indeed, a log file with all logging in it. Looking through this, we can see event left click triggered on object obj magnet 1B.
But, if we click on more of the grid, we see something interesting. Each cell produces a log message with a unique ID.
The obj magnet string ranges from 1A to 6I, which covers each cell in the 6x9 grid. There's no separate log message when you click on a cell with a magnet.
Without playing all the way through, it's hard to know what state the game should be in, but from another post, it says that after the magnet puzzle is solved, the three chaps in the town hall should be gone. But if we walk over there, they are still there.
I think the first step is to figure out how left clicks are handled, especially as they trigger a log message. We can see from the game files that it uses SDL, an open-source and cross-platform hardware abstraction layer that's used by a lot of games to do windowing and event handling. So, let's get this into Ghidra, an open-source disassembler and decompiler. Those log messages don't appear in Ghidra, but I also can't attach a debugger or trace to find where the logging is coming from. I should have thought about this before, but the game probably has Steam DRM. Luckily, there's a free tool to strip this out.
Okay, now using API Monitor, I can see the calls to write file, but a bit more digging and we can see the game uses OutputDebugStringW to do the actual logging. The click event log is coming from here. This is possibly some event dispatcher, as it's called constantly and the function's just a big ball of mud. Thinking about it, though, we know we get a click event for each grid position and not the magnets themselves, so presumably the mouse picking and object selection has already been done by the time we see the log message. We're observing the results of the bug. We can work off what we already know, SDL. The game references SDL_PumpEvents, which allows you to inspect the queued OS events sent to the process. After the game calls this, it does a big switch statement to handle the different types of events. I wanted to experiment with the Ghidra MCP, so I pointed it at that function, gave it some loose context about using SDL, and told it to rename and retype the variables. It then automatically went and got the header files from Git and updated the disassembly. It probably saved me a few hours of work.
I spent some time just digging around, but nothing particularly fruitful. Let's attack this from the other angle. We know the magnets are rendered, so let's try and find out where the code is and see if we can discover some state. SDL does have 2D rendering primitives, but I can't see any of those functions used.
What we can do is attach RenderDoc, a GPU debugging program, and dump a frame of the game. So, it uses DirectX 11, and if we dig through the various calls, we can see where it renders the magnets.
Whilst it does bind what looks like a matrix via constant buffer, each sprite has its own buffer with its own dimensions encoded. Also, for some reason, the buffer is 200 kilobytes, which is a lot of GPU space for four vertices. It also maps and unmaps the buffer before being set. Mapping, in DX11 terms, means getting a pointer to CPU memory that you can write data to that will then get transferred to the GPU.
So, by combining this knowledge with the call stack that RenderDoc provided, we can see that the four vertices are copied here. Digging around here, and I think this function calculates the bounds of the sprite, which will be used for rendering and potentially clicking to section. Just exploring the memory bit, we can see that an object name lives at this offset. So, if we use x64dbg to log, then we can dump them all. In addition to that, we can dump what I think is the sprite dimensions.
We can see it calculating something for all the grid cells, as well as some other random objects. Is {dash} {dash} {dash} {dash} interaction object {dash} {dash} {dash} {dash} important?
Okay, I feel a bit lost at this point. I need some known good state to compare to, so I have no real choice but to play the game. I've started up the game in a VM. This will allow me to snapshot the state when we reach the magnet section and restore it as often as we need.
So, hopefully, I'll only need to play this once. I've also found a guide, so let's just grind this out.
>> [music] [music] >> Okay, we're here, and I can see the puzzle works correctly. Interestingly, there is no extra log lines for clicking, but it does log when we move the magnets.
I suspect now the issue lies with how the save file is loaded. Either some state is not correctly persisted into the save file, or it's not correctly restored.
The save file starts with some magic number that presumably relates to the engine, but has a lot of what looks like either compressed or encrypted values at the end.
Digging around and we end up here, which I believe loads the file. It uses some hardcoded password, save game password 30, to decrypt. I was hoping this would be human-readable, as I saw the string vtp_savedata.xml, but that was just sent to try me. It's certainly a custom binary format. I hate reversing custom binary formats. It's a massive amount of work. I tried using the LLM again, but it really just likes to dereference random pointers and burn tokens.
However, by pure chance, a new thread to pull appeared. I was just scrolling through the strings that contain the word debug to see if there was anything useful, and I saw the string mobdebug.
Mobdebug is a remote debugger for Lua, and if we follow the strings a bit, we can see this function sets it up. I guess it makes sense that the engine uses Lua to allow developers to add custom functionality. But if we put a breakpoint on this function, it's never hit.
That function is called from here, though, which looks like it's registering a bunch of Lua handlers.
I suspect that until some Lua code in the game actually runs, require mobdebug.start, then the debugger is never set up. So, back to API Monitor, and we can search for all calls to CreateFile, which is the Win32 function for opening a file. And luckily for us, right as the game starts, it loads voices_en.lua.
Let's just modify that to start the mobdebug server. Amazingly, it's actually hit my breakpoint. Okay, so how do we actually interact with that? Well, we can use ZeroBrain, a Lua IDE that supports mobdebug. Let's restart the game, and yes, we're attached. We can now mess around with the Lua VM embedded in the game. Of course, there's one slight issue. In order to get this set up in the known good state, we need to restart the game, but if we reload a save file, then we always end up in the broken state.
So, hopefully, I'll only need to play this once.
>> [music] >> Right, new snapshot on good state with attached Lua debugger. I need to discount this idea, as I had several issues with the tooling. One, it wouldn't accept multi-line Lua snippets in the console, and two, nothing was being printed.
Turns out, I need to wrap everything in a single-line immediately invoked function, and also check the log file, as that's where the output goes.
Definitely a skill issue. First thing's first, let's dump all the Lua globals.
There's quite a lot, but there's some that include the word "spring", which is German for blast, which in turn is related to the puzzle we're stuck on.
Let's write some Lua to filter all these out.
Okay, so we've got springplan, springplan_1018_zela, springplan_1018_init, and springplan_1018_erster_alvrof.
I'm interested in the last two, as they're functions. As an aside, this has been another good use of the LLM. I'm not a Lua expert, but telling the Oracle my intent and getting it to crap out a one-liner in an immediately invocable function has been a real time-saver.
Anyway, we can't just dump the original Lua code as by this point it's been turned into bytecode. We could try and find the C++ code that loads the Lua script and dump it from there, but we can actually solve this all from the Lua layer. Lua lets us dump the bytecode for a function. We can then use some open-source tools to turn that back into Lua. So, our init function becomes this and our erste alpha function becomes this. This is really powerful. We can rip out the original Lua code from the game as it's running. Okay, so it looks like init loops over the six-by-nine grid and checks if some condition is set. If it is, then it sets an animation position.
The other function sets up some initial state including positions and a boolean value. Let's work out what this conditions variable is. Using pairs to iterate over the object gives us almost nothing, just some random integer ID.
This is almost certainly backed by C++ object and uses a gateway to interact with the real game engine. However, the bracket notation still works. It'll call into the C++, but it does return some valid output. The same goes for the active animation. Given this knowledge, let's dump the condition values for the various magnet alpha objects in the good state. Okay, we can see the first three are true and the rest are err, which means they don't exist. And in the bad state, after loading it's all err. So, from this we can infer that after loading a game, the state information is lost. The implication of this is that when the init function is called, this if will always fail and therefore never initialize anything. Interestingly, the Lua functions we decompiled call something called get object. Now, this is backed by C++ function, but every time we try and call it with one of the arguments they've used, it fails. Why can't we invoke a function they're using in their own scripts?
Well, just using the table index directly actually works. I'm not really sure why this is, but after a bit more experimenting, it's not actually an issue. In fact, the fix is quite simple.
We add some more Lua to the language file we know gets executed at the start to reset the state to what it's meant to be and ensure the erste alpha function is called.
>> [music] >> Works on my machine. I emailed this over to the chap who suggested this and he said it works for him as well. It turns out there's a fan-made patch for this game which fixes this and a host of other issues. It's therefore technically better than mine, but it isn't open source, so you have to be okay with running a random XE from the internet.
Dealer's choice.
This was fun, but there's another game that crashes when you talk to a specific NPC. And if you want to see how I fixed that, then check out this next video.
Related Videos
Agentforce NOW AMA: Build with React and Salesforce Multi-Framework
SalesforceDevs
490 viewsโข2026-05-28
How agent o11y differs from traditional o11y โ Phil Hetzel, Braintrust
aiDotEngineer
450 viewsโข2026-05-28
WEB TECHNOLOGIES UNIT-2 | Degree 4th sem BCOM Computers web technologies unit-2 full explanation๐ฏโ
LearnwithSahera
1K viewsโข2026-05-29
More tests are always better? How to use AI to identify tests that bring little value
Alliance4Qualification
335 viewsโข2026-05-29
Search Algorithms Explained in 60 Seconds! ๐ค๐จ
samarthtuliofficial
218 viewsโข2026-06-01
People of Game of Thrones using JavaScript DOM
AltCampus
296 viewsโข2026-05-30
Introduction to Problem Solving Part - 1 | Lecture 1 | Intermediate DSA
ascensionix
107 viewsโข2026-05-29
So What's Odin Lang Even Good For
TechOverTea
131 viewsโข2026-06-01











