Files
mirkobrombin a3bcf00168 chore: replace the ReactOS code page table tool
Assisted-by: Claude Code:claude-opus-5-5
AI-Scope: Wrote the build tool that turns the code page data into its NLS table as own code and checked its output against the pinned table.
2026-10-30 12:21:37 +00:00

114 lines
4.1 KiB
C++

// Builds a single-byte code page's NLS file from its text description, the form the Windows Supported Code Page Data Files are published in.
// It runs during the build: the result is checked against a known hash before it is embedded.
#include <algorithm>
#include <array>
#include <charconv>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
namespace {
uint16_t number(const std::string &text)
{
const bool hex = text.starts_with("0x") || text.starts_with("0X");
const char *first = text.data() + (hex ? 2 : 0), *last = text.data() + text.size();
unsigned value = 0;
const auto [end, error] = std::from_chars(first, last, value, hex ? 16 : 10);
if (error != std::errc() || end != last || value > 0xffff)
throw std::runtime_error("not a 16-bit number: " + text);
return uint16_t(value);
}
// The next line's words, without its comment; none at the end of the file.
std::vector<std::string> next_words(std::istream &input)
{
for (std::string line; std::getline(input, line);) {
std::istringstream stream(line.substr(0, line.find(';')));
std::vector<std::string> words;
for (std::string word; stream >> word;)
words.push_back(word);
if (!words.empty())
return words;
}
return {};
}
void put(std::vector<uint8_t> &out, uint16_t value)
{
out.push_back(uint8_t(value));
out.push_back(uint8_t(value >> 8));
}
}
int main(int argc, char **argv)
{
try {
if (argc != 3)
throw std::runtime_error("usage: nls-tables <page.txt> <page.nls>");
std::ifstream input(argv[1]);
if (!input)
throw std::runtime_error(std::string("cannot read ") + argv[1]);
uint16_t page = 0, default_byte = 0, default_wide = 0;
std::array<uint16_t, 256> to_wide{};
std::vector<uint8_t> to_byte(65536);
bool bytes_seen = false, wide_seen = false, ended = false;
const auto entries = [&](const std::vector<std::string> &words, auto &&store) {
for (int left = number(words.at(1)); left > 0; --left) {
const auto entry = next_words(input);
if (entry.size() != 2)
throw std::runtime_error("expected a pair after " + words[0]);
store(number(entry[0]), number(entry[1]));
}
};
for (auto words = next_words(input); !words.empty() && !ended; words = next_words(input)) {
if (words[0] == "CODEPAGE")
page = number(words.at(1));
else if (words[0] == "CPINFO") {
if (number(words.at(1)) != 1)
throw std::runtime_error("only single-byte pages are built");
default_byte = number(words.at(2));
default_wide = number(words.at(3));
to_wide.fill(default_wide);
std::fill(to_byte.begin(), to_byte.end(), uint8_t(default_byte));
} else if (words[0] == "MBTABLE") {
bytes_seen = true;
entries(words, [&](uint16_t byte, uint16_t wide) { to_wide.at(byte) = wide; });
} else if (words[0] == "WCTABLE") {
wide_seen = true;
entries(words, [&](uint16_t wide, uint16_t byte) { to_byte[wide] = uint8_t(byte); });
} else if (words[0] == "ENDCODEPAGE")
ended = true;
else
throw std::runtime_error("unexpected section " + words[0]);
}
if (!bytes_seen || !wide_seen || !ended)
throw std::runtime_error("incomplete code page");
std::vector<uint8_t> out;
// The header: its size in words, the page, the longest character, both default characters and what each translates to, and no lead bytes.
for (const uint16_t field : {uint16_t(13), page, uint16_t(1), default_byte, default_wide, to_wide.at(default_byte), uint16_t(to_byte[default_wide])})
put(out, field);
out.resize(out.size() + 12);
// The words up to the Unicode table: the byte table, and no glyph table, lead bytes or flags.
put(out, uint16_t(to_wide.size() + 3));
for (const uint16_t wide : to_wide)
put(out, wide);
for (int empty = 0; empty < 3; ++empty)
put(out, 0);
out.insert(out.end(), to_byte.begin(), to_byte.end());
std::ofstream output(argv[2], std::ios::binary);
output.write(reinterpret_cast<const char *>(out.data()), std::streamsize(out.size()));
if (!output)
throw std::runtime_error(std::string("cannot write ") + argv[2]);
} catch (const std::exception &error) {
std::cerr << "nls-tables: " << error.what() << '\n';
return 1;
}
return 0;
}