Parts
Part 1: Decoding the Gears of War 2 graphical settings.
Part 2: Writing the code to read and write the settings.
Introduction
In part 1 I showed you how I successfully decoded the binary settings of the Xbox 360 title Gears of War 2. This was the first step in my attempt to play the game without 2008 annoyances like bloom, depth of field and overly detailed textures that like to pop in. And don’t get me started on that FOV. The next step was to write an application that can read and write the settings. I dubbed the project Coalesced Inifier and it is released on GitHub. The following sections describe the development process.
A small recap: the settings are stored in a file called Coalesced_int.bin
and a hash of said file is stored in a text file called Xbox360TOC.txt
. The former is just a bunch of INI files stored in binary form, the latter basically just acts as a checksum to determine if something went wrong when reading the DVD.
Now that we have the format covered, let’s write a small tool to extract the files. For this project I chose Go because I wanted to get familiar with it, and because it compiles to a single binary I can actually release. Prior to this I have written maybe 100 lines of Go in a PR for an open source project so bear that in mind when you are disgusted by the code.
The code listings are abbreviated and don’t show all the error handling. For the full monty check out the project on GitHub.
Some libraries to help us out
It seems that every language I try has a novel approach to CLI argument parsing and Go is no exception. I, of course, tried to roll it myself, but it was very rudimentary. I then tried to make it nicer, but skill issues (and free time) are a thing, so I gave up and started looking for a library.
Another thing we need is the ability to parse an INI file. Now I remember INIs being very simple from ye olde days of Windows 98 and XP, perfectly editable in Notepad. Like many things in software it turns out INI files are extremely complicated to parse and any attempts at doing so will probably end in tears wasting a week trying to understand the 30 years worth of implicit convention that defines the format.
I ended up finding go-arg for argument parsing and ini.v1 for the INI file handling.
Writing the parser
Go structs look a lot like C structs which look a lot like the ones in the ImHex pattern language discussed in part 1. This made the translation fairly simple:
type BinaryIniKeyValue struct {
Key string
Value string
}
type BinaryIniSection struct {
Name string
Values []BinaryIniKeyValue
}
type BinaryIniFile struct {
Name string
Sections []BinaryIniSection
}
type BinaryCoalescedIniFiles struct {
fileCount uint32
Files []BinaryIniFile
}
Now that we have all the structs in order we need a couple of boilerplate-y things to handle the binary encoding in question. We need to be able to read big endian numbers, we need to be able to read nul-terminated UTF-16 strings and we need to read the string lengths as bitwise inverted big endian numbers. That’s a mouthful, but it’s basic IO/bit fiddling stuff.
Go has fairly nice support for bit fiddly stuff so reading a big endian number is easy. The string reading gets a little hairy:
|
|
Now that’s a piece of code you wouldn’t be happy to see in a code review, but it actually works. At line 4 we see the trivial reading of a big endian number.
Jumping to line 10 we see the handling of the 0xFFFFFF
encoding for “empty string”. This jumps at you as a WTF, that’s why it has a comment.
Lines 28-31 are the ones I am truly proud of. This is a for-loop that raw-dogs bytes in pairs into a UTF-16 rune. Before you start sending me e-mails or yelling at the screen, I did try to do this the right way with a UTF-16 reader thingy, but the array in question is apparently not what Go considers valid UTF-16. But I digress, the string is read fine and while it may not be the prettiest method on the planet, I am sure you’ve seen worse.
Now that we can read strings from the coalesced INI format we are actually good to go. Going bottom up we start by reading a key/value pair:
|
|
The weird if above is due to some weird escaping within the format, the rest is pretty straightforward. The method reads a single key/value pair from the stream. If you attempt to read a pair where there is none, you will most likely run out of bytes or memory. Now all we need to do is read the files and sections and then call this method for each key/value pair:
|
|
The above shows how to read an INI section. It is fairly simple and the interesting parts are in line 7 and 11. Now we almost have the entire file written, we just need to loop over all the sections and read them:
|
|
Reading the sections looks a lot like reading the values, we read a count and then loop over the count and read some values. In the above we do the same just for sections. The last thing we need to do is to loop over the file count and call readIniFile
:
|
|
Again it looks like the rest, read a count and loop over it. But that’s actually it. That reads all the settings from the game into the structs we wrote in the beginning of this post.
Writing the INI files
Once we have the INI files in memory we can just feed them to the INI library and write it to a file. The file names in the Gears of War 2 settings are prefixed with ..\
which we need to handle, otherwise it is straightforward.
|
|
And that’s it! We can now extract all the settings from Gears of War 2 into simple INI files on disk. If we run the tool we get:
./GearGame/Config/Xe-GearUI.ini
./GearGame/Config/Xe-GearGame.ini
./GearGame/Config/GearWeaponMP.ini
./GearGame/Config/GearPlaylist.ini
./GearGame/Config/GearCamera.ini
./GearGame/Config/GearPawn.ini
./GearGame/Config/Xe-GearEngine.ini
./GearGame/Config/GearAI.ini
./GearGame/Config/Xe-GearInput.ini
./GearGame/Config/GearPawnMP.ini
If we poke into GearGame/Config/GearAI.ini
we find the key EnemyDistance_Melee
we saw in part 1:
» grep -B 10 EnemyDistance_Melee GearGame/Config/GearAI.ini
[GearGame.GearAI]
MinDistBetweenMantles = 1024
EnemyDistance_Melee = 256.f
Converting the INI files to binary
Now that we have a way of peeking at the settings, we need to coalesce them back into the binary format. This consists of a couple of steps:
- Listing all the INI files in a folder structure
- Counting the files
- Reading all the INI files
- Writing the INI files as binary
This is not that complicated and is mostly IO stuff which I will not bore you with. The code is up on GitHub if you hate yourself.
The Coalesced Inifier CLI tool
The end product of this fine work is a small CLI tool that can unpack the INI files from Gears of War 2 and pack them again:
./coalesced-inifier unpack -i /path/to/Coalesced_int.bin -o my-output-dir -g gow2
And packing them again:
./coalesced-inifier pack -i my-output-dir -o /path/to/coalesced_int.bin -g gow2
The last command will pack the INI files and print out the length and hash of the coalesced file:
[...]
Packing 'my-output-dir/GearGame/Localization/INT/locust_skorge_chatter.INT' as '..\GearGame\Localization\INT\locust_skorge_chatter.INT'... done.
Packing 'my-output-dir/GearGame/Localization/INT/locust_theron_chatter.INT' as '..\GearGame\Localization\INT\locust_theron_chatter.INT'... done.
File length: 1305998
File hash: 3cab0a4f2237b00875ab02061bdd46a6
Successfully packed 63 files to 'coalesced.bin'.
Conclusion
This project started as a hunch but quickly turned into a working piece of whisky-fueled code that is actually useful. It enables changing engine and graphics settings of Gears of War 2 (and probably more UE3 games).
While fiddling with the settings I found some really amusing configurations such as Marcus running with supersonic speeds and locust rifles having 1000 bullets.
Next time we will look into actually changing the graphical settings and taking a look at their effect on the game using a cheap HDMI capture card.
Thanks for reading.
/J