* Initial commit

This commit is contained in:
Phillip Stephens 2015-04-18 23:26:07 -07:00
commit 0b8823356c
4 changed files with 350 additions and 0 deletions

10
RetroCommon.pri Normal file
View File

@ -0,0 +1,10 @@
INCLUDEPATH += $$PWD/include
include(../libSquish/libSquish.pri)
HEADERS += \
$$PWD/include/RetroCommon.hpp
SOURCES += \
$$PWD/src/RetroCommon.cpp \
$$PWD/src/MREADecompress.cpp

21
include/RetroCommon.hpp Normal file
View File

@ -0,0 +1,21 @@
#ifndef RETROCOMMON_HPP
#define RETROCOMMON_HPP
#include <Athena/IStreamWriter.hpp>
#include <Athena/IStreamReader.hpp>
namespace aIO = Athena::io;
enum class EPAKSection
{
STRG = 0x53545247,
RSHD = 0x52534844,
DATA = 0x44415441
};
void decompressData(aIO::IStreamWriter& outbuf, const atUint8* srcData, atUint32 srcLength, atInt32 uncompressedLength);
void decompressFile(aIO::IStreamWriter& outbuf, const atUint8* srcData, atUint32 srcLength);
bool decompressMREA(aIO::IStreamReader& in, aIO::IStreamWriter& out);
#endif // RETROCOMMON_HPP

212
src/MREADecompress.cpp Normal file
View File

@ -0,0 +1,212 @@
#include "RetroCommon.hpp"
#include <Athena/Compression.hpp>
#include <memory.h>
enum MREAVersion
{
MetroidPrimeDemo = 0xC,
MetroidPrime1 = 0x0F,
MetroidPrime2 = 0x19,
MetroidPrime3 = 0x1E,
DKCR = 0x20
};
struct CompressedBlockInfo
{
atUint32 blockSize;
atUint32 dataSize;
atUint32 dataCompSize;
atUint32 sectionCount; // How many actual sections specified by header.sectionCount are in this compressed bock
};
bool decompressBlock(CompressedBlockInfo& info, Athena::io::IStreamReader& in, Athena::io::IStreamWriter& out);
bool decompressMREA(Athena::io::IStreamReader& in, Athena::io::IStreamWriter& out)
{
try
{
// Do this as a precaution, MREAs are always in big endian
in.setEndian(Athena::Endian::BigEndian);
out.setEndian(Athena::Endian::BigEndian);
atUint32 magic = in.readUint32();
if (magic != 0xDEADBEEF)
return false;
atUint32 version = in.readUint32();
// Metroid prime 1 MREAs aren't compressed
if (version == MetroidPrime1 || version == MetroidPrimeDemo)
return false;
out.writeUint32(magic);
out.writeUint32(version);
atInt8* mtxData = in.readBytes(sizeof(float[3][4]));
out.writeBytes(mtxData, sizeof(float[3][4]));
delete[] mtxData;
out.writeUint32(in.readUint32()); // mesh count
out.writeUint32(in.readUint32()); // scly count
atUint32 sectionCount = in.readUint32();
out.writeUint32(sectionCount);
if (version == MetroidPrime2)
{
for (atUint32 i = 0; i < 11; i++)
out.writeUint32(in.readUint32());
}
atUint32 compressedBlockCount = in.readUint32();
out.writeUint32(compressedBlockCount);
atUint32 sectionNumberCount = 0;
if (version == MetroidPrime3 || version == DKCR)
sectionNumberCount = in.readUint32();
out.writeUint32(sectionNumberCount);
in.seekAlign32();
out.seekAlign32();
for (atUint32 i = 0; i < sectionCount; i++)
out.writeUint32(in.readUint32());
in.seekAlign32();
out.seekAlign32();
std::vector<CompressedBlockInfo> blockInfo;
for (atUint32 i = 0; i < compressedBlockCount; i++)
{
CompressedBlockInfo block;
block.blockSize = in.readUint32();
out.writeUint32(block.blockSize);
block.dataSize = in.readUint32();
out.writeUint32(block.dataSize);
block.dataCompSize = in.readUint32();
out.writeUint32(0);
block.sectionCount = in.readUint32();
out.writeUint32(block.sectionCount);
blockInfo.push_back(block);
}
// We only need to seek in, out is already where it needs to be
in.seekAlign32();
out.seekAlign32();
if (version == MetroidPrime3 || version == DKCR)
{
for (atUint32 i = 0; i < sectionNumberCount * 2; i++)
out.writeUint32(in.readUint32());
in.seekAlign32();
out.seekAlign32();
}
for (CompressedBlockInfo info : blockInfo)
if (!decompressBlock(info, in, out))
return false;
}
catch(...)
{
return false;
}
return true;
}
bool decompressBlock(CompressedBlockInfo& info, Athena::io::IStreamReader& in, Athena::io::IStreamWriter& out)
{
try
{
// if dataCompSize is 0 we just write the raw data
if (info.dataCompSize == 0)
{
atUint8* rawData = in.readUBytes(info.dataSize);
out.writeUBytes(rawData, info.dataSize);
delete[] rawData;
}
else
{
// We have compressed data, this is a bit tricky since the compression header isn't always located at the start of the data
// Retro did something unorthodox, instead of padding the end of the block, they padded the beginning
atUint32 blockStart = ROUND_UP_32(info.dataCompSize) - info.dataCompSize;
atUint8* rawData = in.readUBytes(ROUND_UP_32(info.dataCompSize));
atUint8* dataStart = rawData;
rawData += blockStart;
bool result = true;
atUint32 decompressedSize = info.dataSize;
atInt32 remainingSize = info.dataSize;
// We use the blockSize because it's always larger than either size, it's also the behavior observed in the engine.
atUint8* newData = new atUint8[info.blockSize];
while (remainingSize > 0)
{
atUint16 segmentSize = *(atUint16*)(rawData);
Athena::utility::BigUint16(segmentSize);
rawData += 2;
atUint16 peek = *(atUint16*)(rawData);
Athena::utility::BigUint16(peek);
if (peek != 0x78DA && peek != 0x7801 && peek != 0x789C)
{
if (segmentSize > 0x4000)
{
// not compressed
memcpy(&newData[decompressedSize - remainingSize], rawData, 0x10000 - segmentSize);
rawData += 0x10000 - segmentSize;
remainingSize -= 0x10000 - segmentSize;
result = true;
continue;
}
int lzoStatus = Athena::io::Compression::decompressLZO(rawData, segmentSize, &newData[decompressedSize - remainingSize], remainingSize);
if (!lzoStatus)
result = true;
else
{
result = false;
break;
}
rawData += segmentSize;
}
else
{
int err = Athena::io::Compression::decompressZlib(rawData, segmentSize, &newData[decompressedSize - remainingSize], decompressedSize);
if (err > 0)
{
remainingSize -= err;
result = true;
}
else
{
result = false;
break;
}
rawData += segmentSize;
}
}
if (result)
out.writeUBytes(newData, decompressedSize);
delete[] newData;
delete[] dataStart;
}
}
catch(...)
{
return false;
}
return true;
}

107
src/RetroCommon.cpp Normal file
View File

@ -0,0 +1,107 @@
#include "RetroCommon.hpp"
#include <Athena/Compression.hpp>
#include <Athena/InvalidDataException.hpp>
#include <memory.h>
struct CMPDBlock
{
atUint32 compressedLen;
atUint32 uncompressedLen;
};
void decompressData(aIO::IStreamWriter& outbuf, const atUint8* srcData, atUint32 srcLength, atInt32 uncompressedLength)
{
atUint16 compressionMethod = *(atUint16*)(srcData);
Athena::utility::BigUint16(compressionMethod);
if (compressionMethod == 0x78DA || compressionMethod == 0x7801 || compressionMethod == 0x789C)
{
atUint8* decompData = new atUint8[uncompressedLength];
if (aIO::Compression::decompressZlib(srcData, srcLength, decompData, uncompressedLength) == uncompressedLength)
outbuf.writeUBytes(decompData, uncompressedLength);
delete[] decompData;
}
else
{
bool result = true;
atUint8* newData = new atUint8[uncompressedLength];
atInt32 remainingSize = uncompressedLength;
do
{
if (remainingSize <= 0)
break;
atInt16 segmentSize = *(atInt16*)(srcData);
srcData += 2;
Athena::utility::BigInt16(segmentSize);
if (segmentSize < 0)
{
segmentSize = -segmentSize;
memcpy(&newData[uncompressedLength - remainingSize], srcData, segmentSize);
}
else
{
atInt32 lzoStatus = Athena::io::Compression::decompressLZO((const atUint8*)srcData, segmentSize, &newData[uncompressedLength - remainingSize], remainingSize);
if ((lzoStatus & 8) != 0)
{
result = false;
break;
}
}
srcData += segmentSize;
}
while (remainingSize > 0);
if (result)
outbuf.writeUBytes(newData, uncompressedLength);
delete[] newData;
}
}
void decompressFile(aIO::IStreamWriter& outbuf, const atUint8* data, atUint32 srcLength)
{
atUint32 magic = *(atUint32*)(data);
Athena::utility::BigUint32(magic);
if (magic == 0x434D5044)
{
atUint32 currentOffset = 4;
atUint32 blockCount = *(atUint32*)(data + currentOffset);
currentOffset += 4;
Athena::utility::BigUint32(blockCount);
CMPDBlock* blocks = new CMPDBlock[blockCount];
memcpy(blocks, data + currentOffset, sizeof(CMPDBlock)*blockCount);
currentOffset += (sizeof(CMPDBlock)*blockCount);
for (atUint32 i = 0; i < blockCount; i++)
{
Athena::utility::BigUint32(blocks[i].compressedLen);
Athena::utility::BigUint32(blocks[i].uncompressedLen);
blocks[i].compressedLen &= 0x00FFFFFF;
if (blocks[i].compressedLen == blocks[i].uncompressedLen)
outbuf.writeUBytes((atUint8*)(data + currentOffset), blocks[i].uncompressedLen);
else
{
decompressData(outbuf, (const atUint8*)(data + currentOffset), blocks[i].compressedLen, blocks[i].uncompressedLen);
}
currentOffset += blocks[i].compressedLen;
}
}
else
{
atUint32 uncompressedLength = *(atUint32*)(data);
Athena::utility::BigUint32(uncompressedLength);
atUint8* tmp = new atUint8[srcLength];
memcpy(tmp, data + 4, srcLength - 4);
decompressData(outbuf, (const atUint8*)tmp, srcLength - 4, uncompressedLength);
delete[] tmp;
}
delete[] data;
}