yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_collision_commands.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <array>
5#include <cstdint>
6#include <fstream>
7#include <sstream>
8#include <string>
9#include <unordered_map>
10#include <unordered_set>
11#include <vector>
12
13#include "absl/status/status.h"
14#include "absl/strings/ascii.h"
15#include "absl/strings/str_format.h"
16#include "absl/strings/str_split.h"
17#include "absl/types/span.h"
18#include "cli/util/hex_util.h"
19#include "nlohmann/json.hpp"
20#include "rom/rom.h"
21#include "util/macro.h"
25#include "zelda3/dungeon/room.h"
27
28namespace yaze {
29namespace cli {
30namespace handlers {
31
33
34namespace {
35
36constexpr int kCollisionGridSize = 64;
37using json = nlohmann::json;
38
39absl::StatusOr<std::unordered_set<int>> ParseTileFilter(
40 const resources::ArgumentParser& parser) {
41 std::unordered_set<int> tiles;
42 auto tiles_opt = parser.GetString("tiles");
43 if (!tiles_opt.has_value()) {
44 return tiles;
45 }
46
47 for (absl::string_view token :
48 absl::StrSplit(tiles_opt.value(), ',', absl::SkipEmpty())) {
49 std::string t = std::string(absl::StripAsciiWhitespace(token));
50 int v = 0;
51 if (!ParseHexString(t, &v)) {
52 return absl::InvalidArgumentError(
53 absl::StrFormat("Invalid tile value in --tiles: %s", t));
54 }
55 if (v < 0 || v > 0xFF) {
56 return absl::InvalidArgumentError(
57 absl::StrFormat("Tile value out of range (0x00-0xFF): %s", t));
58 }
59 tiles.insert(v);
60 }
61
62 return tiles;
63}
64
65absl::StatusOr<int> ParseRoomIdToken(absl::string_view token) {
66 std::string trimmed = std::string(absl::StripAsciiWhitespace(token));
67 int room_id = 0;
68 if (!ParseHexString(trimmed, &room_id)) {
69 return absl::InvalidArgumentError(
70 absl::StrFormat("Invalid room ID: %s", trimmed));
71 }
72 if (room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
73 return absl::OutOfRangeError(
74 absl::StrFormat("Room ID out of range: 0x%02X", room_id));
75 }
76 return room_id;
77}
78
79absl::StatusOr<std::vector<int>> ParseRoomSelection(
80 const resources::ArgumentParser& parser) {
81 std::vector<int> room_ids;
82 bool any_explicit = false;
83
84 if (auto room_opt = parser.GetString("room"); room_opt.has_value()) {
85 any_explicit = true;
86 ASSIGN_OR_RETURN(int room_id, ParseRoomIdToken(room_opt.value()));
87 room_ids.push_back(room_id);
88 }
89
90 if (auto rooms_opt = parser.GetString("rooms"); rooms_opt.has_value()) {
91 any_explicit = true;
92 for (absl::string_view token :
93 absl::StrSplit(rooms_opt.value(), ',', absl::SkipEmpty())) {
94 ASSIGN_OR_RETURN(int room_id, ParseRoomIdToken(token));
95 room_ids.push_back(room_id);
96 }
97 }
98
99 if (parser.HasFlag("all")) {
100 any_explicit = true;
101 room_ids.clear();
102 room_ids.reserve(zelda3::kNumberOfRooms);
103 for (int room_id = 0; room_id < zelda3::kNumberOfRooms; ++room_id) {
104 room_ids.push_back(room_id);
105 }
106 }
107
108 if (!any_explicit) {
109 room_ids.reserve(zelda3::kNumberOfRooms);
110 for (int room_id = 0; room_id < zelda3::kNumberOfRooms; ++room_id) {
111 room_ids.push_back(room_id);
112 }
113 }
114
115 std::sort(room_ids.begin(), room_ids.end());
116 room_ids.erase(std::unique(room_ids.begin(), room_ids.end()), room_ids.end());
117
118 if (room_ids.empty()) {
119 return absl::InvalidArgumentError(
120 "No rooms selected (use --room, --rooms, or --all)");
121 }
122 return room_ids;
123}
124
125absl::StatusOr<std::string> ReadTextFile(const std::string& path) {
126 std::ifstream in(path, std::ios::in | std::ios::binary);
127 if (!in.is_open()) {
128 return absl::NotFoundError(
129 absl::StrFormat("Cannot open file for reading: %s", path));
130 }
131 std::stringstream ss;
132 ss << in.rdbuf();
133 if (!in.good() && !in.eof()) {
134 return absl::InternalError(
135 absl::StrFormat("Failed while reading file: %s", path));
136 }
137 return ss.str();
138}
139
140absl::Status WriteTextFile(const std::string& path,
141 const std::string& content) {
142 std::ofstream out(path, std::ios::out | std::ios::binary | std::ios::trunc);
143 if (!out.is_open()) {
144 return absl::PermissionDeniedError(
145 absl::StrFormat("Cannot open file for writing: %s", path));
146 }
147 out << content;
148 if (!out.good()) {
149 return absl::InternalError(
150 absl::StrFormat("Failed while writing file: %s", path));
151 }
152 return absl::OkStatus();
153}
154
155std::string StatusCodeName(absl::StatusCode code) {
156 switch (code) {
157 case absl::StatusCode::kOk:
158 return "OK";
159 case absl::StatusCode::kCancelled:
160 return "CANCELLED";
161 case absl::StatusCode::kUnknown:
162 return "UNKNOWN";
163 case absl::StatusCode::kInvalidArgument:
164 return "INVALID_ARGUMENT";
165 case absl::StatusCode::kDeadlineExceeded:
166 return "DEADLINE_EXCEEDED";
167 case absl::StatusCode::kNotFound:
168 return "NOT_FOUND";
169 case absl::StatusCode::kAlreadyExists:
170 return "ALREADY_EXISTS";
171 case absl::StatusCode::kPermissionDenied:
172 return "PERMISSION_DENIED";
173 case absl::StatusCode::kResourceExhausted:
174 return "RESOURCE_EXHAUSTED";
175 case absl::StatusCode::kFailedPrecondition:
176 return "FAILED_PRECONDITION";
177 case absl::StatusCode::kAborted:
178 return "ABORTED";
179 case absl::StatusCode::kOutOfRange:
180 return "OUT_OF_RANGE";
181 case absl::StatusCode::kUnimplemented:
182 return "UNIMPLEMENTED";
183 case absl::StatusCode::kInternal:
184 return "INTERNAL";
185 case absl::StatusCode::kUnavailable:
186 return "UNAVAILABLE";
187 case absl::StatusCode::kDataLoss:
188 return "DATA_LOSS";
189 case absl::StatusCode::kUnauthenticated:
190 return "UNAUTHENTICATED";
191 }
192 return "UNKNOWN";
193}
194
195json BuildBaseReport(absl::string_view command_name, bool dry_run) {
196 return json{
197 {"command", std::string(command_name)},
198 {"status", "success"},
199 {"dry_run", dry_run},
200 {"mode", dry_run ? "dry-run" : "write"},
201 };
202}
203
205 const json& report) {
206 auto report_path = parser.GetString("report");
207 if (!report_path.has_value()) {
208 return absl::OkStatus();
209 }
210 return WriteTextFile(*report_path, report.dump(2) + "\n");
211}
212
214 json report, const absl::Status& status) {
215 if (!status.ok()) {
216 report["status"] = "error";
217 report["error"] = json{
218 {"code", StatusCodeName(status.code())},
219 {"message", std::string(status.message())},
220 };
221 }
222
223 const auto report_status = WriteReportIfRequested(parser, report);
224 if (!report_status.ok()) {
225 if (status.ok()) {
226 return report_status;
227 }
228 return absl::InternalError(
229 absl::StrFormat("Command failed (%s) and report write failed (%s)",
230 status.message(), report_status.message()));
231 }
232
233 return status;
234}
235
238 json out;
239 out["ok"] = preflight.ok();
240 json errors = json::array();
241 for (const auto& err : preflight.errors) {
242 json e;
243 e["code"] = err.code;
244 e["message"] = err.message;
245 e["status_code"] = StatusCodeName(err.status_code);
246 if (err.room_id >= 0) {
247 e["room_id"] = absl::StrFormat("0x%02X", err.room_id);
248 }
249 errors.push_back(std::move(e));
250 }
251 out["errors"] = std::move(errors);
252 return out;
253}
254
256 const std::vector<zelda3::WaterFillZoneEntry>& zones) {
257 constexpr std::array<int, 2> kD4RoomIdsRequiringCollision = {0x25, 0x27};
258
259 std::unordered_set<int> imported_rooms;
260 imported_rooms.reserve(zones.size());
261 for (const auto& zone : zones) {
262 imported_rooms.insert(zone.room_id);
263 }
264
265 std::vector<int> required_rooms;
266 for (int room_id : kD4RoomIdsRequiringCollision) {
267 if (imported_rooms.contains(room_id)) {
268 required_rooms.push_back(room_id);
269 }
270 }
271 return required_rooms;
272}
273
274} // namespace
275
277 Rom* rom, const resources::ArgumentParser& parser,
278 resources::OutputFormatter& formatter) {
279 auto room_id_str = parser.GetString("room").value();
280
281 int room_id = 0;
282 if (!ParseHexString(room_id_str, &room_id)) {
283 return absl::InvalidArgumentError("Invalid room ID format. Must be hex.");
284 }
285
286 ASSIGN_OR_RETURN(auto filter_tiles, ParseTileFilter(parser));
287
288 const bool list_all = parser.HasFlag("all");
289 const bool list_nonzero =
290 parser.HasFlag("nonzero") || (!list_all && filter_tiles.empty());
291
292 formatter.BeginObject("Dungeon Custom Collision");
293 formatter.AddField("room_id", room_id);
294 formatter.AddHexField("room_id_hex", room_id, 2);
295 formatter.AddField(
296 "filter_mode",
297 !filter_tiles.empty()
298 ? "tiles"
299 : (list_all ? "all" : (list_nonzero ? "nonzero" : "all")));
300
301 auto map_or = zelda3::LoadCustomCollisionMap(rom, room_id);
302 if (!map_or.ok()) {
303 formatter.AddField("status", "error");
304 formatter.AddField("error", map_or.status().ToString());
305 formatter.EndObject();
306 return map_or.status();
307 }
308
309 const auto& map = map_or.value();
310 formatter.AddField("has_data", map.has_data);
311
312 int nonzero_count = 0;
313 for (uint8_t tile : map.tiles) {
314 if (tile != 0) {
315 ++nonzero_count;
316 }
317 }
318 formatter.AddField("nonzero_tiles", nonzero_count);
319
320 formatter.BeginArray("tiles");
321 int match_count = 0;
322 if (map.has_data) {
323 for (int y = 0; y < 64; ++y) {
324 for (int x = 0; x < 64; ++x) {
325 uint8_t tile = map.tiles[static_cast<size_t>(y * 64 + x)];
326
327 if (!filter_tiles.empty()) {
328 if (filter_tiles.find(static_cast<int>(tile)) == filter_tiles.end()) {
329 continue;
330 }
331 } else if (list_nonzero) {
332 if (tile == 0) {
333 continue;
334 }
335 } else if (!list_all) {
336 // Default behavior if neither filter nor flags are set is nonzero.
337 if (tile == 0) {
338 continue;
339 }
340 }
341
342 formatter.BeginObject();
343 formatter.AddField("x", x);
344 formatter.AddField("y", y);
345 formatter.AddHexField("tile", tile, 2);
346 formatter.EndObject();
347 ++match_count;
348 }
349 }
350 }
351 formatter.EndArray();
352
353 formatter.AddField("match_count", match_count);
354 formatter.AddField("status", "success");
355 formatter.EndObject();
356 return absl::OkStatus();
357}
358
360 Rom* rom, const resources::ArgumentParser& parser,
361 resources::OutputFormatter& formatter) {
362 json report = BuildBaseReport(GetName(), /*dry_run=*/false);
363 const absl::Status status = [&]() -> absl::Status {
364 ASSIGN_OR_RETURN(const auto room_ids, ParseRoomSelection(parser));
365 const std::string out_path = parser.GetString("out").value();
366 report["out_path"] = out_path;
367 report["requested_rooms"] = static_cast<int>(room_ids.size());
368
369 std::vector<zelda3::CustomCollisionRoomEntry> export_rooms;
370 export_rooms.reserve(room_ids.size());
371
372 for (int room_id : room_ids) {
373 ASSIGN_OR_RETURN(auto map, zelda3::LoadCustomCollisionMap(rom, room_id));
374 if (!map.has_data) {
375 continue;
376 }
377
379 entry.room_id = room_id;
380 for (int offset = 0; offset < kCollisionGridSize * kCollisionGridSize;
381 ++offset) {
382 const uint8_t tile = map.tiles[static_cast<size_t>(offset)];
383 if (tile == 0) {
384 continue;
385 }
387 static_cast<uint16_t>(offset), tile});
388 }
389 if (!entry.tiles.empty()) {
390 export_rooms.push_back(std::move(entry));
391 }
392 }
393
395 const std::string exported_json,
397 RETURN_IF_ERROR(WriteTextFile(out_path, exported_json));
398 report["exported_rooms"] = static_cast<int>(export_rooms.size());
399
400 formatter.BeginObject("Custom Collision Export");
401 formatter.AddField("out_path", out_path);
402 formatter.AddField("requested_rooms", static_cast<int>(room_ids.size()));
403 formatter.AddField("exported_rooms", static_cast<int>(export_rooms.size()));
404 formatter.AddField("status", "success");
405 formatter.EndObject();
406 return absl::OkStatus();
407 }();
408
409 return FinalizeWithReport(parser, std::move(report), status);
410}
411
413 Rom* rom, const resources::ArgumentParser& parser,
414 resources::OutputFormatter& formatter) {
415 const bool dry_run = parser.HasFlag("dry-run");
416 json report = BuildBaseReport(GetName(), dry_run);
417 const absl::Status status = [&]() -> absl::Status {
418 const std::string in_path = parser.GetString("in").value();
419 const bool replace_all = parser.HasFlag("replace-all");
420 const bool force = parser.HasFlag("force");
421 report["in_path"] = in_path;
422 report["replace_all"] = replace_all;
423 report["force"] = force;
424
426 return absl::FailedPreconditionError(
427 "Custom collision write support not present in this ROM");
428 }
429
431 preflight_options.require_water_fill_reserved_region = true;
432 preflight_options.require_custom_collision_write_support = true;
433 preflight_options.validate_water_fill_table = true;
434 preflight_options.validate_custom_collision_maps = true;
435 const auto preflight =
436 zelda3::RunOracleRomSafetyPreflight(rom, preflight_options);
437 report["preflight"] = BuildPreflightJson(preflight);
438 if (!preflight.ok()) {
439 return preflight.ToStatus();
440 }
441
442 if (replace_all && !dry_run && !force) {
443 return absl::FailedPreconditionError(
444 "--replace-all requires --force (run with --dry-run first)");
445 }
446
447 ASSIGN_OR_RETURN(const std::string json_content, ReadTextFile(in_path));
449 auto imported_rooms,
451 report["imported_room_entries"] = static_cast<int>(imported_rooms.size());
452
453 // Keep room storage on the heap: zelda3::Room is large enough that a full
454 // `kNumberOfRooms` array can overflow stack frames in optimized builds.
455 std::vector<zelda3::Room> rooms;
456 rooms.reserve(zelda3::kNumberOfRooms);
457 for (int room_id = 0; room_id < zelda3::kNumberOfRooms; ++room_id) {
458 rooms.emplace_back(room_id, rom, nullptr);
459 }
460
461 int populated_rooms = 0;
462 int cleared_rooms = 0;
463 std::unordered_set<int> touched_rooms;
464 for (const auto& imported : imported_rooms) {
465 touched_rooms.insert(imported.room_id);
466 auto& room = rooms[imported.room_id];
467 room.custom_collision().tiles.fill(0);
468 room.custom_collision().has_data = false;
469 room.MarkCustomCollisionDirty();
470
471 bool has_nonzero = false;
472 for (const auto& tile : imported.tiles) {
473 const int offset = static_cast<int>(tile.offset);
474 if (offset < 0 || offset >= kCollisionGridSize * kCollisionGridSize) {
475 continue;
476 }
477 if (tile.value == 0) {
478 continue;
479 }
480 room.SetCollisionTile(offset % kCollisionGridSize,
481 offset / kCollisionGridSize, tile.value);
482 has_nonzero = true;
483 }
484
485 if (has_nonzero) {
486 ++populated_rooms;
487 } else {
488 ++cleared_rooms;
489 }
490 }
491
492 int replace_all_clears = 0;
493 if (replace_all) {
494 for (int room_id = 0; room_id < zelda3::kNumberOfRooms; ++room_id) {
495 if (touched_rooms.contains(room_id)) {
496 continue;
497 }
498 auto& room = rooms[room_id];
499 room.custom_collision().tiles.fill(0);
500 room.custom_collision().has_data = false;
501 room.MarkCustomCollisionDirty();
502 ++cleared_rooms;
503 ++replace_all_clears;
504 }
505 }
506 report["replace_all_clears"] = replace_all_clears;
507
508 if (!dry_run) {
509 RETURN_IF_ERROR(zelda3::SaveAllCollision(rom, absl::MakeSpan(rooms)));
510 }
511
512 report["populated_rooms"] = populated_rooms;
513 report["cleared_rooms"] = cleared_rooms;
514
515 formatter.BeginObject("Custom Collision Import");
516 formatter.AddField("in_path", in_path);
517 formatter.AddField("replace_all", replace_all);
518 formatter.AddField("force", force);
519 formatter.AddField("mode", dry_run ? "dry-run" : "write");
520 formatter.AddField("imported_room_entries",
521 static_cast<int>(imported_rooms.size()));
522 formatter.AddField("populated_rooms", populated_rooms);
523 formatter.AddField("cleared_rooms", cleared_rooms);
524 formatter.AddField("status", "success");
525 formatter.EndObject();
526 return absl::OkStatus();
527 }();
528
529 return FinalizeWithReport(parser, std::move(report), status);
530}
531
533 Rom* rom, const resources::ArgumentParser& parser,
534 resources::OutputFormatter& formatter) {
535 json report = BuildBaseReport(GetName(), /*dry_run=*/false);
536 const absl::Status status = [&]() -> absl::Status {
537 const std::string out_path = parser.GetString("out").value();
538 ASSIGN_OR_RETURN(const auto room_ids, ParseRoomSelection(parser));
539 report["out_path"] = out_path;
540 report["requested_rooms"] = static_cast<int>(room_ids.size());
541
542 if (!zelda3::HasWaterFillReservedRegion(rom->vector().size())) {
543 return absl::FailedPreconditionError(
544 "WaterFill reserved region missing in this ROM");
545 }
546
548 std::unordered_set<int> room_filter(room_ids.begin(), room_ids.end());
549 std::vector<zelda3::WaterFillZoneEntry> filtered;
550 filtered.reserve(zones.size());
551 for (const auto& zone : zones) {
552 if (!room_filter.contains(zone.room_id)) {
553 continue;
554 }
555 filtered.push_back(zone);
556 }
557
558 ASSIGN_OR_RETURN(const std::string exported_json,
560 RETURN_IF_ERROR(WriteTextFile(out_path, exported_json));
561 report["exported_zones"] = static_cast<int>(filtered.size());
562
563 formatter.BeginObject("Water Fill Export");
564 formatter.AddField("out_path", out_path);
565 formatter.AddField("requested_rooms", static_cast<int>(room_ids.size()));
566 formatter.AddField("exported_zones", static_cast<int>(filtered.size()));
567 formatter.AddField("status", "success");
568 formatter.EndObject();
569 return absl::OkStatus();
570 }();
571
572 return FinalizeWithReport(parser, std::move(report), status);
573}
574
576 Rom* rom, const resources::ArgumentParser& parser,
577 resources::OutputFormatter& formatter) {
578 const bool dry_run = parser.HasFlag("dry-run");
579 const bool strict_masks = parser.HasFlag("strict-masks");
580 json report = BuildBaseReport(GetName(), dry_run);
581 const absl::Status status = [&]() -> absl::Status {
582 const std::string in_path = parser.GetString("in").value();
583 report["in_path"] = in_path;
584 report["strict_masks"] = strict_masks;
585
586 if (!zelda3::HasWaterFillReservedRegion(rom->vector().size())) {
587 return absl::FailedPreconditionError(
588 "WaterFill reserved region missing in this ROM");
589 }
590
591 ASSIGN_OR_RETURN(const std::string json_content, ReadTextFile(in_path));
592 ASSIGN_OR_RETURN(auto zones,
594 const auto required_collision_rooms =
595 RequiredCollisionRoomsForImportedWaterFillZones(zones);
596 if (!required_collision_rooms.empty()) {
597 json required_rooms_json = json::array();
598 for (int room_id : required_collision_rooms) {
599 required_rooms_json.push_back(absl::StrFormat("0x%02X", room_id));
600 }
601 report["required_collision_rooms"] = std::move(required_rooms_json);
602 }
603
605 preflight_options.require_water_fill_reserved_region = true;
606 preflight_options.require_custom_collision_write_support = false;
607 preflight_options.validate_water_fill_table = true;
608 preflight_options.validate_custom_collision_maps = true;
609 preflight_options.room_ids_requiring_custom_collision =
610 required_collision_rooms;
611 const auto preflight =
612 zelda3::RunOracleRomSafetyPreflight(rom, preflight_options);
613 report["preflight"] = BuildPreflightJson(preflight);
614 if (!preflight.ok()) {
615 return preflight.ToStatus();
616 }
617
618 auto original_zones = zones;
620
621 int normalized_masks = 0;
622 std::unordered_map<int, uint8_t> before_masks;
623 before_masks.reserve(original_zones.size());
624 for (const auto& z : original_zones) {
625 before_masks[z.room_id] = z.sram_bit_mask;
626 }
627 for (const auto& z : zones) {
628 auto it = before_masks.find(z.room_id);
629 if (it == before_masks.end() || it->second != z.sram_bit_mask) {
630 ++normalized_masks;
631 }
632 }
633
634 report["zone_count"] = static_cast<int>(zones.size());
635 report["normalized_masks"] = normalized_masks;
636
637 if (strict_masks && normalized_masks > 0) {
638 return absl::FailedPreconditionError(absl::StrFormat(
639 "WaterFill masks require normalization (%d changed); rerun without "
640 "--strict-masks to apply normalized masks",
641 normalized_masks));
642 }
643
644 if (!dry_run) {
646 }
647
648 formatter.BeginObject("Water Fill Import");
649 formatter.AddField("in_path", in_path);
650 formatter.AddField("mode", dry_run ? "dry-run" : "write");
651 formatter.AddField("strict_masks", strict_masks);
652 formatter.AddField("zone_count", static_cast<int>(zones.size()));
653 formatter.AddField("normalized_masks", normalized_masks);
654 formatter.AddField("status", "success");
655 formatter.EndObject();
656 return absl::OkStatus();
657 }();
658
659 return FinalizeWithReport(parser, std::move(report), status);
660}
661
662} // namespace handlers
663} // namespace cli
664} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
const auto & vector() const
Definition rom.h:143
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
std::string GetName() const override
Get the command name.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
std::string GetName() const override
Get the command name.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
Utility for parsing common CLI argument patterns.
std::optional< std::string > GetString(const std::string &name) const
Parse a named argument (e.g., –format=json or –format json)
bool HasFlag(const std::string &name) const
Check if a flag is present.
Utility for consistent output formatting across commands.
void BeginArray(const std::string &key)
Begin an array.
void BeginObject(const std::string &title="")
Start a JSON object or text section.
void EndObject()
End a JSON object or text section.
void AddField(const std::string &key, const std::string &value)
Add a key-value pair.
void AddHexField(const std::string &key, uint64_t value, int width=2)
Add a hex-formatted field.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
absl::Status WriteTextFile(const std::string &path, const std::string &content)
absl::StatusOr< std::unordered_set< int > > ParseTileFilter(const resources::ArgumentParser &parser)
absl::Status WriteReportIfRequested(const resources::ArgumentParser &parser, const json &report)
std::vector< int > RequiredCollisionRoomsForImportedWaterFillZones(const std::vector< zelda3::WaterFillZoneEntry > &zones)
absl::Status FinalizeWithReport(const resources::ArgumentParser &parser, json report, const absl::Status &status)
json BuildPreflightJson(const zelda3::OracleRomSafetyPreflightResult &preflight)
absl::StatusOr< std::vector< int > > ParseRoomSelection(const resources::ArgumentParser &parser)
bool ParseHexString(absl::string_view str, int *out)
Definition hex_util.h:17
absl::StatusOr< std::string > DumpWaterFillZonesToJsonString(const std::vector< WaterFillZoneEntry > &zones)
absl::Status NormalizeWaterFillZoneMasks(std::vector< WaterFillZoneEntry > *zones)
absl::StatusOr< std::vector< CustomCollisionRoomEntry > > LoadCustomCollisionRoomsFromJsonString(const std::string &json_content)
absl::StatusOr< std::string > DumpCustomCollisionRoomsToJsonString(const std::vector< CustomCollisionRoomEntry > &rooms)
absl::StatusOr< std::vector< WaterFillZoneEntry > > LoadWaterFillZonesFromJsonString(const std::string &json_content)
OracleRomSafetyPreflightResult RunOracleRomSafetyPreflight(Rom *rom, const OracleRomSafetyPreflightOptions &options)
absl::StatusOr< CustomCollisionMap > LoadCustomCollisionMap(Rom *rom, int room_id)
constexpr bool HasWaterFillReservedRegion(std::size_t rom_size)
constexpr int kNumberOfRooms
absl::Status SaveAllCollision(Rom *rom, absl::Span< Room > rooms)
Definition room.cc:2370
constexpr bool HasCustomCollisionWriteSupport(std::size_t rom_size)
absl::StatusOr< std::vector< WaterFillZoneEntry > > LoadWaterFillTable(Rom *rom)
absl::Status WriteWaterFillTable(Rom *rom, const std::vector< WaterFillZoneEntry > &zones)
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
std::vector< CustomCollisionTileEntry > tiles