HiveBrain v1.2.0
Get Started
← Back to all entries
patterncMinor

Reverse engineering Darkstone game archives

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
reversedarkstonegameengineeringarchives

Problem

Reverse engineering old games is something I do every now and then. This time, I took a shot at this old RPG called Darkstone.

The bulk of the game's data is stored in archive files with the .MTF extension (not related to the Microsoft Tape Format). A partial description for this file format can be found here. The format uses a custom compression algorithm (I don't know much about compression, so maybe it has a standard name?), which is fairly simple. The previous link describes it, but as @JS1 noted in a comment, the description seems to get the order of bits wrong, the top six bits of a word are the count, the other 10 are the offset.

So I went on and wrote the following code to unpack an MTF archive into normal files. I used plain C this time. Let me know if this can be improved in any way. Performance was not the main concern, but It wouldn't be bad to make it faster if possible, though I don't want to sacrifice readability for that. I also tried to make it more or less like a library, in case someone wants to incorporate the code into another project.

mtf.h:

``
#ifndef DARKSTONE_MTF_H
#define DARKSTONE_MTF_H

#include
#include
#include

/* ========================================================
* DarkStone MTF game archive structures:
======================================================== /

typedef struct mtf_compressed_header {
uint8_t magic1; // (Apparently) always 0xAE (174) or 0xAF (175) for a compressed file.
uint8_t magic2; // (Apparently) always 0xBE (190) for a compressed file.
uint16_t unknown; // Unknown data. Seems to repeat a lot. We can decompressed without it anyways.
uint32_t compressedSize; // Advertised compressed size in byte of the entry.
uint32_t decompressedSize; // Decompressed size from
mtf_file_entry_t` is repeated here.
} mtf_compressed_header_t;

typedef struct mtf_file_entry {
char * filename; // Alloc

Solution

How large are these files?

My suggestion would be to load the entire compressed file into memory instead of using multiple fread calls.

If you are storing the decompressed contents in memory, surely you can also store the compressed file in memory as well. Most of your run time is spent fetching bytes with fread, so being able to replace those calls with direct memory accesses should improve performance quite a bit.

Another suggestion I have is to use memcpy instead of this loop:

for (int n = 0; n < count + 3; ++n) {
                *decompressedPtr = *(decompressedPtr - offset);
                ++decompressedPtr;
                --bytesLeft;
            }


memcpy is a standard way to copy blocks of memory, and most C libraries implement it with special processor instructions to speed it up.

Another coding style issue... instead of:

while (bytesLeft) {
  ...
  bytesLeft -= count;
  if (bytesLeft < 0) { ...error... }
}


I prefer:

while (bytesLeft > 0) {
  ...
  bytesLeft -= count;
}
if (bytesLeft < 0) { ...error... }


It's a safer way to implement the loop as the check bytesLeft > 0 is always performed. The way you've written it the check will get missed if someone uses continue somewhere in the loop body.

Code Snippets

for (int n = 0; n < count + 3; ++n) {
                *decompressedPtr = *(decompressedPtr - offset);
                ++decompressedPtr;
                --bytesLeft;
            }
while (bytesLeft) {
  ...
  bytesLeft -= count;
  if (bytesLeft < 0) { ...error... }
}
while (bytesLeft > 0) {
  ...
  bytesLeft -= count;
}
if (bytesLeft < 0) { ...error... }

Context

StackExchange Code Review Q#102329, answer score: 4

Revisions (0)

No revisions yet.