Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
ArchiveEnglishText.cpp
Go to the documentation of this file.
1#include "ArchiveAssembly.hpp"
2
3#include <charconv>
4#include <fstream>
5#include <iterator>
6#include <limits>
7#include <set>
8#include <sstream>
9#include <stdexcept>
10#include <string_view>
11
13{
14 namespace
15 {
16 std::string readAscii(const std::filesystem::path& path)
17 {
18 if (std::filesystem::file_size(path) > 16U * 1024U * 1024U)
19 throw std::runtime_error("text input exceeds 16 MiB: " + path.string());
20 std::ifstream stream(path, std::ios::binary);
21 if (!stream) throw std::runtime_error("cannot open " + path.string());
22 std::string text{std::istreambuf_iterator<char>(stream), {}};
23 if (stream.bad()) throw std::runtime_error("cannot read " + path.string());
24 for (unsigned char c : text)
25 if (c == 0 || c > 127)
26 throw std::runtime_error("explicit text encoding required for " + path.string());
27 return text;
28 }
29
30 struct AtrReader
31 {
32 std::string_view text;
33 std::size_t offset{};
34 [[noreturn]] void fail(std::string message) const
35 { throw std::runtime_error("ATR byte " + std::to_string(offset) + ": " + message); }
36 void skip()
37 {
38 while (offset < text.size())
39 {
40 if (text[offset] == ' ' || text[offset] == '\t' ||
41 text[offset] == '\r' || text[offset] == '\n') { ++offset; continue; }
42 if (text.substr(offset, 2) == "//")
43 {
44 const auto end = text.find('\n', offset + 2);
45 offset = end == std::string_view::npos ? text.size() : end + 1;
46 }
47 else if (text.substr(offset, 2) == "/*")
48 {
49 const auto end = text.find("*/", offset + 2);
50 if (end == std::string_view::npos) fail("unterminated comment");
51 offset = end + 2;
52 }
53 else break;
54 }
55 }
56 std::string word()
57 {
58 skip();
59 const auto start = offset;
60 while (offset < text.size())
61 {
62 const char c = text[offset];
63 if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
64 (c >= '0' && c <= '9') || c == '_' || c == '.')) break;
65 ++offset;
66 }
67 if (start == offset) fail("expected a token");
68 return std::string(text.substr(start, offset - start));
69 }
70 std::string message()
71 {
72 skip();
73 if (offset == text.size() || text[offset++] != '{') fail("expected message brace");
74 const auto start = offset;
75 const auto end = text.find('}', start);
76 if (end == std::string_view::npos) fail("unterminated message");
77 const auto nested = text.find('{', start);
78 if (nested != std::string_view::npos && nested < end) fail("nested message brace");
79 offset = end + 1;
80 // Do not trim, expand ^ placeholders or remove comments in messages.
81 return std::string(text.substr(start, end - start));
82 }
83 };
84 void u16(DataBytes& out, std::uint16_t value)
85 {
86 out.push_back(static_cast<std::byte>(value & 255U));
87 out.push_back(static_cast<std::byte>(value >> 8U));
88 }
89 void u32(DataBytes& out, std::uint32_t value)
90 { u16(out, static_cast<std::uint16_t>(value)); u16(out, static_cast<std::uint16_t>(value >> 16U)); }
91 }
92
93 EnglishText readEnglishText(const std::filesystem::path& definitions,
94 const std::filesystem::path& atr)
95 {
96 EnglishText result;
97 std::map<std::string, std::uint32_t> symbols;
98 auto generated = readAscii(definitions);
99 // dat_lang.h appends hand-written aliases, separators and enum bases.
100 // They are not additional messages and must not become required IDs.
101 const auto extras = generated.find("/* Extra stuff (not automatically generated)");
102 if (extras != std::string::npos) generated.resize(extras);
103 std::istringstream header(generated);
104 std::string line;
105 while (std::getline(header, line))
106 {
107 std::istringstream tokens(line);
108 std::string define, name, number, trailing;
109 if (!(tokens >> define) || define != "#define") continue;
110 if (!(tokens >> name >> number))
111 throw std::runtime_error("malformed language definition: " + line);
112 std::uint32_t id{};
113 const auto parsed = std::from_chars(number.data(), number.data() + number.size(), id);
114 if (parsed.ec != std::errc{} || parsed.ptr != number.data() + number.size() ||
115 ((tokens >> trailing) && !trailing.starts_with("//")))
116 throw std::runtime_error("non-literal language definition: " + name);
117 if (!symbols.emplace(name, id).second)
118 throw std::runtime_error("duplicate language definition: " + name);
119 result.requiredNames.try_emplace(id, name);
120 }
121 if (symbols.empty()) throw std::runtime_error("language definitions are empty");
122 const auto source = readAscii(atr);
123 AtrReader reader{source};
124 if (reader.word() != "_32Bit" || reader.word() != "_32Bit" ||
125 reader.word() != "_String" || reader.word() != "252")
126 reader.fail("unsupported ATR schema; expected two 32-bit fields and String 252");
127 std::set<std::string> seen;
128 while (true)
129 {
130 reader.skip();
131 if (reader.offset == source.size()) break;
132 if (reader.word() != ".ITEM") reader.fail("expected .ITEM");
133 const auto symbol = reader.word();
134 const auto sound = reader.word();
135 auto message = reader.message();
136 if (!seen.insert(symbol).second) reader.fail("duplicate message " + symbol);
137 if (sound != "WAV_NO_SOUND")
138 reader.fail("audio mapping requires a separate supplied sound bank: " + sound);
139 const auto found = symbols.find(symbol);
140 if (found == symbols.end())
141 {
142 result.unmappedSymbols.push_back(symbol);
143 continue;
144 }
145 const auto [item, inserted] = result.text.emplace(found->second, message);
146 if (!inserted && item->second != message)
147 reader.fail("conflicting aliases for language ID " + std::to_string(found->second));
148 }
149 return result;
150 }
151
152 std::vector<ArchiveBuildItem> buildEnglishItems(const EnglishText& input)
153 {
154 if (input.text.empty()) return {};
156 throw std::runtime_error("language strings exceed DataTag capacity");
157 std::vector<ArchiveBuildItem> items(1);
158 items[0].type = LegacyDataType::IndexTable;
159 for (const auto& [id, message] : input.text)
160 {
161 u32(items[0].payload, id);
162 u16(items[0].payload, static_cast<std::uint16_t>(items.size()));
164 for (unsigned char character : message) u16(text, character);
165 u16(text, 0);
166 items.push_back({LegacyDataType::String, std::move(text)});
167 }
168 return items;
169 }
170}
std::size_t offset
const char * name
const char * header
std::string_view text
std::shared_ptr< const data::HmdTextureImage > source
unsigned index
std::string path
Bytes input
std::uint16_t value
Definition Timers.cpp:16
data::DataId out
static constexpr std::size_t MaximumItemCount
EnglishText readEnglishText(const std::filesystem::path &definitions, const std::filesystem::path &atr)
std::vector< ArchiveBuildItem > buildEnglishItems(const EnglishText &input)
std::vector< std::byte > DataBytes
std::map< std::uint32_t, std::string > requiredNames
std::vector< std::string > unmappedSymbols
std::map< std::uint32_t, std::string > text