126 const std::string& path) {
127 std::ifstream file(path);
129 return absl::NotFoundError(
130 absl::StrFormat(
"Cannot read symbol file %s", path));
132 std::map<std::string, uint32_t> symbols;
134 while (std::getline(file, line)) {
135 if (!line.empty() && line.back() ==
'\r') {
138 const size_t start = line.find_first_not_of(
" \t");
139 if (start == std::string::npos || line.size() < start + 8 ||
140 line[start + 2] !=
':') {
143 const std::string bank = line.substr(start, 2);
144 const std::string addr = line.substr(start + 3, 4);
145 if (!IsHexString(bank) || !IsHexString(addr)) {
148 const size_t name_start = line.find_first_not_of(
" \t", start + 7);
149 if (name_start == std::string::npos || name_start == start + 7) {
152 const size_t name_end = line.find_first_of(
" \t", name_start);
153 const std::string
name = line.substr(name_start, name_end - name_start);
154 const uint32_t snes =
155 (std::stoul(bank,
nullptr, 16) << 16) | std::stoul(addr,
nullptr, 16);
156 symbols.emplace(
name, snes);
163 if (rom ==
nullptr || !rom->
is_loaded()) {
164 return absl::FailedPreconditionError(
"ROM is not loaded");
167 input.
extras.resize(kRooms);
174 if (room_id < 0 || room_id >= kRooms) {
177 auto& extras = input.
extras[room_id];
178 for (
const auto& door : room.GetDoors()) {
179 const auto [tile_x, tile_y] = door.GetTileCoords();
181 fact.
direction =
static_cast<int>(door.direction);
184 fact.
type =
static_cast<int>(door.type);
185 fact.
type_name = std::string(door.GetTypeName());
186 extras.doors.push_back(std::move(fact));
188 for (
const auto&
chest : room.GetChests()) {
189 extras.chests.push_back({
static_cast<int>(
chest.id),
chest.size});
191 for (
const auto& pot : room.GetPotItems()) {
192 extras.pot_items.push_back(pot.item);
194 for (
const auto& sprite : room.GetSprites()) {
195 extras.key_drops_small += sprite.key_drop() == 1 ? 1 : 0;
196 extras.key_drops_big += sprite.key_drop() == 2 ? 1 : 0;
201 return census.status();
203 input.
census = std::move(census).value();
207 for (
const auto& room : dungeon.rooms) {
208 if (room.id >= 0 && room.id < kRooms && !room.name.empty()) {
215 input.
rom.
name = std::filesystem::path(rom->
filename()).filename().string();
223 return symbols.status();
226 const auto& data = rom->
vector();
227 for (
size_t i = 0; i < options.
symbol_links.size(); ++i) {
229 auto it = symbols->find(link.symbol);
230 if (it == symbols->end()) {
233 const size_t pc = LoRomPc(it->second);
234 const size_t bytes =
static_cast<size_t>(link.count) * link.width;
235 if (pc + bytes > data.size()) {
240 for (
int n = 0; n < link.count; ++n) {
241 const size_t at = pc +
static_cast<size_t>(n) * link.width;
242 read.values.push_back(link.width == 2 ? (data[at] | (data[at + 1] << 8))
263 std::vector<RoomCensusRoomFacts> facts = input.
census.
rooms;
264 facts.resize(kRooms);
265 std::vector<DungeonAtlasRoomExtras> extras = input.
extras;
266 extras.resize(kRooms);
269 size_t next_color = 0;
270 auto palette_color = [&]() {
271 return palette[next_color++ % palette.size()];
275 auto annotated_by_owner =
277 for (
const auto& dungeon : annotations.
dungeons) {
278 if (!dungeon.owner.empty() && dungeon.owner == id) {
285 for (
const auto& dungeon : annotations.
dungeons) {
286 if (dungeon.owner.empty() && dungeon.dungeon_id == dungeon_id) {
294 std::vector<OwnerDisplay> owners(census.
owners.size());
295 for (
size_t i = 0; i < census.
owners.size(); ++i) {
296 const auto& owner = census.
owners[i];
297 OwnerDisplay& display = owners[i];
298 if (owner.interior) {
302 if (owner.from_project) {
303 entry = annotated_by_owner(owner.id);
304 display.own_area =
true;
307 entry = annotated_by_id(owner.dungeon_id);
318 if (!display.own_area) {
321 const bool names_apply =
324 display.
code = names_apply && !entry->
code.empty() ? entry->
code : owner.id;
326 names_apply && !entry->
name.empty() ? entry->
name : owner.name;
327 if (names_apply && (!entry->
code.empty() || !entry->
name.empty())) {
328 display.origin = entry->
origin;
330 if (entry !=
nullptr && entry->
color.has_value()) {
331 display.color = *entry->
color;
334 display.color = palette_color();
337 AtlasArea& area = areas.Get(display.code);
338 area.
code = display.code;
339 area.
name = display.name;
340 area.
color = display.color;
341 area.
kind =
"dungeon";
342 area.
owner = owner.id;
343 area.
origin = display.origin;
347 std::map<int, const AreaAnnotation*> area_of;
348 for (
const auto& area : annotations.
areas) {
349 for (
int room : area.rooms) {
350 area_of[room] = &area;
353 out.
code = area.code.empty() ? area.key : area.code;
354 out.
name = area.name.empty() ? area.key : area.name;
355 out.
kind = area.key ==
"INT" ?
"interior" :
"area";
357 if (area.color.has_value()) {
358 out.
color = *area.color;
360 }
else if (area.key ==
"INT") {
364 out.
color = palette_color();
368 auto ensure_fallback_area = [&](
const std::string& key) {
369 if (areas.Has(key)) {
373 area.
code = key ==
"OTHER" ?
"ID?" : key ==
"NONE" ?
"" : key;
378 area.
kind =
"interior";
380 entry !=
nullptr && !entry->
name.empty() ? entry->
name :
"Interiors";
381 area.
color = entry !=
nullptr && entry->
color.has_value()
384 if (entry !=
nullptr) {
388 }
else if (key ==
"OTHER") {
390 area.
name =
"Dungeon ID not in project";
394 area.
name =
"No owner";
400 atlas.
rooms.resize(kRooms);
401 for (
int r = 0; r < kRooms; ++r) {
412 room.
tag1 = facts[r].tag1;
413 room.
tag2 = facts[r].tag2;
415 room.
pit_damage = facts[r].in_pit_damage_table;
418 room.
owner = owner.id;
422 const OwnerDisplay* display =
424 const bool from_project =
426 if (display !=
nullptr && display->own_area) {
427 room.
area = display->code;
429 }
else if (
auto it = area_of.find(r); it != area_of.end()) {
430 room.
area = it->second->key;
435 ensure_fallback_area(
"INT");
439 ensure_fallback_area(
"OTHER");
443 ensure_fallback_area(
"NONE");
459 if (
auto it = annotations.
rooms.find(r); it != annotations.
rooms.end()) {
460 room.
note = it->second.note;
461 if (!it->second.name.empty()) {
462 room.
name = it->second.name;
466 if (room.
name.empty()) {
469 room.
name = it->second;
478 const auto& items = annotations.
items;
479 for (
const auto&
chest : extras[r].chests) {
480 const bool known =
chest.
item == items.small_key ||
488 }
else if (
chest.
item == items.small_key) {
490 }
else if (
chest.
item == items.compass) {
494 }
else if (!known && !
chest.big) {
498 room.
badges.
k_pot =
static_cast<int>(std::count(extras[r].pot_items.begin(),
499 extras[r].pot_items.end(),
500 items.pot_small_key));
509 for (
int r = 0; r < kRooms; ++r) {
512 std::vector<int> stair_targets;
513 bool has_warp_tag_links =
false;
514 for (
const auto& source :
523 atlas.
warnings.push_back(absl::StrFormat(
524 "room %s %s slot %d points past $127 (%s); dropped", RoomHex(r),
525 stair ?
"stair" :
"warp tag", source.slot + 1,
526 RoomHex(source.raw_to)));
530 if (source.to == r ||
531 std::find(stair_targets.begin(), stair_targets.end(),
532 source.to) != stair_targets.end()) {
535 stair_targets.push_back(source.to);
538 link.
note =
"slot by placement order";
542 has_warp_tag_links =
true;
544 link.
index = source.slot + 1;
548 absl::StrFormat(
"room %s holewarp points past $127 (%s); dropped",
549 RoomHex(r), RoomHex(source.raw_to)));
552 if (source.to == r) {
555 if (!source.strong) {
558 switch (source.holewarp_cause) {
563 if (has_warp_tag_links) {
586 room.
links_out.push_back(std::move(link));
591 for (
const auto& note : annotations.
links) {
593 note.from >= kRooms) {
597 for (
const auto& existing : room.
links_out) {
598 if (existing.to == note.to && note.to >= 0 &&
602 atlas.
warnings.push_back(absl::StrFormat(
603 "%s: %s link %s -> %s repeats a ROM fact", note.origin.ref,
604 note.kind, RoomHex(note.from), RoomHex(note.to)));
610 link.
from = note.from;
612 link.
style = note.kind;
613 link.
label = note.label;
614 link.
note = note.note;
615 link.
origin = note.origin;
616 room.
links_out.push_back(std::move(link));
620 if (fact.room_id < 0 || fact.room_id >= kRooms) {
637 auto& links = atlas.
rooms[fact.room_id].links_in;
638 const bool duplicate =
639 std::any_of(links.begin(), links.end(), [&](
const AtlasLink& seen) {
640 return seen.kind == kind &&
641 (kind == LinkKind::kSpawn
642 ? seen.index == fact.entrance_id
643 : seen.entrance == fact.entrance_id &&
644 seen.ow_map == fact.ow_map);
651 link.
to = fact.room_id;
653 link.
index = fact.entrance_id;
656 link.
ow_map = fact.ow_map;
659 links.push_back(std::move(link));
661 for (
size_t i = 0; i < annotations.
symbol_links.size(); ++i) {
665 if (read ==
nullptr || !read->
found) {
668 ? absl::StrFormat(
"%s: symbol link %s skipped: no .sym file",
669 symbol_link.origin.ref, symbol_link.symbol)
670 : absl::StrFormat(
"%s: symbol %s not found in %s",
671 symbol_link.origin.ref, symbol_link.symbol,
677 for (
size_t n = 0; n < read->
values.size(); ++n) {
678 const int target = read->
values[n];
679 if (target < 0 || target >= kRooms) {
680 atlas.
warnings.push_back(absl::StrFormat(
681 "symbol %s entry %d is room %s, past $127; dropped",
682 symbol_link.symbol,
static_cast<int>(n + 1), RoomHex(target)));
688 link.
index =
static_cast<int>(n + 1);
689 link.
style = symbol_link.kind;
690 link.
label = ExpandLabel(symbol_link.label, symbol_link.kind,
691 static_cast<int>(n + 1));
693 atlas.
rooms[target].links_in.push_back(std::move(link));
698 for (
auto& room : atlas.
rooms) {
699 for (
auto& link : room.links_out) {
700 if (link.to >= 0 && link.to < kRooms) {
701 link.cross_area = atlas.
rooms[link.to].area != room.area;
712 for (
const auto& [room_id, note] : annotations.
rooms) {
713 if (note.reserved && room_id >= 0 && room_id < kRooms) {
714 make_reserved(atlas.
rooms[room_id], note.origin);
717 for (
auto& room : atlas.
rooms) {
718 for (
const auto& link : room.
links_in) {
720 make_reserved(room, link.origin);
723 for (
const auto& link : room.
links_out) {
726 make_reserved(atlas.
rooms[link.to], link.origin);
730 for (
const auto& plan : annotations.
plans) {
733 out.
label = plan.label.empty() ? plan.key : plan.label;
734 out.
name = plan.name;
737 if (plan.color.has_value()) {
738 out.
color = *plan.color;
740 out.
rooms = plan.rooms;
741 out.
note = plan.note;
743 for (
int room : plan.rooms) {
744 if (room >= 0 && room < kRooms) {
745 atlas.
rooms[room].plans.push_back(plan.key);
748 atlas.
plans.push_back(std::move(out));
752 std::vector<std::vector<AtlasDoor>> doors_by_room(kRooms);
753 for (
int r = 0; r < kRooms; ++r) {
754 doors_by_room[r] = atlas.
rooms[r].doors;
758 std::map<std::string, AtlasKeyTableRow> key_rows;
759 for (
const auto& room : atlas.
rooms) {
760 ++areas.Get(room.
area).rooms;
761 auto& row = key_rows[room.
area];
762 row.area = room.
area;
789 atlas.
areas = areas.Take();
790 for (
const auto& area : atlas.
areas) {
791 const auto it = key_rows.find(area.key);
792 if (it == key_rows.end()) {
795 const auto& row = it->second;
796 if (row.k_chest + row.k_pot + row.k_drop + row.bk + row.bc + row.c + row.m >