#include "../crypto.h"
#include "hash_table.h"
#include <algorithm>
#include <cstring>
namespace whiteout::storages::mpq {
bool HashTable::parse(std::span<const u8> data, u32 capacity) {
const size_t expectedSize = static_cast<size_t>(capacity) * sizeof(HashEntry);
if (data.size() < expectedSize)
return false;
std::vector<u32> raw(capacity * 4);
std::memcpy(raw.data(), data.data(), expectedSize);
decryptBlock(raw.data(), raw.size(), hashString("(hash table)", HashType::FileKey));
m_entries.resize(capacity);
for (u32 i = 0; i < capacity; ++i) {
const u32* src = raw.data() + i * 4;
m_entries[i].hashA = src[0];
m_entries[i].hashB = src[1];
m_entries[i].locale = static_cast<u16>(src[2] & 0xFFFF);
m_entries[i].platform = static_cast<u16>((src[2] >> 16) & 0xFFFF);
m_entries[i].blockIndex = src[3];
}
return true;
}
void HashTable::createEmpty(u32 capacity) {
m_entries.assign(capacity, HashEntry{}); }
namespace {
std::optional<u32> probeFor(const std::vector<HashEntry>& entries, const std::string& filename,
std::optional<u16> locale) {
if (entries.empty())
return std::nullopt;
u32 const cap = static_cast<u32>(entries.size());
u32 const hashA = hashString(filename, HashType::NameA);
u32 const hashB = hashString(filename, HashType::NameB);
u32 const startIndex = hashString(filename, HashType::TableOffset) & (cap - 1);
u32 index = startIndex;
do {
const auto& e = entries[index];
if (e.isEmpty())
return std::nullopt;
if (e.isOccupied() && e.hashA == hashA && e.hashB == hashB) {
if (!locale || e.locale == *locale)
return index;
}
index = (index + 1) & (cap - 1);
} while (index != startIndex);
return std::nullopt;
}
}
std::optional<u32> HashTable::lookup(const std::string& filename) const {
return probeFor(m_entries, filename, std::nullopt);
}
std::optional<u32> HashTable::lookup(const std::string& filename, u16 locale) const {
return probeFor(m_entries, filename, locale);
}
std::optional<u32> HashTable::insert(const std::string& filename, u16 locale, u32 blockIndex) {
if (m_entries.empty())
return std::nullopt;
u32 const hashA = hashString(filename, HashType::NameA);
u32 const hashB = hashString(filename, HashType::NameB);
u32 const startIndex = hashString(filename, HashType::TableOffset) & (capacity() - 1);
u32 index = startIndex;
do {
auto& e = m_entries[index];
if (!e.isOccupied()) {
e.hashA = hashA;
e.hashB = hashB;
e.locale = locale;
e.platform = 0;
e.blockIndex = blockIndex;
return index;
}
index = (index + 1) & (capacity() - 1);
} while (index != startIndex);
return std::nullopt; }
bool HashTable::remove(const std::string& filename) {
auto idx = lookup(filename);
if (!idx)
return false;
m_entries[*idx].blockIndex = kHashEntryDeleted;
return true;
}
bool HashTable::remove(const std::string& filename, u16 locale) {
auto idx = lookup(filename, locale);
if (!idx)
return false;
m_entries[*idx].blockIndex = kHashEntryDeleted;
return true;
}
std::vector<u8> HashTable::serialize() const {
const size_t byteSize = m_entries.size() * sizeof(HashEntry);
std::vector<u32> raw(m_entries.size() * 4);
for (size_t i = 0; i < m_entries.size(); ++i) {
const auto& e = m_entries[i];
raw[i * 4 + 0] = e.hashA;
raw[i * 4 + 1] = e.hashB;
raw[i * 4 + 2] = static_cast<u32>(e.locale) | (static_cast<u32>(e.platform) << 16);
raw[i * 4 + 3] = e.blockIndex;
}
encryptBlock(raw.data(), raw.size(), hashString("(hash table)", HashType::FileKey));
std::vector<u8> result(byteSize);
std::memcpy(result.data(), raw.data(), byteSize);
return result;
}
u32 HashTable::occupiedCount() const {
u32 count = 0;
for (const auto& e : m_entries) {
if (e.isOccupied())
++count;
}
return count;
}
}