#include "../../common/hex.h"
#include "../../common/jenkins.h"
#include "common/root_build_utils.h"
#include "s1_root.h"
#include <algorithm>
#include <cstring>
#include <sstream>
#include <string>
#include <string_view>
namespace whiteout::storages::casc {
using storages::common::hexDecode16;
using storages::common::jenkinsHash;
namespace {
u32 parseLocaleFlags(std::string_view name) {
if (name == "All")
return 0xFFFFFFFF;
if (name == "enUS")
return 0x2;
if (name == "koKR")
return 0x4;
if (name == "frFR")
return 0x10;
if (name == "deDE")
return 0x20;
if (name == "zhCN")
return 0x40;
if (name == "esES")
return 0x80;
if (name == "zhTW")
return 0x100;
if (name == "enGB")
return 0x200;
if (name == "enCN")
return 0x400;
if (name == "enTW")
return 0x800;
if (name == "esMX")
return 0x1000;
if (name == "ruRU")
return 0x2000;
if (name == "ptBR")
return 0x4000;
if (name == "itIT")
return 0x8000;
if (name == "ptPT")
return 0x10000;
if (name == "plPL")
return 0x20000;
if (name == "jaJP")
return 0x40000;
if (name == "thTH")
return 0x80000;
return 0xFFFFFFFF;
}
}
bool S1Root::looksLikeS1Root(std::span<const u8> data) {
if (data.empty())
return false;
if (data[0] == '#')
return false;
if (data[0] < 0x20 || data[0] > 0x7E)
return false;
size_t const limit = std::min<size_t>(data.size(), 512);
size_t pipePos = 0, newlinePos = 0;
bool foundPipe = false, foundNewline = false;
for (size_t i = 0; i < limit; ++i) {
if (!foundPipe && data[i] == '|') {
pipePos = i;
foundPipe = true;
}
if (!foundNewline && (data[i] == '\n' || data[i] == '\r')) {
newlinePos = i;
foundNewline = true;
}
if (foundPipe && foundNewline)
break;
}
if (!foundPipe)
return false;
size_t const hexStart = pipePos + 1;
size_t const hexEnd = foundNewline ? newlinePos : limit;
size_t hexLen = 0;
for (size_t i = hexStart; i < hexEnd; ++i) {
char const c = static_cast<char>(data[i]);
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
++hexLen;
else
break;
}
return hexLen == 32;
}
std::unique_ptr<S1Root> S1Root::parse(std::span<const u8> data, interfaces::WorkerPool* ) {
if (data.empty())
return nullptr;
std::string_view text(reinterpret_cast<const char*>(data.data()), data.size());
if (text.size() >= 3 && text[0] == '\xEF' && text[1] == '\xBB' && text[2] == '\xBF')
text.remove_prefix(3);
auto root = std::make_unique<S1Root>();
size_t pos = 0;
while (pos < text.size()) {
size_t eol = text.find_first_of("\r\n", pos);
if (eol == std::string_view::npos)
eol = text.size();
std::string_view const line = text.substr(pos, eol - pos);
pos = eol;
if (pos < text.size() && text[pos] == '\r')
++pos;
if (pos < text.size() && text[pos] == '\n')
++pos;
if (line.empty() || line[0] == '#')
continue;
size_t const pipe = line.find('|');
if (pipe == std::string_view::npos || pipe == 0)
continue;
std::string_view const pathPart = line.substr(0, pipe);
std::string_view ckeyHex = line.substr(pipe + 1);
while (!ckeyHex.empty() && (ckeyHex.back() == ' ' || ckeyHex.back() == '\t'))
ckeyHex.remove_suffix(1);
if (ckeyHex.size() < 32)
continue; auto cKey = hexDecode16(ckeyHex.substr(0, 32));
std::string filePath;
u32 localeFlags = 0xFFFFFFFF; size_t const colon = pathPart.find(':');
if (colon != std::string_view::npos) {
filePath = std::string(pathPart.substr(0, colon));
localeFlags = parseLocaleFlags(pathPart.substr(colon + 1));
} else {
filePath = std::string(pathPart);
}
auto hash = jenkinsHash(filePath);
u64 const combinedHash = u64(hash.pc) | (u64(hash.pb) << 32);
RootEntry entry{};
entry.cKey = cKey;
entry.fileNameHash = combinedHash;
entry.localeFlags = localeFlags;
entry.contentFlags = 0;
entry.fileDataId = kInvalidFileDataId;
entry.path = std::move(filePath);
root->m_entries.push_back(std::move(entry));
}
if (root->m_entries.empty())
return nullptr;
root->buildIndices();
return root;
}
std::vector<const RootEntry*> S1Root::findByPath(const std::string& path) const {
return findByPathOrHash(path, m_entries, m_byPath, m_byNameHash);
}
std::vector<const RootEntry*> S1Root::findByFileDataId(u32 ,
FileIdHint ) const {
return {}; }
void S1Root::buildIndices() {
buildPathAndHashIndex(m_byPath, m_byNameHash, m_entries);
}
}