When padding can be uncomfortable
For now, I'd like to talk about alignment and padding, and when that can be a pain in the ass. Forewarning: this is probably going to come off quite boring to anyone who's not either mental, a C++ coder, or studying comp. sci. With that disclaimer out of the way, here we go.
Seven Kingdoms, like many other games, has a number of resources for images, sound and other things. It stores these in '.RES' files. On startup, the game loads the .RES files into memory, so it can use the resources in them. A fairly straightforward process so far, but it gets slightly more complicated.
You see, .RES files in Seven Kingdoms have a custom file format. There are three main components to the resource file format. The first two bytes of the .RES indicate how many items are stored in the file. We'll refer to that as 'N'.
After that, there is 'N' times 13 bytes. The 13 bytes are for two items: first, the name of the element: 9 bytes. Second, the offset into the file where the resource's data is stored: 4 bytes. (obviously, to calculate the size of data to read, you use offset[itemindex] - offset[itemindex + 1]).
There is also a magical index entry at the end, storing the end offset of the blob to calculate the size of the final chunk.
After the index records, there is a giant blob of data. Pointer manipulation using the offset maths above is used to find which piece of data is where.
So, to recap on those three elements:
- 2 byte length (N)
- N * 13 byte 'table of contents' entries
- N * B bytes of blobs, where B is an arbitrary value for each item in the resource, it's unimportant.
Now that the description is out of the way, it's time to describe how this caused me grief: you see, in C++, structs may be arbitrarily padded for performance reasons (see http://en.wikipedia.org/wiki/Data_structure_alignment). It appears that VC6 did *not* do this, which meant that the following pseudocode worked fine:
read two bytes to gain number of items N
read N * sizeof(HeaderEntry) bytes to initialise N * sizeof(HeaderEntry) table of contents blocks already allocated
But, in VS2010, struct padding *is* performed, which meant that this didn't work.
A fairly simple struct, described above, looks like it would have 13 bytes. But, padded to the nearest multiple of 4, that's 16 bytes, meaning it was reading the first element fine (13 bytes), but then grabbed the first 3 bytes off the next element and jammed them in there. The name was fine, the pointer was fine, but the padding bytes in the struct royally raped things.
After scratching my head for some time as to why on earth I was getting such random results, I finally printed sizeof(HeaderEntry) and figured out why things were going to hell after the first entry, for obvious reasons.
#pragma pack(1) to save the day, and all was well in the world again. Obviously, this isn't an ideal solution, as it's still not really portable to other architectures: the only real way to write this is to read the full TOC into a string and parse off the bytes needed manually, but it works for the time being, which will do me just fine.
On to the next bug...