TLDR: I used those chirstmas days to generate a pack of the same Lua scripts of the game but with bytecode generated on my pc unsuccessfully (npacking/repacking worked).
During the first lockdown in Italy I worked on various things around Reverse engineering because it was letting me to study and hack without any big distraction and at same time on learning new things.
I ported a retro gaming tool rom patcher from BCX-Basic to C++ and modded a bit and a gameboy advance rom patcher from visual c++ 97 to modern C++ with a new Qt gui. Just because I was bored at home and I wanted to learn new things like C++ and at the same time get fun with retrogaming on Linux (there aren’t so many tools for this system).
So with this involvement in retrogaming I bought this year my firsts console after the game color years ago (when I was a kid9, a Xbox360 and a PlayStation 3 (I did a proxy for Linux to workaround the firmware version checking).
Anyway I am playing a lot of games that I missed during the years and in this period I like a lot The Saboteur game. A GTA-like but in 194X with nazi during the WW2, with less car rides and a bit of Assassin’s Creed through the city. You need to destroy towers and nazi checkpoints in all the city and kills some kind of enemies and other things. Also, the company creator of the game after releasing it gone in bankrupty, so they didn’t release new DLCs.
Some linux explanations
Just in case you are not a dev or a Linux user:
- Wine is a program that let on Unix systems, so also OSX, to run Windows software including games
- Bash is the scripting languages mose used on Linux
- I use Okteta as Hex Editor and vbindiff to compare 2 different binaries
- Virtual machine is a way to virtualize an entire operative systems in another one
- Luac is Lua compiler to bytecode
I am playing since a month, but I saw that would be interesting if I can hack the game and change some stats, like the NPC kills or unlock all the perks. The first experiment was with a Xbox360 trainer that worked for just that needs but not mine. I investigated a bit in the savegame, excluding the issue that is a Xbox360 savegame signed but with a tool like Horizon is possible to unsign and extract it. My investigation showed that the savegame unsign just remove a lot of bytes in the end but don’t affect the savegame, so I can investigate it further without a Windows Virtual machine running.
My first test was to save, kill a NPC and save again. Later compare the savegame to see if the counter is changed you can download both savegames here. At first glance there are a lot of strings, it is saving the last mission of a specific NPC (the one that assign to you quests) but the rest is a lot of garbage. My idea is that the rest of data is compressed or encrypted to save all the positions of stuff destroyed around the city and also the stats.
To me the data changed in the savegames is the date/time and the NPC kill, maybe also the ammo status (I was running the trainer, so it was infinite).
Those are the differences between the 2 savegames, not a lot of things but is not easy to understand what that content means.
So I abandoned my idea for a savegame editor, also because the only one tool that let you to change the savegame of that game is closed source also if written in .NET. The tool was obfuscated with DNGuard HVM around 2017 the time of the last release and the unpacker for that obfuscator doesn’t cover that period as it was released previously. I asked if it was possible to get the source code of the tool without any hope (the tool crash to me with my savegame unchanged). Also, this tool let you to edit the savegame for a lot of games but is completely closed source, it will be interesting to see how to patch all the save games for xbox360 because it can offer hints for the same game in other platforms.
Also, my first delusion it was that a lot of the savegame editor for computer/xbox and so on are not open source, so you cannot learn from them how they work, it is very sad but seems that they are afraid that some stole their code, also after 11 years (Saboteur was released in 2009).
The modding community tools
The next step was to see if there was already something around. So the game is written also in a proprietary Lua 5.1 version and there are tools that let you to repack the scripts.
For the computer version there is Luap Explorer and Lua Hook. About the first one, let’s wait a minute. The second tool let to open a Lua terminal and run custom codes, so it should be around a dev community, it is on Discord and Facebook.
Looking the Facebook group posts there is a lot in this game, from code snippet to change some behaviour in the game and show unused cars or incomplete missions as example. Also, on PC it is possible to add custom Lua code by editing a DLC file, so I said maybe in the Xbox360 is the same!
So on the pc version there is a
LuaScripts.luap instead on xbox
LuaMissions.luap. The first 2 have different sizes but probably because on xbox some stuff was put on the other file.
Inspecting the 2 files there are a lot of strings and code, so I tried the pc tools on Xbox files, but it wasn’t working. The issue to me was that Luap Explorer is written in C# for windows but there was the source code, also that tool was ported as quickbms script.
QuickBMS is a tool with a custom scripting language created to unpack/pack game assets packages, and there are scripts for a ton of game assets and also for The Saboteur. At the same time quickbms there is also for Linux, so I started investigating the LuaScripts file on both the version to find the difference (excluding the size) and hacking with quickbms.
Here you can see the difference from an Hex editor between the PC version and Xbox360. After some experiments I understood how works the package, with the help of the quickbms original script.
I am sharing here as reference:
get FILES long for i = 0 < FILES getdstring HASH 8 get OFFSET long get SIZE long get XSIZE long get DUMMY byte savepos TMP goto OFFSET getdstring DUMMY 12 get NAMESZ long getdstring NAME NAMESZ string NAME | ":" goto TMP log NAME OFFSET SIZE next i
As we can see there are some bytes with the number of file inside the package, for any files there are some data, some are dummy and others are the offset where the content start and the size of the file. Also it is possible to get the file name because the files are Lua file converted to bytecode and that file save the path (that the dev want to be used, I understood that after) in the file itself.
Anyway as the size of the package was different probably also the file amount was different so when I got 175 instead of 321 (the pc version) I was thinking that I was on the right way.
After a lot of testing, counting manually and converting the Hex values to integers I was able to extract also the Xbox360 version. They tried to create a different version, and they reverse some bytes but not all of them as you can see in the QuickBMS script I did:
get FILES long reverselong FILES for i = 0 < FILES getdstring HASH 8 get OFFSET long reverselong OFFSET get SIZE long reverselong SIZE get XSIZE long reverselong XSIZE get DUMMY byte savepos TMP goto OFFSET set NAMESZ 120 # fixed value because the data is not consistent getdstring DUMMY 17 getdstring NAME NAMESZ string NAME | ":" goto TMP log NAME OFFSET SIZE next i
The other difference are that the position of the file is different, less dummy data initially and to be able to get the file name I used a fixed value, as quickbms will do a search anyway (also because it wasn’t consistent in my tests).
Now that I was able to unpack and pack again with the same files (the game works and the file generated is the same) I needed to try to gain the original Lua code from the bytecode and repack everything. Something that is already available for PC with LuapExplorer.
So today I wrote a suite of Bash scripts to decompress/compress and upload on my Xbox360 this file.
Yes it is a project of the lasts few days.
The suite is on GitHub (they are bash scripts files so it is easy to understand what everything does) but I want to add some other information about my discoveries.
- Some files if converted as bytecode are different from the original from extraction
- AggroSpawner.lua and CoDSpawner.lua on repacking I get an alert from QuickBMS that says that the size is different
- LuapExplorer in the package include Lua 5.1.4 instead I am using 5.1.5 (but testing showed no difference in the bytecode generated)
So for those cases I chose to not convert these 3 files to original Lua and keep the luac for repacking. I want to be able to run the original Lua code but with a new bytecode, later I will see in Quickbms how to repack files with different sizes.
Another issue was that the Luac keep a custom path that between the pc/xbox version is somehow different as you can see above in the previous screenshot. So after a bit I saw that I cannot force the Linux luac tool to use a windows-like path.
I chose the Wine way and with a bash script I generate a batch command to generate a bytecode in a windows way. The use of batch help also on speed because don’t require to start the wine server for any file but just one.
After all this work of decompression and compression, there is the install tool that downloads the various tools and the script that upload the modded file via FTP on my xbox in the game folder, ready to be tested.
Other information is that Xenia, the most famous Xbox360 emulator doesn’t support well that game and also is not easy to import Xbox360 savegames, so I wasn’t able to do some tests.
Anyway reading the Lua code offered some hints that the Lua proprietary version used has some custom modules that the
saveload class that uses
tables (I don’t know Lua also if with NeoVim I am doing some practice) and share those values from Lua, and they will be saved.
Basically I can patch the lua code to change this data and on saving the game force the perks and other statistics on the numbers I want.
Luap and Fatal Crashes
We are on the sad part of the story and of an hour ago to me (on writing time).
Repacking the files with those exclusions generate a file Luap file that is different from the original because the lua bytecode is different. So I did some testings to get the same.
I discovered in that way the original bytecode includes the debug stuff because if I strip those the file size is more lower.
Also using luac 5.1.5 and 5.1.4 for Windows create a similar file but those release are from 2014/2015 and the versions like 5.1.1/5.1.0 was released around 2006/2007 so probably they was used on this game development. As today build of this lua versions are not avalaible online so i cannot do some tests and see if I can generate the same bytecode.
Comparing for the same file between the computer/xbox version the bytecode is the same:
So with this files generated the Xbox game crash after loading the savegame, with the original file of course doesn’t crash.
Also using wine or wine64 doesn’t change the bytecode generated, this because on Lua documentation is written that the bytecode can change by system.
Next steps on reverse engineering should be:
- Use a real Windows instance and see if luac 5.1.5 generates the same bytecode for the same file
- Compile for windows an old luac version to see if generates the same bytecode (lua versions prior 5.1.4 for windows are not avalaible)
- Test on pc version if the luac 5.1.5 version of bytecode is different (to me is different but doesn’t crash) otherwise the LuapExplorer would add the same issue (it is using the 5.1.4 version).
Now I am tired and tomorrow I am back to work so I maybe I can look on that in the next days but i open to help and other ideas.
Looking also the errors reported by luac probably the lua version used was different because of that errors in those files. I am wondering if the changes in the bytecode are there because on the Lua proprietary version or just because it was used an old version?
Maybe the bytecode included is creating some issues to the intepreter in the game but on Xbox there is no error log also if a Fatal Crash happens (maybe I can get it somehow).
What I learned? Well how a custom package can be extracted/repacked and how to use Wine from a Bash script. Also that the modding community on Discord is very nice and it is possible to find a lot of information from old discussions. The important part that everything is kept publicly and not secret…
You can find me on Telegram, Discord, Reddit, Twitter and so on just check with Mte90 nickname or on the blue sidebar on the right.