2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2026-06-06 08:42:35 +00:00

feat: properly parse data from xenon ipaks

This commit is contained in:
Jan Laupetin
2026-05-12 22:02:52 +02:00
parent e1bb8ae4d2
commit 71ca182524
12 changed files with 261 additions and 129 deletions
@@ -2,133 +2,25 @@
#include "Utils/Logging/Log.h"
#include <cstring>
#include <format>
#include <iostream>
#include <lzx.h>
namespace
{
uint8_t NextByte(const uint8_t* input, size_t& offset, size_t& remainingSize)
{
const auto value = input[offset];
offset++;
remainingSize--;
return value;
}
uint16_t CombineHighLow(const uint8_t highByte, const uint8_t lowByte)
{
return static_cast<uint16_t>(static_cast<uint16_t>(static_cast<uint16_t>(highByte) << 8u) | static_cast<uint16_t>(lowByte));
}
void LogErrorHeaderSpace(size_t remainingInputSize)
{
con::error("XMemCompress: Not enough data for header: {}", remainingInputSize);
}
} // namespace
XChunkProcessorLzxDecompress::XChunkProcessorLzxDecompress(const unsigned streamCount)
: m_lzx_states(streamCount)
: m_xmemdecompress_contexts(streamCount)
{
// T6 uses 17 for window bits
for (auto& lzxState : m_lzx_states)
lzxState = lzx_init(17);
}
XChunkProcessorLzxDecompress::~XChunkProcessorLzxDecompress()
{
for (auto* lzxState : m_lzx_states)
lzx_teardown(static_cast<lzx_state*>(lzxState));
}
size_t XChunkProcessorLzxDecompress::Process(
const unsigned streamNumber, const uint8_t* input, const size_t inputLength, uint8_t* output, const size_t outputBufferSize)
{
auto* state = static_cast<lzx_state*>(m_lzx_states[streamNumber]);
const auto& xMemDecompress = m_xmemdecompress_contexts[streamNumber];
// lzx state is reset before each chunk
lzx_reset(state);
xMemDecompress.Reset();
size_t curInputOffset = 0uz;
size_t curInputSize = inputLength;
size_t curOutputOffset = 0uz;
size_t curOutputSize = outputBufferSize;
uint8_t lowByte;
uint16_t dstSize, srcSize;
while (curInputSize > 0)
const auto maybeDecompressedSize = xMemDecompress.Process(input, inputLength, output, outputBufferSize);
if (!maybeDecompressedSize.has_value())
{
uint8_t highByte = NextByte(input, curInputOffset, curInputSize);
uint8_t suffixSize;
if (highByte == 0xFF) // magic number: output is smaller than 0x8000
{
if (curInputSize < 4)
{
LogErrorHeaderSpace(curInputSize);
return curOutputOffset;
}
highByte = NextByte(input, curInputOffset, curInputSize);
lowByte = NextByte(input, curInputOffset, curInputSize);
dstSize = CombineHighLow(highByte, lowByte);
highByte = NextByte(input, curInputOffset, curInputSize);
lowByte = NextByte(input, curInputOffset, curInputSize);
srcSize = CombineHighLow(highByte, lowByte);
// The game seems to skip a 5 byte suffix after these blocks, not sure why.
suffixSize = 5u;
}
else
{
if (curInputSize < 1)
{
LogErrorHeaderSpace(curInputSize);
return curOutputOffset;
}
dstSize = 0x8000u;
lowByte = NextByte(input, curInputOffset, curInputSize);
srcSize = CombineHighLow(highByte, lowByte);
suffixSize = 0u;
}
if (srcSize == 0 || dstSize == 0)
{
// Other implementations do not handle this as a failure, game code suggests otherwise though
con::error("XMemCompress: EOF: {} {}, {}", srcSize, dstSize, curInputSize);
return curOutputOffset;
}
if (static_cast<size_t>(srcSize) + suffixSize > curInputSize)
{
con::error("XMemCompress: block size bigger than remaining data: {} > {}", srcSize, curInputSize);
return curOutputOffset;
}
if (dstSize > curOutputSize)
{
con::error("XMemCompress: output size bigger than remaining data: {} > {}", dstSize, curOutputSize);
return curOutputOffset;
}
auto ret = lzx_decompress(state, &input[curInputOffset], &output[curOutputOffset], srcSize, dstSize);
curInputOffset += srcSize + suffixSize;
curInputSize -= (srcSize + suffixSize);
curOutputOffset += dstSize;
curOutputSize -= srcSize;
if (ret != DECR_OK)
{
con::error("XMemCompress: lzx decompression failed: {}", ret);
return curOutputOffset;
}
con::error("Failed to decompress xchunk with XMemDecompress");
return 0;
}
return curOutputOffset;
return *maybeDecompressedSize;
}
@@ -1,6 +1,7 @@
#pragma once
#include "IXChunkProcessor.h"
#include "XMemDecompress.h"
#include <vector>
@@ -8,9 +9,8 @@ class XChunkProcessorLzxDecompress final : public IXChunkProcessor
{
public:
explicit XChunkProcessorLzxDecompress(unsigned streamCount);
~XChunkProcessorLzxDecompress();
size_t Process(unsigned streamNumber, const uint8_t* input, size_t inputLength, uint8_t* output, size_t outputBufferSize) override;
private:
std::vector<void*> m_lzx_states;
std::vector<XMemDecompressContext> m_xmemdecompress_contexts;
};