Porytiles
Loading...
Searching...
No Matches
incbin_declaration_appender.cpp
Go to the documentation of this file.
2
3#include <cctype>
4#include <filesystem>
5#include <fstream>
6#include <string>
7#include <utility>
8#include <vector>
9
10#include "fmt/format.h"
11
15
16namespace {
17
18using namespace porytiles;
19
20// Project source file paths
21const std::filesystem::path graphics_rel_path = std::filesystem::path{"src"} / "data" / "tilesets" / "graphics.h";
22const std::filesystem::path metatiles_rel_path = std::filesystem::path{"src"} / "data" / "tilesets" / "metatiles.h";
23
24const std::string tileset_prefix = "gTileset_";
25const std::string porytiles_managed_suffix = "PorytilesManaged_";
26
28[[nodiscard]] std::string extract_shorthand(const std::string &tileset_name)
29{
30 if (!tileset_name.starts_with(tileset_prefix)) {
31 return "";
32 }
33 return tileset_name.substr(tileset_prefix.size());
34}
35
37[[nodiscard]] std::string
38generate_tiles_declaration(const std::string &shorthand, const std::string &bin_path_base, const std::string &snake_dir)
39{
40 return fmt::format(
41 "const u32 gTilesetTiles_{}{}[] = INCBIN_U32(\"{}/{}/porytiles_bin/tiles.4bpp.lz\");",
42 porytiles_managed_suffix,
43 shorthand,
44 bin_path_base,
45 snake_dir);
46}
47
49[[nodiscard]] std::vector<std::string> generate_palettes_declaration(
50 const std::string &shorthand,
51 const std::string &bin_path_base,
52 const std::string &snake_dir,
53 std::size_t num_palettes)
54{
55 std::vector<std::string> lines;
56
57 lines.push_back(fmt::format("const u16 gTilesetPalettes_{}{}[][16] =", porytiles_managed_suffix, shorthand));
58 lines.emplace_back("{");
59
60 for (std::size_t i = 0; i < num_palettes; ++i) {
61 const std::string comma = (i < num_palettes - 1) ? "," : "";
62 lines.push_back(
63 fmt::format(
64 " INCBIN_U16(\"{}/{}/porytiles_bin/palettes/{:02}.gbapal\"){}", bin_path_base, snake_dir, i, comma));
65 }
66
67 lines.emplace_back("};");
68 return lines;
69}
70
72[[nodiscard]] std::string generate_metatiles_declaration(
73 const std::string &shorthand, const std::string &bin_path_base, const std::string &snake_dir)
74{
75 return fmt::format(
76 "const u16 gMetatiles_{}{}[] = INCBIN_U16(\"{}/{}/porytiles_bin/metatiles.bin\");",
77 porytiles_managed_suffix,
78 shorthand,
79 bin_path_base,
80 snake_dir);
81}
82
90[[nodiscard]] std::string generate_attributes_declaration(
91 const std::string &shorthand,
92 const std::string &bin_path_base,
93 const std::string &snake_dir,
94 std::size_t attribute_bytes)
95{
96 const std::string c_type = (attribute_bytes == 4) ? "u32" : (attribute_bytes == 1) ? "u8" : "u16";
97 const std::string incbin_macro = (attribute_bytes == 4) ? "INCBIN_U32"
98 : (attribute_bytes == 1) ? "INCBIN_U8"
99 : "INCBIN_U16";
100 return fmt::format(
101 "const {} gMetatileAttributes_{}{}[] = {}(\"{}/{}/porytiles_bin/metatile_attributes.bin\");",
102 c_type,
103 porytiles_managed_suffix,
104 shorthand,
105 incbin_macro,
106 bin_path_base,
107 snake_dir);
108}
109
112read_file_lines(const std::filesystem::path &path, const TextFormatter *format)
113{
114 std::ifstream in{path};
115 if (!in.is_open()) {
116 return FormattableError{
117 format->format("{}: failed to open for reading", FormatParam{path.string(), Style::bold})};
118 }
119
120 std::vector<std::string> lines;
121 std::string line;
122 while (std::getline(in, line)) {
123 lines.push_back(line);
124 }
125 return lines;
126}
127
129[[nodiscard]] ChainableResult<void>
130write_file_lines(const std::filesystem::path &path, const std::vector<std::string> &lines, const TextFormatter *format)
131{
132 std::ofstream out{path};
133 if (!out.is_open()) {
134 return FormattableError{
135 format->format("{}: failed to open for writing", FormatParam{path.string(), Style::bold})};
136 }
137
138 for (const auto &line : lines) {
139 out << line << '\n';
140 }
141 out.flush();
142
143 if (out.fail()) {
144 return FormattableError{format->format("{}: failed to write file", FormatParam{path.string(), Style::bold})};
145 }
146
147 return {};
148}
149
156[[nodiscard]] bool is_porytiles_managed_declaration(const std::string &line, const std::string &shorthand)
157{
158 const std::string pattern = porytiles_managed_suffix + shorthand;
159 for (std::size_t pos = line.find(pattern); pos != std::string::npos; pos = line.find(pattern, pos + 1)) {
160 const std::size_t after = pos + pattern.size();
161 if (after >= line.size()) {
162 return true;
163 }
164 const auto next = static_cast<unsigned char>(line[after]);
165 if (std::isalnum(next) == 0 && next != '_') {
166 return true;
167 }
168 }
169 return false;
170}
171
177[[nodiscard]] std::vector<std::string>
178strip_graphics_declarations(const std::vector<std::string> &lines, const std::string &shorthand)
179{
180 std::vector<std::string> filtered_lines;
181 bool in_palette_array = false;
182
183 for (const auto &line : lines) {
184 if (is_porytiles_managed_declaration(line, shorthand)) {
185 // Check if this starts a multi-line declaration
186 if (line.find("[][16] =") != std::string::npos) {
187 in_palette_array = true;
188 }
189 // Skip this line (single-line declaration or start of multi-line)
190 continue;
191 }
192
193 if (in_palette_array) {
194 // Skip lines until we find the closing brace
195 if (line.find("};") != std::string::npos) {
196 in_palette_array = false;
197 }
198 continue;
199 }
200
201 filtered_lines.push_back(line);
202 }
203
204 return filtered_lines;
205}
206
211[[nodiscard]] std::vector<std::string>
212strip_metatiles_declarations(const std::vector<std::string> &lines, const std::string &shorthand)
213{
214 std::vector<std::string> filtered_lines;
215 for (const auto &line : lines) {
216 if (!is_porytiles_managed_declaration(line, shorthand)) {
217 filtered_lines.push_back(line);
218 }
219 }
220 return filtered_lines;
221}
222
224void trim_trailing_blank_lines(std::vector<std::string> &lines)
225{
226 while (!lines.empty() && lines.back().find_first_not_of(" \t") == std::string::npos) {
227 lines.pop_back();
228 }
229}
230
231} // namespace
232
233namespace porytiles {
234
236 std::filesystem::path project_root, gsl::not_null<const TextFormatter *> format)
237 : project_root_{std::move(project_root)}, format_{format}
238{
239}
240
242 const std::string &tileset_name, const std::string &bin_path_base, std::size_t num_palettes) const
243{
244 const std::string shorthand = extract_shorthand(tileset_name);
245 if (shorthand.empty()) {
246 return FormattableError{format_->format(
247 "tileset name '{}' does not start with 'gTileset_'", FormatParam{tileset_name, Style::bold})};
248 }
249
250 const std::string snake_dir = DynamicCasedName{shorthand}.to_snake_case();
251 const auto graphics_path = project_root_ / graphics_rel_path;
252
253 // Read existing file
255 lines,
256 read_file_lines(graphics_path, format_),
257 void,
258 "Failed to read graphics.h for tileset '{}'.",
259 FormatParam(tileset_name, Style::bold));
260
261 // Generate declarations
262 const std::string tiles_decl = generate_tiles_declaration(shorthand, bin_path_base, snake_dir);
263 auto palettes_decl_lines = generate_palettes_declaration(shorthand, bin_path_base, snake_dir, num_palettes);
264
265 // Remove any existing managed declarations (wherever they are, including copies misplaced inside a trailing
266 // preprocessor conditional), then append fresh ones after the last non-blank line, which is always at preprocessor
267 // conditional depth 0.
268 lines = strip_graphics_declarations(lines, shorthand);
269 trim_trailing_blank_lines(lines);
270
271 lines.emplace_back("");
272 lines.push_back(tiles_decl);
273 lines.emplace_back("");
274 lines.append_range(palettes_decl_lines);
275
276 // Write file back
277 return write_file_lines(graphics_path, lines, format_);
278}
279
281 const std::string &tileset_name, const std::string &bin_path_base, std::size_t attribute_bytes) const
282{
283 const std::string shorthand = extract_shorthand(tileset_name);
284 if (shorthand.empty()) {
285 return FormattableError{format_->format(
286 "tileset name '{}' does not start with 'gTileset_'", FormatParam{tileset_name, Style::bold})};
287 }
288
289 const std::string snake_dir = DynamicCasedName{shorthand}.to_snake_case();
290 const auto metatiles_path = project_root_ / metatiles_rel_path;
291
292 // Read existing file
294 lines,
295 read_file_lines(metatiles_path, format_),
296 void,
297 "Failed to read metatiles.h for tileset '{}'.",
298 FormatParam(tileset_name, Style::bold));
299
300 // Generate declarations
301 const std::string metatiles_decl = generate_metatiles_declaration(shorthand, bin_path_base, snake_dir);
302 const std::string attributes_decl =
303 generate_attributes_declaration(shorthand, bin_path_base, snake_dir, attribute_bytes);
304
305 // Remove any existing managed declarations (wherever they are, including copies misplaced inside a trailing
306 // preprocessor conditional), then append fresh ones after the last non-blank line, which is always at preprocessor
307 // conditional depth 0.
308 lines = strip_metatiles_declarations(lines, shorthand);
309 trim_trailing_blank_lines(lines);
310
311 lines.emplace_back("");
312 lines.push_back(metatiles_decl);
313 lines.push_back(attributes_decl);
314
315 // Write file back
316 return write_file_lines(metatiles_path, lines, format_);
317}
318
320{
321 const std::string shorthand = extract_shorthand(tileset_name);
322 if (shorthand.empty()) {
323 return FormattableError{format_->format(
324 "tileset name '{}' does not start with 'gTileset_'", FormatParam{tileset_name, Style::bold})};
325 }
326
327 // Remove from graphics.h
328 {
329 const auto graphics_path = project_root_ / graphics_rel_path;
330
332 lines,
333 read_file_lines(graphics_path, format_),
334 void,
335 "Failed to read graphics.h for tileset '{}'.",
336 FormatParam(tileset_name, Style::bold));
337
338 const std::vector<std::string> filtered_lines = strip_graphics_declarations(lines, shorthand);
339
340 auto write_result = write_file_lines(graphics_path, filtered_lines, format_);
341 if (!write_result.has_value()) {
342 return write_result;
343 }
344 }
345
346 // Remove from metatiles.h
347 {
348 const auto metatiles_path = project_root_ / metatiles_rel_path;
349
351 lines,
352 read_file_lines(metatiles_path, format_),
353 void,
354 "Failed to read metatiles.h for tileset '{}'.",
355 FormatParam(tileset_name, Style::bold));
356
357 const std::vector<std::string> filtered_lines = strip_metatiles_declarations(lines, shorthand);
358
359 auto write_result = write_file_lines(metatiles_path, filtered_lines, format_);
360 if (!write_result.has_value()) {
361 return write_result;
362 }
363 }
364
365 return {};
366}
367
368} // namespace porytiles
#define PT_TRY_ASSIGN_CHAIN_ERR(var, expr, return_type,...)
Unwraps a ChainableResult, chaining a new error message on failure.
A result type that maintains a chainable sequence of errors for debugging and error reporting.
A smart string wrapper that preserves word structure for lossless case format conversion.
std::string to_snake_case() const
Outputs all words flattened and joined with underscores.
A text parameter with associated styling for formatted output.
General-purpose error implementation with formatted message support.
Definition error.hpp:57
ChainableResult< void > append_graphics_declarations(const std::string &tileset_name, const std::string &bin_path_base, std::size_t num_palettes) const
Appends INCBIN declarations for a Porytiles-managed tileset to graphics.h.
ChainableResult< void > remove_declarations(const std::string &tileset_name) const
Removes INCBIN declarations for a Porytiles-managed tileset (for restore workflow).
IncbinDeclarationAppender(std::filesystem::path project_root, gsl::not_null< const TextFormatter * > format)
Constructs an IncbinDeclarationAppender with required dependencies.
ChainableResult< void > append_metatiles_declarations(const std::string &tileset_name, const std::string &bin_path_base, std::size_t attribute_bytes) const
Appends INCBIN declarations for a Porytiles-managed tileset to metatiles.h.
static const Style bold
Bold text formatting.
Abstract base class for applying text styling with context-aware formatting.
virtual std::string format(const std::string &format_str, const std::vector< FormatParam > &params) const
Formats a string with styled parameters using fmtlib syntax.
constexpr std::size_t num_palettes
Definition palette.hpp:21