130 std::span<const uint8_t> data) {
131 const auto matches = [&](
size_t pc, std::initializer_list<uint8_t> bytes) {
132 return pc <= data.size() && bytes.size() <= data.size() - pc &&
133 std::equal(bytes.begin(), bytes.end(), data.begin() + pc);
135 const bool marker = matches(0x7F000, {0x00, 0x01});
136 const bool hook = matches(0x1599F, {0x22});
137 const bool relocated_load = matches(0x15A74, {0xBD, 0x00, 0x96});
139 if (marker || hook || relocated_load) {
140 const auto invalid = [](
const char* reason) {
141 return absl::FailedPreconditionError(absl::StrFormat(
142 "Unsupported or malformed expanded dungeon entrance layout: %s",
145 if (!marker || !matches(0x1599F, {0x22, 0x08, 0xF0, 0x0F}) ||
146 !matches(0x15ACE, {0x22, 0x15, 0xF0, 0x0F})) {
147 return invalid(
"marker or databank hooks do not match");
149 if (!matches(0x7F008, {0xAA, 0x0A, 0x0A, 0xA8, 0xE2, 0x20, 0xA9, 0x0F, 0x48,
150 0xAB, 0xC2, 0x20, 0x6B}) ||
152 {0x29, 0x0F, 0x85, 0xAA, 0xA9, 0x02, 0x48, 0xAB, 0x6B})) {
153 return invalid(
"databank helpers do not match");
156 const auto operand_matches = [&](uint32_t pc, uint8_t opcode,
158 const auto snes =
static_cast<uint16_t
>((table & 0x7FFF) | 0x8000);
159 return matches(pc, {opcode,
static_cast<uint8_t
>(snes),
160 static_cast<uint8_t
>(snes >> 8)});
163 if (!operand_matches(site.pc, site.opcode, layout.table(site.field))) {
164 return invalid(
"loader table operand does not match");
167 for (
int i = 1; i < 8; ++i) {
168 if (!operand_matches(0x15A1A + i * 6, 0xB9, 0x78200 + i)) {
169 return invalid(
"camera boundary operand does not match");
172 for (
const auto& [pc, table] : std::array<std::pair<uint32_t, uint32_t>, 3>{
173 {{0x15AAA, 0x79A00}, {0x15ABB, 0x79B00}, {0x15ACB, 0x79C00}}}) {
174 if (!operand_matches(pc, 0xBD, table)) {
175 return invalid(
"secondary loader table operand does not match");
179 for (
const auto& [base, width] : layout.tables) {
181 return absl::FailedPreconditionError(
182 "Dungeon entrance tables exceed ROM size");