mirror of https://github.com/AxioDL/metaforce.git
* Initial commit
This commit is contained in:
commit
0b8823356c
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue