From 0b8823356c84bccc79ab135bf057eb302608c25b Mon Sep 17 00:00:00 2001 From: Phillip Stephens Date: Sat, 18 Apr 2015 23:26:07 -0700 Subject: [PATCH] * Initial commit --- RetroCommon.pri | 10 ++ include/RetroCommon.hpp | 21 ++++ src/MREADecompress.cpp | 212 ++++++++++++++++++++++++++++++++++++++++ src/RetroCommon.cpp | 107 ++++++++++++++++++++ 4 files changed, 350 insertions(+) create mode 100644 RetroCommon.pri create mode 100644 include/RetroCommon.hpp create mode 100644 src/MREADecompress.cpp create mode 100644 src/RetroCommon.cpp diff --git a/RetroCommon.pri b/RetroCommon.pri new file mode 100644 index 000000000..49fbd2452 --- /dev/null +++ b/RetroCommon.pri @@ -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 diff --git a/include/RetroCommon.hpp b/include/RetroCommon.hpp new file mode 100644 index 000000000..5569f9dd6 --- /dev/null +++ b/include/RetroCommon.hpp @@ -0,0 +1,21 @@ +#ifndef RETROCOMMON_HPP +#define RETROCOMMON_HPP + +#include +#include + +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 diff --git a/src/MREADecompress.cpp b/src/MREADecompress.cpp new file mode 100644 index 000000000..ef76f08a0 --- /dev/null +++ b/src/MREADecompress.cpp @@ -0,0 +1,212 @@ +#include "RetroCommon.hpp" +#include +#include + +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 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; +} diff --git a/src/RetroCommon.cpp b/src/RetroCommon.cpp new file mode 100644 index 000000000..49a5134a2 --- /dev/null +++ b/src/RetroCommon.cpp @@ -0,0 +1,107 @@ +#include "RetroCommon.hpp" +#include +#include +#include + +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; +}