#include "header.h"
#include "../../../common/binary_reader.h"
#include "../../../common/streams.h"
#include <algorithm>
#include <cstring>
#include <sstream>
namespace whiteout::storages::mpq {
using whiteout::common::BinaryReader;
using whiteout::common::span_streambuf;
u32 nextPowerOf2(u32 v) {
if (v == 0)
return 1;
--v;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
return v + 1;
}
namespace {
u32 minimumHeaderSizeForVersion(u16 formatVersion) {
switch (formatVersion) {
case 0:
return 32;
case 1:
return 44;
case 2:
return 68;
case 3:
return 208;
default:
return 0;
}
}
std::optional<MpqHeader> parseHeaderFields(BinaryReader& reader, size_t availableBytes) {
if (availableBytes < 32)
return std::nullopt;
MpqHeader h{};
h.magic = reader.read<u32>();
if (h.magic != kMpqMagic)
return std::nullopt;
h.headerSize = reader.read<u32>();
h.archiveSize = reader.read<u32>();
h.formatVersion = reader.read<u16>();
h.sectorSizeShift = reader.read<u16>();
h.hashTableOffset = reader.read<u32>();
h.blockTableOffset = reader.read<u32>();
h.hashTableEntries = reader.read<u32>();
h.blockTableEntries = reader.read<u32>();
u32 const minimumHeaderSize = minimumHeaderSizeForVersion(h.formatVersion);
if (minimumHeaderSize == 0 || h.headerSize < minimumHeaderSize ||
h.headerSize > availableBytes) {
return std::nullopt;
}
if (h.formatVersion >= 1 && availableBytes >= 44) {
h.hiBlockTableOffset = reader.read<u64>();
h.hashTableOffsetHi = reader.read<u16>();
h.blockTableOffsetHi = reader.read<u16>();
}
if (h.formatVersion >= 2 && availableBytes >= 68) {
h.archiveSize64 = reader.read<u64>();
h.betTableOffset = reader.read<u64>();
h.hetTableOffset = reader.read<u64>();
}
if (h.formatVersion >= 3 && availableBytes >= 208) {
h.hashTableSize64 = reader.read<u64>();
h.blockTableSize64 = reader.read<u64>();
h.hiBlockTableSize64 = reader.read<u64>();
h.hetTableSize64 = reader.read<u64>();
h.betTableSize64 = reader.read<u64>();
h.rawChunkSize = reader.read<u32>();
auto readMd5 = [&](std::array<u8, 16>& dest) {
for (auto& b : dest)
b = reader.read<u8>();
};
readMd5(h.blockTableMd5);
readMd5(h.hashTableMd5);
readMd5(h.hiBlockTableMd5);
readMd5(h.betTableMd5);
readMd5(h.hetTableMd5);
readMd5(h.mpqHeaderMd5);
}
return h;
}
}
std::optional<HeaderParseResult> findAndParseHeader(std::span<const u8> fileData) {
if (fileData.size() < 32)
return std::nullopt;
const size_t maxOffset = fileData.size() - 32;
for (size_t offset = 0; offset <= maxOffset; offset += kHeaderSearchAlignment) {
u32 magic = 0;
std::memcpy(&magic, fileData.data() + offset, sizeof(u32));
if (magic == kMpqUserDataMagic && offset == 0) {
if (fileData.size() < offset + 16)
continue;
span_streambuf udBuf(fileData.subspan(offset, 16));
std::istream udStream(&udBuf);
BinaryReader udReader(udStream);
u32 const udMagic = udReader.read<u32>();
(void)udMagic;
u32 const userDataSize = udReader.read<u32>();
u32 const headerOff = udReader.read<u32>();
u32 const userDataHeaderSize = udReader.read<u32>();
size_t const realHeaderOffset = offset + headerOff;
if (realHeaderOffset + 32 > fileData.size())
continue;
size_t const avail = fileData.size() - realHeaderOffset;
size_t const readSize = std::min<size_t>(avail, 512);
span_streambuf hdrBufSmall(fileData.subspan(realHeaderOffset, readSize));
std::istream hdrStreamSmall(&hdrBufSmall);
BinaryReader hdrReaderSmall(hdrStreamSmall);
auto hdr = parseHeaderFields(hdrReaderSmall, avail);
if (!hdr)
continue;
HeaderParseResult result;
result.header = *hdr;
result.archiveOffset = realHeaderOffset;
UserData ud;
ud.magic = kMpqUserDataMagic;
ud.userDataSize = userDataSize;
ud.headerOffset = headerOff;
ud.userDataHeaderSize = userDataHeaderSize;
size_t const udDataStart = offset + 16; size_t const udDataLen = std::min<size_t>(userDataSize, fileData.size() - udDataStart);
ud.data.assign(fileData.data() + udDataStart,
fileData.data() + udDataStart + udDataLen);
result.userData = std::move(ud);
return result;
}
if (magic == kMpqMagic) {
size_t const avail = fileData.size() - offset;
size_t const readSize = std::min<size_t>(avail, 512);
span_streambuf sbuf(fileData.subspan(offset, readSize));
std::istream stream(&sbuf);
BinaryReader reader(stream);
auto hdr = parseHeaderFields(reader, avail);
if (!hdr)
continue;
HeaderParseResult result;
result.header = *hdr;
result.archiveOffset = offset;
return result;
}
}
return std::nullopt;
}
MpqHeader buildHeader(u16 formatVersion, u32 hashTableSize, u16 sectorSizeShift) {
MpqHeader h{};
h.magic = kMpqMagic;
h.formatVersion = formatVersion;
h.sectorSizeShift = sectorSizeShift;
u32 const htSize = nextPowerOf2(hashTableSize);
h.hashTableEntries = htSize;
h.blockTableEntries = 0;
switch (formatVersion) {
case 0:
h.headerSize = 32;
break;
case 1:
h.headerSize = 44;
break;
case 2:
h.headerSize = 68;
break;
case 3:
h.headerSize = 208;
break;
default:
h.headerSize = 32;
break;
}
return h;
}
}