It looks like a console VOL decompress/compressor already exists in the repository (Outpost2SVN\GameResources\VolDecompress). The code looks clean and easy to read. While reviewing it, I realized void main(int argc, char **argv) contains a pointer to a pointer (**)... I had to look that up.
Anyways, the program is incomplete:
void OutputUsageMessage()
{
// Print usage info
cout << endl;
cout << "OP2Decomp - The Outpost 2 decompressor/unpacker for VOL and CLM files" << endl;
cout << endl;
cout << "To extract all files from an archive:" << endl;
cout << "OP2Decomp [filename]" << endl;
cout << endl;
cout << "To pack a list of files into an archive:" << endl;
cout << "... not sure on syntax yet (but code exists!)" << endl;
}
It looks like at some point we should probably finish up the console interface of the application and separate the backend logic into a library project. Then the OP2MapImager could just reuse this VOL library instead of re-doing all the VOL decompress code. It looks like a lot of work went into writing the code, including a Huffman Tree for compression. I also had to lookup what a Huffman Tree is.
More generally, I would like new code to allow for easy cross platform extraction of game resources. Many of the current tools are Windows specific, and have extra dependencies that need to be installed. I'd like standalone utilities available with minimal dependencies, to ensure stuff just works wherever. Ultimately, I want to make it easy for anyone to access game resources, either through library code, or without needing to program anything, in easy to use standard formats. That should allow people to do whatever they want with the resources, creating new utilities, editing resources, or extracting resources for use in new projects or remakes. I'm hoping easy access to the game resources might spur on new projects.
Since we are currently working in Visual Studio, I think the code can only be easily compiled for use in Windows. I'm guessing it should be pretty easy to use through Wine on Linux though?
If we keep the applications simple enough and generic, the files could be loaded into a new solution in an IDE that supports a Linux or Macintosh compiler and then compiled for use. Is that sufficient?
I don't know a lot about other compilers, but if we used something else like Vim or EMACs, would that be trivial to switch targets and compile natively for Windows, Linux, or Macintosh. Besides a couple of Code::Blocks projects, I believe everything in the repo right now is Visual Studio.
I'm not well versed on cross platform development.
void main(int argc, char **argv)
The argc is the number (count) of command line arguments. The argv is the actual command line arguments. It is an array of strings, where each string is an array of chars. As each array decays to a pointer, it's simply written as a double pointer.
It might not be a bad idea to finish that project. I'd forgotten about that note about not having a command line interface to repack VOL files.
It's possible the compiled code might run on Linux under Wine. However, the code can't currently be compiled under Linux, which creates a significant barrier to development for anyone using Linux. You could run a compiler in a VM, but this is still an extra step that doesn't integrate well with tooling.
The problem doesn't come from using Visual Studio. That's just an IDE, which is really just a glorified text editor. The problem comes from the code using platform specific features through direct API calls.
The files with platform specific code are CArchiveFile, CVolFile, and CClmFile. Within them, you'll notice "#include <windows.h>". They also make use of the Windows specific HANDLE type, for referencing file handles, along with associated functions: CreateFileA, MoveFileA, DeleteFileA, CreateFileMapping, MapViewOfFile, and WriteFile. To make the code platform independent, those files would need to be rewritten to replace those occurrences.
The files CAdaptHuffTree, CHuffLZ, CBitStream, and even Main.cpp don't appear to use platform specific features themselves, they just rely on the other files that do.
Then there's getting it all to compile in one easy command on a new platform. You'd need a replacement for the Visual Studio project file that basically says what your sources files are, and how to feed them through a compiler. Typically this is done with a Makefile. This is a small issue. Visual Studio already has a built in tool to output a suitable Makefile.
In regards to the extra data at the end of the Prt file, it is not loaded or used by Outpost 2. We have found extra unused data. This is very similar to the tile group data at the end of map files. The game never tries to load it. There is no code to analyse how the data is loaded or used, because it isn't loaded or used.
I spent some time examining the code we were looking at in OllyDbg. Turns out the code at the end of the Prt loading method corresponds to the UnknownContainer struct array, defined in Animations.h in the OP2Utility project. That particular code corresponds to data that is already being loaded, we just don't yet know what to do with that part yet.
The Prt loading method in Outpost 2 closes the stream and ends after loading the animation array. It never touches the data after that.
The only way to determine what that data means is to analyse the data itself.
In terms of the structure of the UnknownContainer, I strongly believe it represents a Rect. More specifically, I believe it represents two Point objects.
The reason for this conclusion is the peculiar and somewhat nonsensical stream error checks around that code, where it reads the first 4 bytes, and then conditionally loads the next 4 bytes, provided there were no errors from reading the first 4 bytes. As the only expected error might be end of file, it could have just tried to read all 8 bytes together.
This error checking behavior is consistent for other parts of the code that process Point structs. In particular, the clipRect struct exhibits the exact same checks. My guess is, the game's code had an inline method which read the x and y fields of the Point object separately, and returned early if there was a failure. It may have even returned an error code to indicate success or failure. Though the problem with returning error codes is they are inconvenient to check, and make a mess of the control flow of the program. As such, they are frequently ignored. That may explain the pattern in the code, which seems to be repeated for each Point or Rect that is loaded.
Possible example code:
/* Returns true if read was successful */
bool StreamIO::Read(void* buffer, std::size_t size);
/* Returns true if read was successful */
inline bool StreamIO::Read(Point& p) {
/* Note: The && is short-circuiting */
/* It only evaluates the right hand side if the left hand side is true */
return Read(p.x) && Read(p.y);
}
/* Returns true if read was successful */
/* ... or get lazy, not check things, and return void */
inline void StreamIO::Read(Rect& rect) {
Read(rect.p1); /* Ignore return value */
Read(rect.p2); /* Ignore return value */
}
The Rect class doesn't seem to use the same short-circuiting behaviour. If one point fails to load, it still tries to load the second point.