yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
special_entrance_inspector.h
Go to the documentation of this file.
1#ifndef YAZE_ZELDA3_OVERWORLD_SPECIAL_ENTRANCE_INSPECTOR_H_
2#define YAZE_ZELDA3_OVERWORLD_SPECIAL_ENTRANCE_INSPECTOR_H_
3
4#include <algorithm>
5#include <array>
6#include <cstdint>
7#include <string>
8#include <vector>
9
11#include "rom/rom.h"
13
14namespace yaze::zelda3 {
15
16// The inspected routine is the four-entry $0EDE49 implementation. These
17// results describe the loaded ROM and the current Overworld model, not an ASM
18// source tree or a claim that a controller can reach the trigger.
20 uint16_t trigger = 0;
21 uint16_t source_area = 0;
22 uint16_t direction = 0;
23 uint16_t destination_marker = 0;
24 int destination_area = -1; // Only known $0180-$019F marker encodings.
25};
26
28 int screen_id = 0;
29 int parent_area = 0;
30 int tile_x = 0; // Screen-local Tile16 coordinate.
31 int tile_y = 0;
32 int world_x = 0; // Canvas Tile16 coordinate.
33 int world_y = 0;
34 uint16_t tile16_id = 0;
35 uint16_t first_word = 0;
36 uint16_t masked_trigger = 0;
37 int row_index = 0;
38};
39
41 bool available = false;
42 std::string provenance;
43 std::string limitation;
45 int inspected_map = -1;
46 int parent_area = -1;
47 std::array<SpecialEntranceRow, 4> rows{};
48 std::vector<SpecialEntranceMatch> matches;
49};
50
52 Overworld& overworld,
53 int map_id) {
55 result.inspected_map = map_id;
56 result.limitation =
57 "A tile match does not prove a reachable Link approach or a completed "
58 "transition. Link samples at (x+8,y+12); direction is arrival behavior.";
59 const auto* map = overworld.overworld_map(map_id);
60 if (!overworld.is_loaded() || map == nullptr) {
61 result.provenance = "Unavailable: no loaded overworld map";
62 return result;
63 }
64 result.parent_area = map->parent();
65 const auto* parent = overworld.overworld_map(result.parent_area);
67 parent != nullptr &&
68 (parent->has_overlay() || parent->render_subscreen_overlay() != 0x00FF);
69
70 // LoROM $0EDE49. Validate the actual compiled reader, including its count,
71 // Map16Definitions operand, masked comparison, area comparison and target
72 // table operands. A ROM with a different hook/table is left unsupported.
73 constexpr size_t kReader = 0x75E49;
74 constexpr size_t kTable = 0x75E29;
75 constexpr std::array<uint8_t, 17> kReaderPrefix = {
76 0xC2, 0x31, 0x20, 0x9A, 0xDE, 0xBF, 0x00, 0x80, 0xBD,
77 0x29, 0xFF, 0x01, 0x85, 0x00, 0xA2, 0x08, 0x00};
78 constexpr std::array<uint8_t, 18> kReaderComparisons = {
79 0xDF, 0x29, 0xDE, 0x0E, 0xD0, 0xF6, 0xA5, 0x8A, 0xDF,
80 0x31, 0xDE, 0x0E, 0xD0, 0xEC, 0xBF, 0x41, 0xDE, 0x0E};
81 if (rom.size() < kReader + 0x52 ||
82 !std::equal(kReaderPrefix.begin(), kReaderPrefix.end(),
83 rom.data() + kReader) ||
84 !std::equal(kReaderComparisons.begin(), kReaderComparisons.end(),
85 rom.data() + kReader + 0x17)) {
86 result.provenance =
87 "Unavailable: loaded ROM does not have the verified four-row $0EDE49 "
88 "reader";
89 return result;
90 }
91 const auto read_word = [&rom](size_t offset) -> uint16_t {
92 return static_cast<uint16_t>(rom.data()[offset] |
93 (rom.data()[offset + 1] << 8));
94 };
95 for (int i = 0; i < 4; ++i) {
96 const uint16_t marker = read_word(kTable + 24 + i * 2);
97 result.rows[i] = {read_word(kTable + i * 2), read_word(kTable + 8 + i * 2),
98 read_word(kTable + 16 + i * 2), marker,
99 marker >= 0x0180 && marker <= 0x019F
100 ? static_cast<int>(marker & 0xFF)
101 : -1};
102 }
103 result.available = true;
104 result.provenance = "Loaded ROM " + rom.filename() +
105 ": compiled $0EDE49 reader and $0EDE29 table";
106 const int world = map_id / 0x40;
107 const auto& grid = overworld.GetMapTiles(world);
108 const auto& definitions = overworld.tiles16();
109 for (int screen = world * 0x40;
110 screen < std::min((world + 1) * 0x40, kNumOverworldMaps); ++screen) {
111 const auto* candidate = overworld.overworld_map(screen);
112 if (candidate == nullptr || candidate->parent() != result.parent_area) {
113 continue;
114 }
115 const int left = (screen % 8) * 32;
116 const int top = ((screen % 0x40) / 8) * 32;
117 for (int y = 0; y < 32; ++y) {
118 for (int x = 0; x < 32; ++x) {
119 if (left + x >= static_cast<int>(grid.size()) ||
120 top + y >= static_cast<int>(grid[left + x].size())) {
121 continue;
122 }
123 const uint16_t tile16_id = grid[left + x][top + y];
124 if (tile16_id >= definitions.size()) {
125 continue;
126 }
127 const uint16_t first_word =
128 gfx::TileInfoToWord(definitions[tile16_id].tile0_);
129 const uint16_t masked = first_word & 0x01FF;
130 // The compiled loop decrements X from 8 before each comparison.
131 // A duplicate pair therefore resolves to the last table row.
132 for (int row = 3; row >= 0; --row) {
133 if (result.rows[row].source_area == result.parent_area &&
134 result.rows[row].trigger == masked) {
135 result.matches.push_back({screen, result.parent_area, x, y,
136 left + x, top + y, tile16_id, first_word,
137 masked, row});
138 break;
139 }
140 }
141 }
142 }
143 }
144 return result;
145}
146
147} // namespace yaze::zelda3
148
149#endif // YAZE_ZELDA3_OVERWORLD_SPECIAL_ENTRANCE_INSPECTOR_H_
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:49
auto filename() const
Definition rom.h:207
auto data() const
Definition rom.h:201
auto size() const
Definition rom.h:200
const std::vector< gfx::Tile16 > & tiles16() const
Definition overworld.h:751
auto is_loaded() const
Definition overworld.h:803
auto overworld_map(int i) const
Definition overworld.h:737
OverworldBlockset & GetMapTiles(int world_type)
Definition overworld.h:721
uint16_t TileInfoToWord(TileInfo tile_info)
Definition snes_tile.cc:362
Zelda 3 specific classes and functions.
constexpr int kNumOverworldMaps
Definition common.h:85
SpecialEntranceInspection InspectSpecialEntrances(const Rom &rom, Overworld &overworld, int map_id)
std::vector< SpecialEntranceMatch > matches
std::array< SpecialEntranceRow, 4 > rows