Porytiles
Loading...
Searching...
No Matches
incbin_declaration_appender.cpp
Go to the documentation of this file.
2
3#include <filesystem>
4#include <fstream>
5#include <string>
6#include <utility>
7#include <vector>
8
9#include "fmt/format.h"
10
14
15namespace {
16
17using namespace porytiles;
18
19// Project source file paths
20const std::filesystem::path graphics_rel_path = std::filesystem::path{"src"} / "data" / "tilesets" / "graphics.h";
21const std::filesystem::path metatiles_rel_path = std::filesystem::path{"src"} / "data" / "tilesets" / "metatiles.h";
22
23const std::string tileset_prefix = "gTileset_";
24const std::string porytiles_managed_suffix = "PorytilesManaged_";
25
29[[nodiscard]] std::string extract_shorthand(const std::string &tileset_name)
30{
31 if (!tileset_name.starts_with(tileset_prefix)) {
32 return "";
33 }
34 return tileset_name.substr(tileset_prefix.size());
35}
36
40[[nodiscard]] std::string
41generate_tiles_declaration(const std::string &shorthand, const std::string &bin_path_base, const std::string &snake_dir)
42{
43 return fmt::format(
44 "const u32 gTilesetTiles_{}{}[] = INCBIN_U32(\"{}/{}/porytiles_bin/tiles.4bpp.lz\");",
45 porytiles_managed_suffix,
46 shorthand,
47 bin_path_base,
48 snake_dir);
49}
50
54[[nodiscard]] std::vector<std::string> generate_palettes_declaration(
55 const std::string &shorthand,
56 const std::string &bin_path_base,
57 const std::string &snake_dir,
58 std::size_t num_palettes)
59{
60 std::vector<std::string> lines;
61
62 lines.push_back(fmt::format("const u16 gTilesetPalettes_{}{}[][16] =", porytiles_managed_suffix, shorthand));
63 lines.emplace_back("{");
64
65 for (std::size_t i = 0; i < num_palettes; ++i) {
66 const std::string comma = (i < num_palettes - 1) ? "," : "";
67 lines.push_back(
68 fmt::format(
69 " INCBIN_U16(\"{}/{}/porytiles_bin/palettes/{:02}.gbapal\"){}", bin_path_base, snake_dir, i, comma));
70 }
71
72 lines.emplace_back("};");
73 return lines;
74}
75
79[[nodiscard]] std::string generate_metatiles_declaration(
80 const std::string &shorthand, const std::string &bin_path_base, const std::string &snake_dir)
81{
82 return fmt::format(
83 "const u16 gMetatiles_{}{}[] = INCBIN_U16(\"{}/{}/porytiles_bin/metatiles.bin\");",
84 porytiles_managed_suffix,
85 shorthand,
86 bin_path_base,
87 snake_dir);
88}
89
98[[nodiscard]] std::string generate_attributes_declaration(
99 const std::string &shorthand,
100 const std::string &bin_path_base,
101 const std::string &snake_dir,
102 std::size_t metatile_attr_size)
103{
104 const std::string c_type = (metatile_attr_size == 4) ? "u32" : "u16";
105 const std::string incbin_macro = (metatile_attr_size == 4) ? "INCBIN_U32" : "INCBIN_U16";
106 return fmt::format(
107 "const {} gMetatileAttributes_{}{}[] = {}(\"{}/{}/porytiles_bin/metatile_attributes.bin\");",
108 c_type,
109 porytiles_managed_suffix,
110 shorthand,
111 incbin_macro,
112 bin_path_base,
113 snake_dir);
114}
115
120read_file_lines(const std::filesystem::path &path, const TextFormatter *format)
121{
122 std::ifstream in{path};
123 if (!in.is_open()) {
124 return FormattableError{
125 format->format("{}: failed to open for reading", FormatParam{path.string(), Style::bold})};
126 }
127
128 std::vector<std::string> lines;
129 std::string line;
130 while (std::getline(in, line)) {
131 lines.push_back(line);
132 }
133 return lines;
134}
135
139[[nodiscard]] ChainableResult<void>
140write_file_lines(const std::filesystem::path &path, const std::vector<std::string> &lines, const TextFormatter *format)
141{
142 std::ofstream out{path};
143 if (!out.is_open()) {
144 return FormattableError{
145 format->format("{}: failed to open for writing", FormatParam{path.string(), Style::bold})};
146 }
147
148 for (const auto &line : lines) {
149 out << line << '\n';
150 }
151 out.flush();
152
153 if (out.fail()) {
154 return FormattableError{format->format("{}: failed to write file", FormatParam{path.string(), Style::bold})};
155 }
156
157 return {};
158}
159
167[[nodiscard]] std::size_t find_append_position(const std::vector<std::string> &lines)
168{
169 // Find the last non-empty, non-comment line
170 for (std::size_t i = lines.size(); i > 0; --i) {
171 const auto &line = lines[i - 1];
172 // Skip empty lines and preprocessor directives at the end
173 if (!line.empty() && !line.starts_with("#") && line.find_first_not_of(' ') != std::string::npos) {
174 return i;
175 }
176 }
177 return lines.size();
178}
179
183[[nodiscard]] bool is_porytiles_managed_declaration(const std::string &line, const std::string &shorthand)
184{
185 const std::string pattern = porytiles_managed_suffix + shorthand;
186 return line.find(pattern) != std::string::npos;
187}
188
189} // namespace
190
191namespace porytiles {
192
194 std::filesystem::path project_root, gsl::not_null<const TextFormatter *> format)
195 : project_root_{std::move(project_root)}, format_{format}
196{
197}
198
200 const std::string &tileset_name, const std::string &bin_path_base, std::size_t num_palettes) const
201{
202 const std::string shorthand = extract_shorthand(tileset_name);
203 if (shorthand.empty()) {
204 return FormattableError{format_->format(
205 "tileset name '{}' does not start with 'gTileset_'", FormatParam{tileset_name, Style::bold})};
206 }
207
208 const std::string snake_dir = DynamicCasedName{shorthand}.to_snake_case();
209 const auto graphics_path = project_root_ / graphics_rel_path;
210
211 // Read existing file
213 lines,
214 read_file_lines(graphics_path, format_),
215 void,
216 "Failed to read graphics.h for tileset '{}'.",
217 FormatParam(tileset_name, Style::bold));
218
219 // Generate declarations
220 const std::string tiles_decl = generate_tiles_declaration(shorthand, bin_path_base, snake_dir);
221 auto palettes_decl_lines = generate_palettes_declaration(shorthand, bin_path_base, snake_dir, num_palettes);
222
223 // Find position and append
224 const std::size_t pos = find_append_position(lines);
225
226 // Insert blank line separator
227 lines.insert(lines.begin() + static_cast<std::ptrdiff_t>(pos), "");
228
229 // Insert tiles declaration
230 lines.insert(lines.begin() + static_cast<std::ptrdiff_t>(pos) + 1, tiles_decl);
231
232 // Insert blank line before palettes
233 lines.insert(lines.begin() + static_cast<std::ptrdiff_t>(pos) + 2, "");
234
235 // Insert palettes declaration (multi-line)
236 for (std::size_t i = 0; i < palettes_decl_lines.size(); ++i) {
237 lines.insert(
238 lines.begin() + static_cast<std::ptrdiff_t>(pos) + 3 + static_cast<std::ptrdiff_t>(i),
239 palettes_decl_lines[i]);
240 }
241
242 // Write file back
243 return write_file_lines(graphics_path, lines, format_);
244}
245
247 const std::string &tileset_name, const std::string &bin_path_base, std::size_t metatile_attr_size) const
248{
249 const std::string shorthand = extract_shorthand(tileset_name);
250 if (shorthand.empty()) {
251 return FormattableError{format_->format(
252 "tileset name '{}' does not start with 'gTileset_'", FormatParam{tileset_name, Style::bold})};
253 }
254
255 const std::string snake_dir = DynamicCasedName{shorthand}.to_snake_case();
256 const auto metatiles_path = project_root_ / metatiles_rel_path;
257
258 // Read existing file
260 lines,
261 read_file_lines(metatiles_path, format_),
262 void,
263 "Failed to read metatiles.h for tileset '{}'.",
264 FormatParam(tileset_name, Style::bold));
265
266 // Generate declarations
267 const std::string metatiles_decl = generate_metatiles_declaration(shorthand, bin_path_base, snake_dir);
268 const std::string attributes_decl =
269 generate_attributes_declaration(shorthand, bin_path_base, snake_dir, metatile_attr_size);
270
271 // Find position and append
272 const std::size_t pos = find_append_position(lines);
273
274 // Insert blank line separator
275 lines.insert(lines.begin() + static_cast<std::ptrdiff_t>(pos), "");
276
277 // Insert metatiles declaration
278 lines.insert(lines.begin() + static_cast<std::ptrdiff_t>(pos) + 1, metatiles_decl);
279
280 // Insert attributes declaration
281 lines.insert(lines.begin() + static_cast<std::ptrdiff_t>(pos) + 2, attributes_decl);
282
283 // Write file back
284 return write_file_lines(metatiles_path, lines, format_);
285}
286
288{
289 const std::string shorthand = extract_shorthand(tileset_name);
290 if (shorthand.empty()) {
291 return FormattableError{format_->format(
292 "tileset name '{}' does not start with 'gTileset_'", FormatParam{tileset_name, Style::bold})};
293 }
294
295 // Remove from graphics.h
296 {
297 const auto graphics_path = project_root_ / graphics_rel_path;
298
300 lines,
301 read_file_lines(graphics_path, format_),
302 void,
303 "Failed to read graphics.h for tileset '{}'.",
304 FormatParam(tileset_name, Style::bold));
305
306 // Remove lines containing PorytilesManaged_{Shorthand}
307 // Also handle multi-line palette arrays by tracking brace depth
308 std::vector<std::string> filtered_lines;
309 bool in_palette_array = false;
310
311 for (const auto &line : lines) {
312 if (is_porytiles_managed_declaration(line, shorthand)) {
313 // Check if this starts a multi-line declaration
314 if (line.find("[][16] =") != std::string::npos) {
315 in_palette_array = true;
316 }
317 // Skip this line (single-line declaration or start of multi-line)
318 continue;
319 }
320
321 if (in_palette_array) {
322 // Skip lines until we find the closing brace
323 if (line.find("};") != std::string::npos) {
324 in_palette_array = false;
325 }
326 continue;
327 }
328
329 filtered_lines.push_back(line);
330 }
331
332 auto write_result = write_file_lines(graphics_path, filtered_lines, format_);
333 if (!write_result.has_value()) {
334 return write_result;
335 }
336 }
337
338 // Remove from metatiles.h
339 {
340 const auto metatiles_path = project_root_ / metatiles_rel_path;
341
343 lines,
344 read_file_lines(metatiles_path, format_),
345 void,
346 "Failed to read metatiles.h for tileset '{}'.",
347 FormatParam(tileset_name, Style::bold));
348
349 // Remove lines containing PorytilesManaged_{Shorthand}
350 std::vector<std::string> filtered_lines;
351 for (const auto &line : lines) {
352 if (!is_porytiles_managed_declaration(line, shorthand)) {
353 filtered_lines.push_back(line);
354 }
355 }
356
357 auto write_result = write_file_lines(metatiles_path, filtered_lines, format_);
358 if (!write_result.has_value()) {
359 return write_result;
360 }
361 }
362
363 return {};
364}
365
366} // 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:63
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 > append_metatiles_declarations(const std::string &tileset_name, const std::string &bin_path_base, std::size_t metatile_attr_size) const
Appends INCBIN declarations for a Porytiles-managed tileset to metatiles.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.
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.