#include <whiteout/sno/core_toc.h>
#include <algorithm>
#include <cstring>
#include <istream>
#include "../common/binary_reader.h"
#include "../common/streams.h"
namespace whiteout {
namespace sno {
bool CoreToc::parse(std::span<const u8> data) {
m_all.clear();
m_groupIndex.clear();
m_idIndex.clear();
m_formatHashes.clear();
m_format = CoreTocFormat::Unknown;
if (data.size() < 8) {
return false;
}
common::span_streambuf sbuf(data);
std::istream stream(&sbuf);
common::BinaryReader reader(stream);
const u32 firstWord = reader.read<u32>();
if (firstWord == 0xBCDE6611u) {
m_format = CoreTocFormat::D4New;
return parseD4(data, true);
}
constexpr u32 kD3NumGroups = 70;
constexpr size_t kD3HeaderSize = kD3NumGroups * 4 * 4; if (firstWord == 0 && data.size() > kD3HeaderSize + 64) {
m_format = CoreTocFormat::D3Legacy;
return parseD3Legacy(data);
}
m_format = CoreTocFormat::D4Old;
return parseD4(data, false);
}
bool CoreToc::parseD3Legacy(std::span<const u8> data) {
constexpr u32 kNumGroups = 70;
constexpr size_t kHeaderSize = kNumGroups * 4 * 4;
if (data.size() < kHeaderSize) {
return false;
}
common::span_streambuf sbuf(data);
std::istream stream(&sbuf);
common::BinaryReader reader(stream);
std::vector<u32> entryCounts(kNumGroups);
for (u32 g = 0; g < kNumGroups; ++g) {
entryCounts[g] = reader.read<u32>();
}
std::vector<u32> sectionOffsets(kNumGroups);
for (u32 g = 0; g < kNumGroups; ++g) {
sectionOffsets[g] = reader.read<u32>();
}
const size_t dataStart = kHeaderSize;
struct GroupInfo {
u32 groupId;
u32 headerCount;
size_t sectionStart;
};
std::vector<GroupInfo> groups;
groups.reserve(kNumGroups);
for (u32 g = 0; g < kNumGroups; ++g) {
if (entryCounts[g] > 0) {
groups.push_back({g, entryCounts[g], dataStart + sectionOffsets[g]});
}
}
std::sort(groups.begin(), groups.end(), [](const GroupInfo& a, const GroupInfo& b) {
return a.sectionStart < b.sectionStart;
});
size_t totalEstimate = 0;
for (auto& gi : groups)
totalEstimate += gi.headerCount;
m_all.reserve(totalEstimate);
for (size_t gi = 0; gi < groups.size(); ++gi) {
const auto& info = groups[gi];
const i32 expectedGroup = static_cast<i32>(info.groupId);
const size_t sectionEnd =
(gi + 1 < groups.size()) ? groups[gi + 1].sectionStart : data.size();
if (info.sectionStart >= data.size()) {
continue;
}
size_t entryCount = 0;
{
reader.setPosition(static_cast<u32>(info.sectionStart));
size_t pos = info.sectionStart;
while (pos + 12 <= sectionEnd) {
const i32 g = reader.read<i32>();
if (g != expectedGroup)
break;
reader.skip(8);
++entryCount;
pos += 12;
}
}
const size_t nameBase = info.sectionStart + entryCount * 12;
const size_t startIdx = m_all.size();
for (size_t i = 0; i < entryCount; ++i) {
reader.setPosition(static_cast<u32>(info.sectionStart + i * 12 + 4));
const i32 snoId = reader.read<i32>();
const i32 nameRelOffset = reader.read<i32>();
const size_t namePos = nameBase + static_cast<size_t>(nameRelOffset);
std::string name;
if (namePos < data.size()) {
reader.setPosition(static_cast<u32>(namePos));
name = reader.readZString();
}
TocEntry entry;
entry.group = static_cast<SnoGroup>(expectedGroup);
entry.snoId = snoId;
entry.name = std::move(name);
m_idIndex[snoId] = m_all.size();
m_all.push_back(std::move(entry));
}
m_groupIndex[expectedGroup] = {startIdx, m_all.size() - startIdx};
}
return true;
}
bool CoreToc::parseD4(std::span<const u8> data, bool newFormat) {
const size_t tocOffset = newFormat ? 8 : 4;
common::span_streambuf sbuf(data);
std::istream stream(&sbuf);
common::BinaryReader reader(stream);
reader.setPosition(newFormat ? 4 : 0);
const u32 snoGroupsCount = reader.read<u32>();
if (snoGroupsCount > 1024) {
return false; }
const u32 headerArrays = newFormat ? 4u : 3u;
const size_t headerSize = tocOffset + static_cast<size_t>(headerArrays) * snoGroupsCount * 4;
if (data.size() < headerSize + 4) {
return false;
}
std::vector<u32> counts(snoGroupsCount);
for (u32 c = 0; c < snoGroupsCount; ++c) {
counts[c] = reader.read<u32>();
}
std::vector<u32> offsets(snoGroupsCount);
for (u32 c = 0; c < snoGroupsCount; ++c) {
offsets[c] = reader.read<u32>();
}
reader.skip(snoGroupsCount * 4);
if (newFormat) {
for (u32 c = 0; c < snoGroupsCount; ++c) {
u32 const fh = reader.read<u32>();
if (c > 0 && fh != 0) {
m_formatHashes[static_cast<i32>(c)] = fh;
}
}
}
const size_t dataStart = headerSize + 4;
size_t totalEntries = 0;
for (u32 c = 0; c < snoGroupsCount; ++c) {
totalEntries += counts[c];
}
m_all.reserve(totalEntries);
for (u32 c = 0; c < snoGroupsCount; ++c) {
const u32 entryCount = counts[c];
const u32 entryOffset = offsets[c];
if (entryCount == 0) {
continue;
}
const size_t groupDataStart = dataStart + entryOffset;
const size_t entryTableSize = static_cast<size_t>(entryCount) * 12;
const size_t nameTableStart = groupDataStart + entryTableSize;
if (groupDataStart + entryTableSize > data.size()) {
return false;
}
const size_t startIdx = m_all.size();
for (u32 i = 0; i < entryCount; ++i) {
reader.setPosition(static_cast<u32>(groupDataStart + static_cast<size_t>(i) * 12));
const i32 snoGroup = reader.read<i32>();
const i32 snoId = reader.read<i32>();
const i32 nameRelOffset = reader.read<i32>();
const size_t namePos = nameTableStart + static_cast<size_t>(nameRelOffset);
std::string name;
if (namePos < data.size()) {
reader.setPosition(static_cast<u32>(namePos));
name = reader.readZString();
}
TocEntry entry;
entry.group = static_cast<SnoGroup>(snoGroup);
entry.snoId = snoId;
entry.name = std::move(name);
m_idIndex[snoId] = m_all.size();
m_all.push_back(std::move(entry));
}
m_groupIndex[static_cast<i32>(c)] = {startIdx, m_all.size() - startIdx};
}
return true;
}
std::span<const TocEntry> CoreToc::entriesForGroup(SnoGroup group) const {
const auto groupId = static_cast<i32>(group);
auto it = m_groupIndex.find(groupId);
if (it == m_groupIndex.end()) {
return {};
}
return std::span<const TocEntry>(m_all.data() + it->second.first, it->second.second);
}
const TocEntry* CoreToc::findById(i32 snoId) const {
auto it = m_idIndex.find(snoId);
if (it == m_idIndex.end()) {
return nullptr;
}
return &m_all[it->second];
}
bool CoreToc::addEntry(const TocEntry& entry) {
if (m_idIndex.count(entry.snoId))
return false;
const i32 groupId = static_cast<i32>(entry.group);
const size_t idx = m_all.size();
m_all.push_back(entry);
m_idIndex[entry.snoId] = idx;
auto git = m_groupIndex.find(groupId);
if (git == m_groupIndex.end()) {
m_groupIndex[groupId] = {idx, 1};
} else {
auto& [start, count] = git->second;
if (start + count == idx) {
++count;
} else {
const size_t oldCount = count;
std::vector<TocEntry> const groupEntries(m_all.begin() + static_cast<ptrdiff_t>(start),
m_all.begin() +
static_cast<ptrdiff_t>(start + oldCount));
++count; }
}
return true;
}
const TocEntry* CoreToc::findByName(SnoGroup group, const std::string& name) const {
const i32 groupId = static_cast<i32>(group);
for (auto& e : m_all) {
if (static_cast<i32>(e.group) == groupId && e.name == name)
return &e;
}
return nullptr;
}
i32 CoreToc::maxSnoId() const {
i32 maxId = 0;
for (auto& e : m_all) {
if (e.snoId > maxId)
maxId = e.snoId;
}
return maxId;
}
static void writeU32(std::vector<u8>& buf, u32 val) {
const auto off = buf.size();
buf.resize(off + 4);
std::memcpy(buf.data() + off, &val, 4);
}
static void writeI32(std::vector<u8>& buf, i32 val) {
const auto off = buf.size();
buf.resize(off + 4);
std::memcpy(buf.data() + off, &val, 4);
}
std::vector<u8> CoreToc::serializeD3Legacy() const {
constexpr u32 kNumGroups = 70;
std::vector<std::vector<const TocEntry*>> perGroup(kNumGroups);
for (auto& e : m_all) {
const auto gid = static_cast<u32>(e.group);
if (gid < kNumGroups)
perGroup[gid].push_back(&e);
}
struct GroupSection {
std::vector<u8> data;
u32 entryCount = 0;
};
std::vector<GroupSection> sections(kNumGroups);
for (u32 g = 0; g < kNumGroups; ++g) {
auto& entries = perGroup[g];
if (entries.empty())
continue;
auto& sec = sections[g];
sec.entryCount = static_cast<u32>(entries.size());
std::vector<u8> namePool;
std::vector<i32> nameRelOffsets;
nameRelOffsets.reserve(entries.size());
for (auto* e : entries) {
nameRelOffsets.push_back(static_cast<i32>(namePool.size()));
namePool.insert(namePool.end(), e->name.begin(), e->name.end());
namePool.push_back(0); }
sec.data.reserve(entries.size() * 12 + namePool.size());
for (size_t i = 0; i < entries.size(); ++i) {
writeI32(sec.data, static_cast<i32>(g));
writeI32(sec.data, entries[i]->snoId);
writeI32(sec.data, nameRelOffsets[i]);
}
sec.data.insert(sec.data.end(), namePool.begin(), namePool.end());
}
std::vector<u32> sectionOffsets(kNumGroups, 0);
u32 offset = 0;
for (u32 g = 0; g < kNumGroups; ++g) {
sectionOffsets[g] = offset;
offset += static_cast<u32>(sections[g].data.size());
}
std::vector<u8> result;
constexpr size_t kHeaderSize = kNumGroups * 4 * 4; result.reserve(kHeaderSize + offset);
for (u32 g = 0; g < kNumGroups; ++g)
writeU32(result, sections[g].entryCount);
for (u32 g = 0; g < kNumGroups; ++g)
writeU32(result, sectionOffsets[g]);
for (u32 g = 0; g < kNumGroups; ++g)
writeU32(result, 0);
for (u32 g = 0; g < kNumGroups; ++g)
writeU32(result, 0);
for (u32 g = 0; g < kNumGroups; ++g) {
result.insert(result.end(), sections[g].data.begin(), sections[g].data.end());
}
return result;
}
std::vector<u8> CoreToc::serializeD4New() const {
std::unordered_map<i32, std::vector<const TocEntry*>> perGroup;
i32 maxGroupId = 0;
for (auto& e : m_all) {
const auto gid = static_cast<i32>(e.group);
if (gid > maxGroupId)
maxGroupId = gid;
perGroup[gid].push_back(&e);
}
const u32 snoGroupsCount = static_cast<u32>(maxGroupId + 1);
struct GroupBlob {
std::vector<u8> data;
u32 entryCount = 0;
};
std::vector<GroupBlob> groupBlobs(snoGroupsCount);
for (auto& [gid, entries] : perGroup) {
if (gid < 0 || static_cast<u32>(gid) >= snoGroupsCount)
continue;
auto& blob = groupBlobs[static_cast<u32>(gid)];
blob.entryCount = static_cast<u32>(entries.size());
std::vector<u8> namePool;
std::vector<i32> nameRelOffsets;
nameRelOffsets.reserve(entries.size());
for (auto* e : entries) {
nameRelOffsets.push_back(static_cast<i32>(namePool.size()));
namePool.insert(namePool.end(), e->name.begin(), e->name.end());
namePool.push_back(0);
}
blob.data.reserve(entries.size() * 12 + namePool.size());
for (size_t i = 0; i < entries.size(); ++i) {
writeI32(blob.data, gid);
writeI32(blob.data, entries[i]->snoId);
writeI32(blob.data, nameRelOffsets[i]);
}
blob.data.insert(blob.data.end(), namePool.begin(), namePool.end());
}
std::vector<u32> offsets(snoGroupsCount, 0);
u32 dataOffset = 0;
for (u32 g = 0; g < snoGroupsCount; ++g) {
offsets[g] = dataOffset;
dataOffset += static_cast<u32>(groupBlobs[g].data.size());
}
const size_t headerSize = 8 + static_cast<size_t>(snoGroupsCount) * 4 * 4 + 4;
std::vector<u8> result;
result.reserve(headerSize + dataOffset);
writeU32(result, 0xBCDE6611u);
writeU32(result, snoGroupsCount);
for (u32 g = 0; g < snoGroupsCount; ++g)
writeU32(result, groupBlobs[g].entryCount);
for (u32 g = 0; g < snoGroupsCount; ++g)
writeU32(result, offsets[g]);
for (u32 g = 0; g < snoGroupsCount; ++g)
writeU32(result, 0);
for (u32 g = 0; g < snoGroupsCount; ++g) {
auto it = m_formatHashes.find(static_cast<i32>(g));
writeU32(result, (it != m_formatHashes.end()) ? it->second : 0);
}
writeU32(result, 0);
for (u32 g = 0; g < snoGroupsCount; ++g) {
result.insert(result.end(), groupBlobs[g].data.begin(), groupBlobs[g].data.end());
}
return result;
}
} }