//version 2.0 (20061001)
//by thakis
//decompresses compressed metroid prime files.
//metroid prime 1 uses zlib compression,
//metroid prime 2 uses segmented LZO1x (thanks to SkippyJr
// for pointing this out)
//zlib decompression is handled by http://www.zlib.net/,
//LZO code (lzo.h/c) was taken from ffmpegs libavcodec.
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <string>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "zlib.h"
#include "lzo.h"
using namespace std;
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
char* fname;
vector<u8> dst;
inline void toWORD(u16& d)
{
u8 w1 = d & 0xff;
u8 w2 = (d >> 8) & 0xff;
d = (w1 << 8) | w2;
}
inline void toDWORD(u32& d)
{
u8 w1 = d & 0xff;
u8 w2 = (d >> 8) & 0xff;
u8 w3 = (d >> 16) & 0xff;
u8 w4 = d >> 24;
d = (w1 << 24) | (w2 << 16) | (w3 << 8) | w4;
}
#pragma pack(push, 1)
struct HeaderMP1_2
{
u32 uncompSize;
//mp1 has 0x78da (zlib stream with maximal compression)
//mp2 has something else :-P (lzo doesn't seem to have a header)
u16 compressionMethod;
};
struct HeaderMP3
{
char id[4];
u32 unk; // version maybe?
u32 unk2; // Hash?
u32 uncompSize;
};
#pragma pack(pop)
int decompressZLib(u8* source, int sourceSize, u8* dest, int dstSize)
{
int ret;
z_stream strm;
// allocate decompression state
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = sourceSize;
strm.next_in = source;
ret = inflateInit(&strm);
if (ret != Z_OK)
return ret;
//decompress input
strm.avail_out = dstSize;
strm.next_out = dest;
ret = inflate(&strm, Z_NO_FLUSH);
assert(ret != Z_STREAM_ERROR); /* state not clobbered */
switch (ret)
{
case Z_NEED_DICT:
ret = Z_DATA_ERROR; /* and fall through */
case Z_DATA_ERROR:
case Z_MEM_ERROR:
(void)inflateEnd(&strm);
return ret;
}
assert(strm.avail_out == 0);
assert(ret == Z_STREAM_END);
// clean up and return
(void)inflateEnd(&strm);
return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
}
int decompressLZO(u8* source, int sourceSize, u8* dest, u32& dstSize)
{
int size = dstSize;
int result = lzo1x_decode(dest, &size, source, &sourceSize);
dstSize = size;
return result;
}
void WriteFile(FILE * inFile, u32 uncompSize, int size = 0)
{
if (size == 0)
{
u32 bytesLeft = uncompSize;
while (bytesLeft > 0)
{
u16 segmentSize;
fread(&segmentSize, sizeof(segmentSize), 1, inFile);
toWORD(segmentSize);
vector<u8> src(segmentSize);
fread(&src[0], 1, segmentSize, inFile);
int result = decompressLZO(&src[0], segmentSize,
&dst[uncompSize - bytesLeft], bytesLeft);
if ((result & LZO_ERROR) != 0)
{
cerr << "Failed to decompress \'" << fname << "\'" << endl;
system("pause");
return;
}
}
}
else
{
vector<u8> src(size - 4);
fread(&src[0], 1, size - 4, inFile);
fclose(inFile);
if (decompressZLib(&src[0], size - 4, &dst[0], uncompSize) != Z_OK)
{
cerr << "Failed to decompress \'" << fname << "\'" << endl;
return;
}
}
//write buffer to output file
string inName = fname, outName;
string::size_type pos = inName.rfind('\\');
if (pos != string::npos)
outName = inName.substr(0, pos + 1) + "0" + inName.substr(pos + 1);
else
outName = "0" + inName;
FILE* outFile = fopen(outName.c_str(), "wb");
if (outFile == NULL)
return;
fwrite(&dst[0], 1, uncompSize, outFile);
fclose(outFile);
}
void decompressMP1_2()
{
FILE* inFile = fopen(fname, "rb");
//get file size
fseek(inFile, 0, SEEK_END);
int size = ftell(inFile);
fseek(inFile, 0, SEEK_SET);
//read file
HeaderMP1_2 h;
fread(&h, sizeof(h), 1, inFile);
toDWORD(h.uncompSize);
toWORD(h.compressionMethod);
fseek(inFile, -2, SEEK_CUR);
//decompress to buffer
dst.resize(h.uncompSize);
if (h.compressionMethod == 0x78da)
WriteFile(inFile, h.uncompSize, size);
else
WriteFile(inFile, h.uncompSize);
}
void decompressMP3()
{
FILE* inFile = fopen(fname, "rb");
HeaderMP3 h;
fread(&h, sizeof(h), 1, inFile);
toDWORD(h.unk);
toDWORD(h.unk2);
toDWORD(h.uncompSize);
dst.resize(h.uncompSize);
WriteFile(inFile, h.uncompSize);
}
int main(int argc, char* argv[])
{
if (argc < 2)
return EXIT_FAILURE;
FILE* inFile = fopen(argv[1], "rb");
if (inFile == NULL)
return EXIT_FAILURE;
fname = argv[1];
u16 tmp;
fread(&tmp, sizeof(tmp), 1, inFile);
toWORD(tmp);
fclose(inFile);
switch (tmp)
{
case 0x434d:
decompressMP3();
break;
default:
decompressMP1_2();
break;
}
return EXIT_SUCCESS;
}