11#include "absl/strings/ascii.h"
12#include "absl/strings/str_cat.h"
13#include "absl/strings/str_format.h"
14#include "absl/strings/str_split.h"
61absl::StatusOr<std::optional<RoomPropertyManifestContext>>
63 const auto manifest_path = parser.
GetString(
"manifest");
64 if (!manifest_path.has_value()) {
72 if (manifest_layout ==
nullptr) {
73 return absl::FailedPreconditionError(
74 "Manifest does not define dungeon_stream_regions.objects");
77 return absl::FailedPreconditionError(
78 "dungeon_stream_regions.objects must use copy_on_write");
83 return std::optional<RoomPropertyManifestContext>(std::move(context));
88 std::vector<std::pair<uint32_t, uint32_t>> ranges;
90 uint32_t pointer_table_snes = 0;
93 const uint32_t pointer_table_pc =
SnesToPc(pointer_table_snes);
94 const uint64_t pointer_slot =
static_cast<uint64_t
>(pointer_table_pc) +
95 static_cast<uint64_t
>(room_id) * 3u;
96 if (pointer_slot + 3u > rom.
size()) {
97 return absl::OutOfRangeError(
98 "Selected object pointer slot is outside the ROM");
101 uint32_t stream_snes = 0;
103 const uint32_t stream_pc =
SnesToPc(stream_snes);
104 if (
static_cast<uint64_t
>(stream_pc) + 2u > rom.
size()) {
105 return absl::OutOfRangeError(
106 "Selected object stream header is outside the ROM");
108 ranges.emplace_back(stream_pc, stream_pc + 2u);
111 ranges.emplace_back(range.begin, range.end);
113 const uint64_t cow_pointer_slot =
115 static_cast<uint64_t
>(room_id) * 3u;
116 if (cow_pointer_slot + 3u > std::numeric_limits<uint32_t>::max()) {
117 return absl::OutOfRangeError(
"Selected COW pointer range overflows");
119 ranges.emplace_back(
static_cast<uint32_t
>(cow_pointer_slot),
120 static_cast<uint32_t
>(cow_pointer_slot + 3u));
122 const uint64_t door_pointer_slot =
124 static_cast<uint64_t
>(room_id) * 3u;
125 if (door_pointer_slot + 3u > std::numeric_limits<uint32_t>::max()) {
126 return absl::OutOfRangeError(
"Selected door pointer range overflows");
128 ranges.emplace_back(
static_cast<uint32_t
>(door_pointer_slot),
129 static_cast<uint32_t
>(door_pointer_slot + 3u));
132 return absl::PermissionDeniedError(
"Write conflict with Hack Manifest");
134 return absl::OkStatus();
142 for (
int i = 0; i < static_cast<int>(existing.
tiles.size()); ++i) {
152 if (parser.
HasFlag(
"mock-rom")) {
153 formatter.
AddField(
"save_status",
"mock-rom-skipped");
154 return absl::OkStatus();
159 auto save_status = rom->
SaveToFile(save_settings);
160 if (!save_status.ok()) {
161 formatter.
AddField(
"save_error", std::string(save_status.message()));
165 formatter.
AddField(
"save_status",
"saved");
166 return absl::OkStatus();
171 auto registry_path = parser.
GetString(
"sprite-registry");
172 if (!registry_path.has_value()) {
173 return absl::OkStatus();
176 std::ifstream file(registry_path.value());
177 if (!file.is_open()) {
178 return absl::NotFoundError(absl::StrFormat(
179 "Could not open sprite registry: %s", registry_path.value()));
182 std::stringstream buffer;
183 buffer << file.rdbuf();
191 std::string_view object_title) {
193 return absl::InvalidArgumentError(absl::StrFormat(
194 "Dungeon spawn point ID %d out of range (0x00-0x06).", spawn_id));
198 if (!spawn_or.ok()) {
199 return spawn_or.status();
201 const auto& spawn = spawn_or.value();
203 if (!object_title.empty()) {
208 formatter.
AddField(
"is_spawn_point",
true);
209 formatter.
AddHexField(
"room_id", spawn.room_id, 4);
210 formatter.
AddHexField(
"dungeon_id", spawn.dungeon_id, 2);
211 formatter.
AddHexField(
"quadrant", spawn.quadrant, 2);
212 formatter.
AddHexField(
"overworld_door_tilemap", spawn.overworld_door_tilemap,
214 formatter.
AddHexField(
"entrance_id", spawn.entrance_id, 4);
217 formatter.
AddField(
"x",
static_cast<int>(spawn.x_coordinate));
218 formatter.
AddField(
"y",
static_cast<int>(spawn.y_coordinate));
222 formatter.
AddField(
"horizontal_scroll",
223 static_cast<int>(spawn.horizontal_scroll));
224 formatter.
AddField(
"vertical_scroll",
225 static_cast<int>(spawn.vertical_scroll));
226 formatter.
AddField(
"trigger_x",
static_cast<int>(spawn.camera_trigger_x));
227 formatter.
AddField(
"trigger_y",
static_cast<int>(spawn.camera_trigger_y));
231 formatter.
AddHexField(
"main_gfx", spawn.main_gfx, 2);
235 spawn.camera_scroll_controller, 2);
240 formatter.
AddHexField(
"qn", spawn.camera_scroll_boundaries[0], 2);
241 formatter.
AddHexField(
"fn", spawn.camera_scroll_boundaries[1], 2);
242 formatter.
AddHexField(
"qs", spawn.camera_scroll_boundaries[2], 2);
243 formatter.
AddHexField(
"fs", spawn.camera_scroll_boundaries[3], 2);
244 formatter.
AddHexField(
"qw", spawn.camera_scroll_boundaries[4], 2);
245 formatter.
AddHexField(
"fw", spawn.camera_scroll_boundaries[5], 2);
246 formatter.
AddHexField(
"qe", spawn.camera_scroll_boundaries[6], 2);
247 formatter.
AddHexField(
"fe", spawn.camera_scroll_boundaries[7], 2);
250 if (!object_title.empty()) {
253 return absl::OkStatus();
260 auto registry_status = MaybeLoadSpriteRegistry(parser);
261 if (!registry_status.ok()) {
262 return registry_status;
265 auto room_id_str = parser.
GetString(
"room").value();
268 if (!ParseHexString(room_id_str, &room_id)) {
269 return absl::InvalidArgumentError(
"Invalid room ID format. Must be hex.");
273 formatter.
AddField(
"room_id", room_id);
279 formatter.
AddField(
"total_sprites",
static_cast<int>(sprites.size()));
280 formatter.
AddField(
"status",
"success");
283 for (
const auto& sprite : sprites) {
285 formatter.
AddHexField(
"sprite_id", sprite.id(), 2);
287 formatter.
AddField(
"x", sprite.x());
288 formatter.
AddField(
"y", sprite.y());
289 formatter.
AddField(
"subtype", sprite.subtype());
290 formatter.
AddField(
"layer", sprite.layer());
291 if (sprite.key_drop() > 0) {
292 formatter.
AddField(
"key_drop", sprite.key_drop());
299 return absl::OkStatus();
306 auto registry_status = MaybeLoadSpriteRegistry(parser);
307 if (!registry_status.ok()) {
308 return registry_status;
311 auto room_id_str = parser.
GetString(
"room").value();
314 if (!ParseHexString(room_id_str, &room_id)) {
315 return absl::InvalidArgumentError(
"Invalid room ID format. Must be hex.");
318 formatter.
AddField(
"room_id", room_id);
322 const bool include_objects = parser.
HasFlag(
"include-objects");
325 formatter.
AddField(
"status",
"success");
326 formatter.
AddField(
"name", absl::StrFormat(
"Room %d", room.
id()));
328 formatter.
AddField(
"room_type",
"Dungeon Room");
339 formatter.
AddField(
"tag1",
static_cast<int>(room.
tag1()));
340 formatter.
AddField(
"tag2",
static_cast<int>(room.
tag2()));
343 const int object_count = include_objects
344 ?
static_cast<int>(std::count_if(
345 tile_objects.begin(), tile_objects.end(),
346 IsEncodedRoomStreamObject))
347 :
static_cast<int>(tile_objects.size());
348 formatter.
AddField(
"object_count", object_count);
352 if (include_objects) {
354 int stream_index = 0;
355 for (
const auto&
object : tile_objects) {
356 if (!IsEncodedRoomStreamObject(
object)) {
360 const auto encoded =
object.EncodeObjectToBytes();
362 formatter.
AddHexField(
"object_id",
static_cast<uint16_t
>(
object.id_), 3);
364 encoded.b1, encoded.b3));
365 formatter.
AddField(
"x",
static_cast<int>(
object.x()));
366 formatter.
AddField(
"y",
static_cast<int>(
object.y()));
367 formatter.
AddField(
"size",
static_cast<int>(
object.size()));
368 formatter.
AddField(
"stream_index", stream_index++);
376 for (
const auto& door : room.
GetDoors()) {
378 formatter.
AddField(
"position", door.position);
379 formatter.
AddField(
"direction", std::string(door.GetDirectionName()));
380 formatter.
AddField(
"type", std::string(door.GetTypeName()));
381 auto [tx, ty] = door.GetTileCoords();
390 for (
const auto& stair : room.
GetStairs()) {
395 "tile_y", stair.room);
396 formatter.
AddField(
"label", stair.label);
412 return absl::OkStatus();
418 auto room_id_opt = parser.
GetString(
"room");
420 bool has_room_filter = room_id_opt.has_value();
421 int room_filter = -1;
422 if (has_room_filter) {
423 if (!ParseHexString(room_id_opt.value(), &room_filter)) {
424 return absl::InvalidArgumentError(
"Invalid room ID format. Must be hex.");
429 int rooms_with_chests = 0;
430 int total_chests = 0;
431 std::map<int, int> item_counts;
434 formatter.
AddField(
"room_filter", has_room_filter
435 ? absl::StrFormat(
"0x%02X", room_filter)
439 int start_room = has_room_filter ? room_filter : 0;
442 for (
int room_id = start_room; room_id <= end_room; ++room_id) {
448 if (chests.empty()) {
453 total_chests +=
static_cast<int>(chests.size());
456 formatter.
AddField(
"room_id", absl::StrFormat(
"0x%02X", room_id));
458 formatter.
AddField(
"chest_count",
static_cast<int>(chests.size()));
462 for (
const auto&
chest : chests) {
464 formatter.
AddField(
"index", chest_index++);
471 item_counts[
chest.id]++;
480 formatter.
AddField(
"total_rooms", total_rooms);
481 formatter.
AddField(
"rooms_with_chests", rooms_with_chests);
482 formatter.
AddField(
"total_chests", total_chests);
483 formatter.
AddField(
"unique_items",
static_cast<int>(item_counts.size()));
486 for (
const auto& [item_id, count] : item_counts) {
500 return absl::OkStatus();
506 auto entrance_id_str = parser.
GetString(
"entrance").value();
507 bool is_spawn_point = parser.
HasFlag(
"spawn");
510 if (!ParseHexString(entrance_id_str, &entrance_id)) {
511 return absl::InvalidArgumentError(
512 "Invalid entrance ID format. Must be hex.");
515 if (is_spawn_point) {
521 formatter.
AddField(
"entrance_id", absl::StrFormat(
"0x%02X", entrance_id));
522 formatter.
AddField(
"is_spawn_point", is_spawn_point);
523 formatter.
AddField(
"room_id", absl::StrFormat(
"0x%04X", entrance.
room_));
524 formatter.
AddField(
"exit_id", absl::StrFormat(
"0x%04X", entrance.
exit_));
540 formatter.
AddField(
"floor", absl::StrFormat(
"0x%02X", entrance.
floor_));
543 formatter.
AddField(
"door", absl::StrFormat(
"0x%02X", entrance.
door_));
545 absl::StrFormat(
"0x%02X", entrance.
ladder_bg_));
547 absl::StrFormat(
"0x%02X", entrance.
scrolling_));
548 formatter.
AddField(
"scroll_quadrant",
550 formatter.
AddField(
"music", absl::StrFormat(
"0x%02X", entrance.
music_));
572 return absl::OkStatus();
578 auto room_id_str = parser.
GetString(
"room").value();
581 if (!ParseHexString(room_id_str, &room_id)) {
582 return absl::InvalidArgumentError(
"Invalid room ID format. Must be hex.");
586 formatter.
AddField(
"room_id", room_id);
590 auto room_or = dungeon_editor.
GetRoom(room_id);
592 formatter.
AddField(
"status",
"error");
593 formatter.
AddField(
"error", room_or.status().ToString());
595 return room_or.status();
598 auto& room = room_or.value();
601 formatter.
AddField(
"status",
"success");
602 formatter.
AddField(
"room_width",
"Unknown");
603 formatter.
AddField(
"room_height",
"Unknown");
604 formatter.
AddField(
"room_name", absl::StrFormat(
"Room %d", room.id()));
608 formatter.
AddField(
"tiles",
"Room tile data would be exported here");
609 formatter.
AddField(
"sprites",
"Room sprite data would be exported here");
610 formatter.
AddField(
"doors",
"Room door data would be exported here");
615 return absl::OkStatus();
621 auto room_id_str = parser.
GetString(
"room").value();
624 if (!ParseHexString(room_id_str, &room_id)) {
625 return absl::InvalidArgumentError(
"Invalid room ID format. Must be hex.");
629 formatter.
AddField(
"room_id", room_id);
633 auto room_or = dungeon_editor.
GetRoom(room_id);
635 formatter.
AddField(
"status",
"error");
636 formatter.
AddField(
"error", room_or.status().ToString());
638 return room_or.status();
641 auto& room = room_or.value();
646 const auto& objects = room.GetTileObjects();
647 formatter.
AddField(
"total_objects",
static_cast<int>(objects.size()));
648 formatter.
AddField(
"status",
"success");
651 for (
const auto& obj : objects) {
654 formatter.
AddField(
"id_hex", absl::StrFormat(
"0x%04X", obj.id_));
657 formatter.
AddField(
"size", obj.size_);
658 formatter.
AddField(
"layer",
static_cast<int>(obj.layer_));
668 return absl::OkStatus();
674 auto room_id_str = parser.
GetString(
"room").value();
677 if (!ParseHexString(room_id_str, &room_id)) {
678 return absl::InvalidArgumentError(
"Invalid room ID format. Must be hex.");
682 formatter.
AddField(
"room_id", room_id);
686 auto room_or = dungeon_editor.
GetRoom(room_id);
688 formatter.
AddField(
"status",
"error");
689 formatter.
AddField(
"error", room_or.status().ToString());
691 return room_or.status();
694 auto& room = room_or.value();
698 formatter.
AddField(
"room_width", 64);
699 formatter.
AddField(
"room_height", 64);
700 formatter.
AddField(
"total_tiles", 64 * 64);
701 formatter.
AddField(
"has_custom_collision", room.has_custom_collision());
703 static_cast<int>(room.GetTileObjects().size()));
704 formatter.
AddField(
"status",
"success");
709 const auto& collision = room.custom_collision().tiles;
710 for (
int y = 0; y < 64; ++y) {
713 for (
int x = 0; x < 64; ++x) {
717 absl::StrAppend(&row, absl::StrFormat(
"%02X", collision[y * 64 + x]));
724 return absl::OkStatus();
730 auto room_id_str = parser.
GetString(
"room").value();
731 auto property = parser.
GetString(
"property").value();
732 auto value = parser.
GetString(
"value").value();
735 if (!ParseHexString(room_id_str, &room_id)) {
736 return absl::InvalidArgumentError(
"Invalid room ID format. Must be hex.");
739 formatter.
BeginObject(
"Dungeon Room Property Set");
740 formatter.
AddField(
"room_id", room_id);
741 formatter.
AddField(
"property", property);
745 const absl::Status room_status =
746 absl::InvalidArgumentError(
"Invalid room ID");
747 formatter.
AddField(
"status",
"error");
748 formatter.
AddField(
"error", room_status.ToString());
753 int parsed_value = 0;
754 if (!ParseHexString(value, &parsed_value)) {
755 const absl::Status value_status =
756 absl::InvalidArgumentError(absl::StrFormat(
757 "Invalid value format: %s (expected integer/hex)", value));
758 formatter.
AddField(
"status",
"error");
759 formatter.
AddField(
"error", std::string(value_status.message()));
764 const std::string prop = absl::AsciiStrToLower(property);
765 const bool is_layout = prop ==
"layout" || prop ==
"layout_id";
766 const bool is_floor = prop ==
"floor1" || prop ==
"floor2";
767 const bool is_object_stream_header = is_layout || is_floor;
768 const int property_max = is_layout ? 0x07 : (is_floor ? 0x0F : 0xFF);
769 if (is_object_stream_header &&
770 (parsed_value < 0 || parsed_value > property_max)) {
771 const absl::Status property_status =
772 absl::InvalidArgumentError(absl::StrFormat(
773 "Property %s must be in range 0..%d", property, property_max));
774 formatter.
AddField(
"status",
"error");
775 formatter.
AddField(
"error", std::string(property_status.message()));
777 return property_status;
780 std::optional<RoomPropertyManifestContext> manifest_context;
781 if (is_object_stream_header) {
782 auto context_or = LoadRoomPropertyManifestContext(parser);
783 if (!context_or.ok()) {
784 formatter.
AddField(
"status",
"error");
785 formatter.
AddField(
"error", std::string(context_or.status().message()));
787 return context_or.status();
789 manifest_context = std::move(context_or).value();
790 if (manifest_context.has_value()) {
791 const absl::Status manifest_status =
792 ValidateRoomPropertyManifestConflicts(*rom, room_id,
794 if (!manifest_status.ok()) {
795 formatter.
AddField(
"status",
"error");
796 formatter.
AddField(
"error", std::string(manifest_status.message()));
798 return manifest_status;
801 formatter.
AddField(
"allocator_capability",
"copy_on_write");
807 if (prop ==
"palette") {
808 room.
SetPalette(
static_cast<uint8_t
>(parsed_value & 0xFF));
809 }
else if (prop ==
"blockset") {
810 room.
SetBlockset(
static_cast<uint8_t
>(parsed_value & 0xFF));
811 }
else if (prop ==
"spriteset") {
812 room.
SetSpriteset(
static_cast<uint8_t
>(parsed_value & 0xFF));
813 }
else if (is_layout) {
814 room.
SetLayoutId(
static_cast<uint8_t
>(parsed_value));
815 }
else if (prop ==
"floor1") {
816 room.
set_floor1(
static_cast<uint8_t
>(parsed_value));
817 }
else if (prop ==
"floor2") {
818 room.
set_floor2(
static_cast<uint8_t
>(parsed_value));
819 }
else if (prop ==
"effect") {
821 }
else if (prop ==
"tag1") {
823 }
else if (prop ==
"tag2") {
825 }
else if (prop ==
"holewarp") {
826 room.
SetHolewarp(
static_cast<uint8_t
>(parsed_value & 0xFF));
828 const absl::Status property_status = absl::InvalidArgumentError(
829 absl::StrFormat(
"Unsupported property: %s", property));
830 formatter.
AddField(
"status",
"error");
831 formatter.
AddField(
"error", std::string(property_status.message()));
833 return property_status;
836 const absl::Status write_status =
838 manifest_context.has_value()
839 ? &manifest_context->allocator_layout
842 if (!write_status.ok()) {
843 formatter.
AddField(
"status",
"error");
844 formatter.
AddField(
"write_error", std::string(write_status.message()));
849 if (parser.
HasFlag(
"mock-rom")) {
851 formatter.
AddField(
"save_status",
"mock-rom-skipped");
855 const absl::Status save_status = rom->
SaveToFile(save_settings);
856 if (!save_status.ok()) {
857 formatter.
AddField(
"status",
"error");
858 formatter.
AddField(
"save_error", std::string(save_status.message()));
863 formatter.
AddField(
"save_status",
"saved");
865 formatter.
AddField(
"status",
"success");
868 return absl::OkStatus();
874 auto room_id_str = parser.
GetString(
"room").value();
877 if (!ParseHexString(room_id_str, &room_id)) {
878 return absl::InvalidArgumentError(
"Invalid room ID format. Must be hex.");
882 formatter.
AddField(
"room_id", room_id);
896 int header_pointer_pc =
SnesToPc(header_pointer);
899 formatter.
AddHexField(
"snes_address", header_pointer, 6);
900 formatter.
AddHexField(
"pc_address", header_pointer_pc, 6);
904 int table_offset = header_pointer_pc + (room_id * 2);
906 (rom->
data()[table_offset + 1] << 8) +
907 rom->
data()[table_offset];
908 int room_header_pc =
SnesToPc(room_header_addr);
911 formatter.
AddHexField(
"table_offset_pc", table_offset, 6);
912 formatter.
AddHexField(
"snes_address", room_header_addr, 6);
913 formatter.
AddHexField(
"pc_address", room_header_pc, 6);
918 for (
int i = 0; i < 14; ++i) {
919 if (room_header_pc + i <
static_cast<int>(rom->
size())) {
921 absl::StrFormat(
"0x%02X", rom->
data()[room_header_pc + i]));
927 if (room_header_pc >= 0 &&
928 room_header_pc + 13 <
static_cast<int>(rom->
size())) {
929 uint8_t byte0 = rom->
data()[room_header_pc];
930 uint8_t byte1 = rom->
data()[room_header_pc + 1];
931 uint8_t byte2 = rom->
data()[room_header_pc + 2];
932 uint8_t byte3 = rom->
data()[room_header_pc + 3];
935 formatter.
AddField(
"bg2", (byte0 >> 5) & 0x07);
936 formatter.
AddField(
"collision", (byte0 >> 2) & 0x07);
937 formatter.
AddField(
"is_light", (byte0 & 0x01) == 1);
938 formatter.
AddField(
"palette", byte1 & 0x3F);
939 formatter.
AddField(
"blockset", byte2);
940 formatter.
AddField(
"spriteset", byte3);
941 formatter.
AddField(
"effect", rom->
data()[room_header_pc + 4]);
942 formatter.
AddField(
"tag1", rom->
data()[room_header_pc + 5]);
943 formatter.
AddField(
"tag2", rom->
data()[room_header_pc + 6]);
944 formatter.
AddField(
"holewarp", rom->
data()[room_header_pc + 9]);
945 formatter.
AddField(
"stair1_room", rom->
data()[room_header_pc + 10]);
946 formatter.
AddField(
"stair2_room", rom->
data()[room_header_pc + 11]);
947 formatter.
AddField(
"stair3_room", rom->
data()[room_header_pc + 12]);
948 formatter.
AddField(
"stair4_room", rom->
data()[room_header_pc + 13]);
951 formatter.
AddField(
"error",
"Room header address out of range");
955 return absl::OkStatus();
965 auto switch_str = parser.
GetString(
"promote-switch");
966 if (switch_str.has_value()) {
967 for (absl::string_view pair :
968 absl::StrSplit(switch_str.value(),
' ', absl::SkipEmpty())) {
969 std::vector<std::string> coords =
970 absl::StrSplit(pair,
',', absl::SkipEmpty());
971 if (coords.size() == 2) {
973 if (ParseHexString(coords[0], &sx) && ParseHexString(coords[1], &sy)) {
980 bool do_write = parser.
HasFlag(
"write");
981 bool do_preserve_stops = parser.
HasFlag(
"preserve-stops");
982 bool do_visualize = parser.
HasFlag(
"visualize");
985 std::vector<int> room_ids;
986 auto rooms_arg = parser.
GetString(
"rooms");
987 auto room_arg = parser.
GetString(
"room");
989 if (rooms_arg.has_value()) {
991 for (absl::string_view token :
992 absl::StrSplit(rooms_arg.value(),
',', absl::SkipEmpty())) {
994 std::string token_str(token);
995 if (!ParseHexString(token_str, &rid)) {
996 return absl::InvalidArgumentError(absl::StrFormat(
997 "Invalid room ID '%s' in --rooms list. Must be hex.", token_str));
999 room_ids.push_back(rid);
1001 if (room_ids.empty()) {
1002 return absl::InvalidArgumentError(
"--rooms list is empty.");
1004 }
else if (room_arg.has_value()) {
1007 if (!ParseHexString(room_arg.value(), &rid)) {
1008 return absl::InvalidArgumentError(
"Invalid room ID format. Must be hex.");
1010 room_ids.push_back(rid);
1012 return absl::InvalidArgumentError(
"Either --room or --rooms is required.");
1015 bool is_batch = room_ids.size() > 1;
1016 std::unique_ptr<ScopedRomTransaction> transaction;
1018 transaction = std::make_unique<ScopedRomTransaction>(*rom);
1023 formatter.
BeginObject(
"Batch Track Collision Generation");
1024 formatter.
AddField(
"mode", do_write ?
"write" :
"dry-run");
1025 formatter.
AddField(
"room_count",
static_cast<int>(room_ids.size()));
1027 int total_tiles = 0;
1028 int total_stops = 0;
1029 int total_corners = 0;
1030 int total_switches = 0;
1031 int rooms_succeeded = 0;
1034 for (
int room_id : room_ids) {
1043 formatter.
AddField(
"error", std::string(result.status().message()));
1045 return absl::InternalError(
1046 absl::StrFormat(
"Generation failed for room 0x%03X: %s", room_id,
1047 result.status().message()));
1050 if (do_preserve_stops && do_write) {
1052 if (existing.ok() && existing->has_data) {
1053 MergeStopTiles(*existing, result->collision_map);
1059 formatter.
AddField(
"tiles_generated", result->tiles_generated);
1060 formatter.
AddField(
"stop_count", result->stop_count);
1061 formatter.
AddField(
"corner_count", result->corner_count);
1062 formatter.
AddField(
"switch_count", result->switch_count);
1063 if (do_preserve_stops) {
1064 formatter.
AddField(
"stops_preserved",
true);
1068 formatter.
AddField(
"visualization", result->ascii_visualization);
1074 if (!write_status.ok()) {
1077 std::string(write_status.message()));
1081 return absl::Status(
1082 write_status.code(),
1083 absl::StrFormat(
"Write failed for room 0x%03X: %s", room_id,
1084 write_status.message()));
1086 formatter.
AddField(
"write_status",
"success");
1089 total_tiles += result->tiles_generated;
1090 total_stops += result->stop_count;
1091 total_corners += result->corner_count;
1092 total_switches += result->switch_count;
1101 auto save_status = SaveTrackCollisionRom(rom, parser, formatter);
1102 if (!save_status.ok()) {
1106 transaction->Commit();
1111 formatter.
AddField(
"rooms_succeeded", rooms_succeeded);
1112 formatter.
AddField(
"tiles_generated", total_tiles);
1113 formatter.
AddField(
"stop_count", total_stops);
1114 formatter.
AddField(
"corner_count", total_corners);
1115 formatter.
AddField(
"switch_count", total_switches);
1121 int room_id = room_ids[0];
1128 return result.status();
1131 if (do_preserve_stops && do_write) {
1133 if (existing.ok() && existing->has_data) {
1134 MergeStopTiles(*existing, result->collision_map);
1138 formatter.
BeginObject(
"Track Collision Generation");
1140 formatter.
AddField(
"tiles_generated", result->tiles_generated);
1141 formatter.
AddField(
"stop_count", result->stop_count);
1142 formatter.
AddField(
"corner_count", result->corner_count);
1143 formatter.
AddField(
"switch_count", result->switch_count);
1144 formatter.
AddField(
"mode", do_write ?
"write" :
"dry-run");
1145 if (do_preserve_stops) {
1146 formatter.
AddField(
"stops_preserved",
true);
1149 if (do_visualize || !do_write) {
1150 formatter.
AddField(
"visualization", result->ascii_visualization);
1156 if (!write_status.ok()) {
1157 formatter.
AddField(
"write_error", std::string(write_status.message()));
1159 return write_status;
1161 formatter.
AddField(
"write_status",
"success");
1162 auto save_status = SaveTrackCollisionRom(rom, parser, formatter);
1163 if (!save_status.ok()) {
1167 transaction->Commit();
1174 return absl::OkStatus();
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::Status SaveToFile(const SaveSettings &settings)
absl::StatusOr< uint32_t > ReadLong(int offset) const
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.
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.
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.
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.
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.
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.
Loads and queries the hack manifest JSON for yaze-ASM integration.
const DungeonStreamLayout * GetDungeonStreamLayout(DungeonStreamType stream) const
Get an explicitly declared dungeon stream layout.
absl::Status LoadFromFile(const std::string &filepath)
Load manifest from a JSON file path.
std::vector< WriteConflict > AnalyzePcWriteRanges(const std::vector< std::pair< uint32_t, uint32_t > > &pc_ranges) const
Analyze a set of PC-offset ranges for write conflicts.
Core dungeon editing system.
absl::StatusOr< Room > GetRoom(int room_id)
static absl::StatusOr< DungeonSpawnPoint > Load(const Rom &rom, int spawn_id)
absl::Status ImportOracleSpriteRegistry(const std::string &csv_content)
Import sprite labels from Oracle of Secrets registry.csv format.
Dungeon Room Entrance or Spawn Point.
uint8_t camera_boundary_fw_
uint8_t camera_boundary_qe_
uint8_t camera_boundary_fe_
uint8_t camera_boundary_fs_
uint8_t camera_boundary_qs_
uint8_t camera_boundary_qw_
uint16_t camera_trigger_y_
uint8_t camera_boundary_fn_
uint16_t camera_trigger_x_
uint8_t camera_boundary_qn_
static int DetermineObjectType(uint8_t b1, uint8_t b3)
const std::vector< chest_data > & GetChests() const
void set_floor2(uint8_t value)
void set_floor1(uint8_t value)
void SetTag2(TagKey tag2)
const std::vector< Door > & GetDoors() const
void SetLayoutId(uint8_t id)
void SetHolewarp(uint8_t hw)
const std::vector< staircase > & GetStairs() const
absl::Status SaveRoomHeader()
void SetEffect(EffectKey effect)
absl::Status SaveObjectStreamHeader(const DungeonStreamLayout *layout=nullptr)
uint8_t spriteset() const
void SetTag1(TagKey tag1)
const std::vector< zelda3::Sprite > & GetSprites() const
const std::vector< RoomObject > & GetTileObjects() const
void SetSpriteset(uint8_t ss)
void SetBlockset(uint8_t bs)
void SetPalette(uint8_t pal)
uint8_t layout_id() const
#define ASSIGN_OR_RETURN(type_variable_name, expression)
absl::Status MaybeLoadSpriteRegistry(const resources::ArgumentParser &parser)
bool IsStopTile(uint8_t v)
absl::StatusOr< std::optional< RoomPropertyManifestContext > > LoadRoomPropertyManifestContext(const resources::ArgumentParser &parser)
constexpr uint8_t kStopTileMin
constexpr uint8_t kStopTileMax
bool IsEncodedRoomStreamObject(const zelda3::RoomObject &object)
absl::Status SaveTrackCollisionRom(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter)
absl::Status ValidateRoomPropertyManifestConflicts(const Rom &rom, int room_id, const RoomPropertyManifestContext &context)
void MergeStopTiles(const zelda3::CustomCollisionMap &existing, zelda3::CustomCollisionMap &generated)
absl::Status WriteDungeonSpawnPointReport(Rom *rom, int spawn_id, resources::OutputFormatter &formatter, std::string_view object_title)
bool ParseHexString(absl::string_view str, int *out)
absl::StatusOr< zelda3::DungeonStreamLayout > ToDungeonStreamAllocatorLayout(DungeonStreamType stream_type, const DungeonStreamLayout &manifest_layout)
constexpr int kDoorPointers
absl::Status WriteTrackCollision(Rom *rom, int room_id, const CustomCollisionMap &map)
Room LoadRoomHeaderFromRom(Rom *rom, int room_id)
std::string GetRoomLabel(int id)
Convenience function to get a room label.
std::string GetItemLabel(int id)
Convenience function to get an item label.
absl::StatusOr< CustomCollisionMap > LoadCustomCollisionMap(Rom *rom, int room_id)
Room LoadRoomFromRom(Rom *rom, int room_id)
constexpr int kNumDungeonSpawnPoints
constexpr int kNumberOfRooms
constexpr int kRoomHeaderPointer
constexpr int kRoomHeaderPointerBank
const char * ResolveSpriteName(uint16_t id)
absl::StatusOr< TrackCollisionResult > GenerateTrackCollision(Room *room, const GeneratorOptions &options)
ResourceLabelProvider & GetResourceLabels()
Get the global ResourceLabelProvider instance.
constexpr int kRoomObjectPointer
uint32_t SnesToPc(uint32_t addr) noexcept
#define RETURN_IF_ERROR(expr)
zelda3::DungeonStreamLayout allocator_layout
core::HackManifest manifest
std::array< uint8_t, 64 *64 > tiles
std::vector< DungeonStreamPcRange > allocation_ranges
uint32_t pointer_table_pc
std::vector< std::pair< int, int > > switch_promotions