mirror of
https://github.com/pret/pokeplatinum.git
synced 2026-03-21 17:55:13 -05:00
321 lines
10 KiB
C
321 lines
10 KiB
C
#ifndef POKEPLATINUM_DATAGEN_H
|
|
#define POKEPLATINUM_DATAGEN_H
|
|
|
|
/*
|
|
* This is a shareable header of utility functions which are of use to data generation
|
|
* and packing programs.
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <ostream>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
// clang-format off
|
|
#include <nitroarc.h>
|
|
#include <rapidjson/document.h>
|
|
#include <rapidjson/error/en.h>
|
|
#include <rapidjson/filewritestream.h>
|
|
#include <rapidjson/writer.h>
|
|
// clang-format on
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
// This attribute is useful when working with structures that have an element
|
|
// which must be word-aligned. For an example (and reasoning), see
|
|
// `SpeciesEvolutionList` in `datagen_species.cpp`.
|
|
#define ALIGN_4 __attribute__((aligned(4)))
|
|
|
|
typedef uint8_t u8;
|
|
typedef uint16_t u16;
|
|
typedef uint32_t u32;
|
|
typedef int8_t s8;
|
|
typedef int16_t s16;
|
|
typedef int32_t s32;
|
|
typedef bool BOOL;
|
|
|
|
#define TRUE true
|
|
#define FALSE false
|
|
|
|
// A lookup-table entry, mirroring the definition of structures generated by `metang`.
|
|
struct LookupEntry {
|
|
const long value;
|
|
const char *def;
|
|
};
|
|
|
|
// Search through a lookup-table for a string value.
|
|
static inline long Search(const LookupEntry *lookupTable, int low, int high, const char *val)
|
|
{
|
|
while (low <= high) {
|
|
int mid = low + (high - low) / 2;
|
|
|
|
if (strcmp(val, lookupTable[mid].def) == 0) {
|
|
return lookupTable[mid].value;
|
|
}
|
|
|
|
const char *val_p = val;
|
|
const char *def_p = lookupTable[mid].def;
|
|
while (*val_p && *def_p && *val_p == *def_p) {
|
|
val_p++;
|
|
def_p++;
|
|
}
|
|
|
|
if (*val_p > *def_p) {
|
|
low = mid + 1;
|
|
} else {
|
|
high = mid - 1;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// Lookup a constant from a lookup-table. If the value is not found, then an
|
|
// `invalid_argument` exception will be thrown. A C-string is taken for the
|
|
// lookup-value for compatibility with rapidjson, which only uses C-strings.
|
|
static inline long Lookup(const LookupEntry *lookupTable, int low, int high, const char *val, const std::string &valDesc)
|
|
{
|
|
long result = Search(lookupTable, low, high, val);
|
|
if (result < 0) {
|
|
std::stringstream buf;
|
|
buf << "no match found for " << val << " as " << valDesc;
|
|
throw std::invalid_argument(buf.str());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// A C++-string wrapper to lookup a constant from a lookup table.
|
|
static inline long Lookup(const LookupEntry *lookupTable, int low, int high, const std::string &val, const std::string &valDesc)
|
|
{
|
|
return Lookup(lookupTable, low, high, val.c_str(), valDesc);
|
|
}
|
|
|
|
// This macro is a standardized bit of short-hand to lookup a constant value
|
|
// of a particular type. e.g., to lookup an Ability constant string's value, you
|
|
// would invoke `LookupConst(abilityString, Ability)`; this would expand to:
|
|
// `Lookup((LookupEntry *)lookup__Ability, 0, num__Ability, abilityString, "Ability")`
|
|
#define LookupConst(val, T) Lookup(reinterpret_cast<const LookupEntry *>(lookup__##T), 0, lengthof__##T, val, #T)
|
|
|
|
// Align a value to the next 4-byte word-boundary.
|
|
static inline long AlignToWord(long val)
|
|
{
|
|
return val + (-val & 3);
|
|
}
|
|
|
|
// Tokenize a string by a given delimiter into a vector of components.
|
|
static inline std::vector<std::string> Tokenize(const std::string &s, const char delim = ' ')
|
|
{
|
|
std::vector<std::string> tokens;
|
|
std::size_t start = s.find_first_not_of(delim, 0);
|
|
std::size_t end = 0;
|
|
while (start != std::string::npos) {
|
|
end = s.find_first_of(delim, start);
|
|
tokens.emplace_back(s.substr(start, end - start));
|
|
start = s.find_first_not_of(delim, end);
|
|
}
|
|
|
|
return tokens;
|
|
}
|
|
|
|
// Wrapper class around nitroarc's packing API
|
|
class NarcBuilder {
|
|
nitroarc_packer_t packer = { 0 };
|
|
|
|
static void* libc_malloc(void *ctx, unsigned items, unsigned size) {
|
|
(void)ctx;
|
|
|
|
return malloc(items * size);
|
|
}
|
|
|
|
static void libc_free(void *ctx, void *ptr, unsigned items, unsigned size) {
|
|
(void)ctx;
|
|
(void)items;
|
|
(void)size;
|
|
|
|
free(ptr);
|
|
}
|
|
|
|
static void* libc_realloc(void *ctx, void *ptr, unsigned items, unsigned size) {
|
|
(void)ctx;
|
|
|
|
return realloc(ptr, items * size);
|
|
}
|
|
|
|
public:
|
|
NarcBuilder(std::size_t num_files, bool named = false, bool stripped = false) {
|
|
packer.malloc = libc_malloc;
|
|
packer.realloc = libc_realloc;
|
|
packer.free = libc_free;
|
|
|
|
if (int errc = nitroarc_pinit(&this->packer, num_files, named, stripped); errc) {
|
|
throw std::runtime_error(nitroarc_errs(errc));
|
|
}
|
|
}
|
|
|
|
struct Span {
|
|
std::byte *ptr;
|
|
u32 size;
|
|
};
|
|
|
|
void append(std::byte *ptr, u32 size, char *name = nullptr) {
|
|
if (int errc = nitroarc_ppack(&this->packer, ptr, size, name); errc) {
|
|
throw std::runtime_error(nitroarc_errs(errc));
|
|
}
|
|
}
|
|
|
|
void append(Span &span, char *name = nullptr) { append(span.ptr, span.size, name); }
|
|
|
|
Span build() {
|
|
void *result = nullptr;
|
|
u32 size = 0;
|
|
if (int errc = nitroarc_pseal(&this->packer, &result, &size); errc) {
|
|
throw std::runtime_error(nitroarc_errs(errc));
|
|
}
|
|
|
|
return Span { reinterpret_cast<std::byte *>(result), size };
|
|
}
|
|
|
|
void write(fs::path path) {
|
|
std::ofstream ofs(path);
|
|
Span narc = this->build();
|
|
ofs.write(reinterpret_cast<char *>(narc.ptr), narc.size);
|
|
free(narc.ptr);
|
|
}
|
|
|
|
template <typename T>
|
|
static void write(std::vector<T> &elems, fs::path path, char *name = nullptr) {
|
|
if (elems.empty()) return;
|
|
|
|
NarcBuilder b { 1 };
|
|
b.append(reinterpret_cast<std::byte *>(elems.data()), sizeof(elems[0]) * elems.size(), name);
|
|
b.write(path);
|
|
}
|
|
};
|
|
|
|
// Read a whole file into a C++-string.
|
|
static inline std::string ReadWholeFile(std::ifstream &ifs)
|
|
{
|
|
ifs.seekg(0, std::ios::end);
|
|
size_t size = ifs.tellg();
|
|
std::string contents(size, ' ');
|
|
ifs.seekg(0);
|
|
ifs.read(&contents[0], size);
|
|
|
|
return contents;
|
|
}
|
|
|
|
// Read a whole file into a C++-string.
|
|
// This routine is a potential exit-point if the file cannot be loaded for reading.
|
|
static inline std::string ReadWholeFile(fs::path &fname)
|
|
{
|
|
std::ifstream ifs(fname, std::ios::in);
|
|
if (ifs.fail()) {
|
|
std::cerr << "could not open file " << fs::relative(fname).generic_string() << std::endl;
|
|
std::exit(EXIT_FAILURE);
|
|
}
|
|
|
|
return ReadWholeFile(ifs);
|
|
}
|
|
|
|
// Read a file's lines into a vector of C++ strings.
|
|
static inline std::vector<std::string> ReadFileLines(fs::path &fname)
|
|
{
|
|
std::ifstream ifs(fname);
|
|
std::vector<std::string> lines;
|
|
std::string line;
|
|
while (std::getline(ifs, line)) {
|
|
lines.emplace_back(line);
|
|
}
|
|
|
|
return lines;
|
|
}
|
|
|
|
// Read a registry-type environment variable into a vector of C++ strings.
|
|
static inline std::vector<std::string> ReadRegistryEnvVar(const char *var)
|
|
{
|
|
const char *val_p = NULL;
|
|
if ((val_p = std::getenv(var)) == NULL) {
|
|
std::cerr << "Missing definition for registry environment variable " << var << std::endl;
|
|
std::exit(EXIT_FAILURE);
|
|
}
|
|
|
|
std::string val = val_p;
|
|
return Tokenize(val, ';');
|
|
}
|
|
|
|
struct Slice {
|
|
long begin;
|
|
long length;
|
|
};
|
|
|
|
static inline void ReportJsonError(rapidjson::ParseResult ok, std::string &json, fs::path &sourcepath)
|
|
{
|
|
std::vector<Slice> linecoords { Slice { 0, 0 } };
|
|
for (int i = 0; i < json.length(); i++) {
|
|
if (json.at(i) == '\n') {
|
|
linecoords.back().length = i - linecoords.back().begin;
|
|
linecoords.emplace_back(Slice { i + 1, 0 });
|
|
}
|
|
}
|
|
linecoords.back().length = json.length() - linecoords.back().begin;
|
|
|
|
auto line = std::find_if(linecoords.begin(), linecoords.end(), [&ok](Slice slice) {
|
|
return slice.length + slice.begin >= ok.Offset();
|
|
});
|
|
auto lineidx = std::distance(linecoords.begin(), line);
|
|
auto linenum = lineidx + 1;
|
|
auto colnum = ok.Offset() - line->begin + 1;
|
|
|
|
std::cerr << "\033[1;37m" << fs::relative(sourcepath).generic_string() << ":" << linenum << ":" << colnum << ": ";
|
|
std::cerr << "\033[1;31merror: ";
|
|
std::cerr << "\033[1;37m" << rapidjson::GetParseError_En(ok.Code());
|
|
std::cerr << "\033[0m" << std::endl;
|
|
|
|
if (lineidx > 0) {
|
|
auto &prev = linecoords.at(lineidx - 1);
|
|
std::cerr << std::setw(5) << linenum - 1 << " | " << json.substr(prev.begin, prev.length) << std::endl;
|
|
}
|
|
|
|
std::cerr << std::setw(5) << linenum << " | " << "\033[0;31m" << json.substr(line->begin, line->length) << "\033[0m" << std::endl;
|
|
|
|
if (lineidx < linecoords.size() - 1) {
|
|
auto &next = linecoords.at(lineidx + 1);
|
|
std::cerr << std::setw(5) << linenum + 1 << " | " << json.substr(next.begin, next.length) << std::endl;
|
|
}
|
|
}
|
|
|
|
static inline void CopyMessage(const rapidjson::Value &member, rapidjson::Value &outMessage, rapidjson::MemoryPoolAllocator<> &allocator)
|
|
{
|
|
if (member.HasMember("en_US")) {
|
|
if (member["en_US"].IsArray()) {
|
|
rapidjson::Value strings(rapidjson::kArrayType);
|
|
for (const auto &line : member["en_US"].GetArray()) {
|
|
std::string str = line.GetString();
|
|
rapidjson::Value string(rapidjson::kStringType);
|
|
string.SetString(str.c_str(), static_cast<rapidjson::SizeType>(str.length()), allocator);
|
|
strings.PushBack(string, allocator);
|
|
}
|
|
outMessage.AddMember("en_US", strings, allocator);
|
|
} else {
|
|
std::string str = member["en_US"].GetString();
|
|
rapidjson::Value string(rapidjson::kStringType);
|
|
string.SetString(str.c_str(), static_cast<rapidjson::SizeType>(str.length()), allocator);
|
|
outMessage.AddMember("en_US", string, allocator);
|
|
}
|
|
} else if (member.HasMember("garbage")) {
|
|
outMessage.AddMember("garbage", member["garbage"].GetInt(), allocator);
|
|
}
|
|
}
|
|
|
|
#endif // POKEPLATINUM_DATAGEN_H
|