35 return absl::InvalidArgumentError(
"ROM not loaded");
38 return absl::OutOfRangeError(
"Room id out of range");
41 const auto& data = rom->
vector();
43 if (pointer_offset < 0 ||
44 pointer_offset + 2 >=
static_cast<int>(data.size())) {
45 return absl::OutOfRangeError(
"Collision pointer table out of range");
51 const bool has_water_fill_reserved_region =
52 (kWaterFillTableEnd <= static_cast<int>(data.size()));
53 const size_t collision_safe_end =
54 has_water_fill_reserved_region
55 ? std::min(
static_cast<size_t>(data.size()),
57 :
static_cast<size_t>(data.size());
59 uint32_t snes_ptr = data[pointer_offset] | (data[pointer_offset + 1] << 8) |
60 (data[pointer_offset + 2] << 16);
71 if (pc_ptr < 0 || pc_ptr >=
static_cast<int>(data.size())) {
72 return absl::OutOfRangeError(
"Collision data pointer out of range");
74 if (
static_cast<size_t>(pc_ptr) >= collision_safe_end) {
75 return absl::FailedPreconditionError(absl::StrFormat(
76 "Collision data for room 0x%02X overlaps WaterFill reserved region (pc=0x%06X)",
80 size_t cursor =
static_cast<size_t>(pc_ptr);
81 bool single_tiles_mode =
false;
82 bool found_end_marker =
false;
84 while (cursor + 1 < collision_safe_end) {
85 uint16_t offset = data[cursor] | (data[cursor + 1] << 8);
88 if (offset == kCollisionEndMarker) {
89 found_end_marker =
true;
93 if (offset == kCollisionSingleTileMarker) {
94 single_tiles_mode =
true;
98 if (!single_tiles_mode) {
99 if (cursor + 1 >= collision_safe_end) {
100 return absl::OutOfRangeError(
"Collision rectangle header out of range");
102 uint8_t width = data[cursor];
103 uint8_t height = data[cursor + 1];
106 if (width == 0 || height == 0) {
110 for (uint8_t row = 0; row < height; ++row) {
111 int row_offset =
static_cast<int>(offset) + (row * kCollisionMapWidth);
112 for (uint8_t col = 0; col < width; ++col) {
113 if (cursor >= collision_safe_end) {
114 return absl::OutOfRangeError(
115 "Collision rectangle data out of range");
117 uint8_t tile = data[cursor++];
118 int idx = row_offset + col;
119 if (idx >= 0 && idx < kCollisionMapWidth * kCollisionMapHeight) {
120 result.
tiles[
static_cast<size_t>(idx)] = tile;
125 if (cursor >= collision_safe_end) {
126 return absl::OutOfRangeError(
"Collision single tile out of range");
128 uint8_t tile = data[cursor++];
129 int idx =
static_cast<int>(offset);
130 if (idx >= 0 && idx < kCollisionMapWidth * kCollisionMapHeight) {
131 result.
tiles[
static_cast<size_t>(idx)] = tile;
136 if (has_water_fill_reserved_region && !found_end_marker) {
137 return absl::FailedPreconditionError(absl::StrFormat(
138 "Collision data for room 0x%02X is unterminated before WaterFill reserved region",
146 const std::vector<CustomCollisionRoomEntry>& rooms) {
147#if !defined(YAZE_WITH_JSON)
148 return absl::UnimplementedError(
149 "JSON support not enabled. Build with -DYAZE_WITH_JSON=ON");
151 using json = nlohmann::json;
153 std::vector<CustomCollisionRoomEntry> sorted = rooms;
154 std::sort(sorted.begin(), sorted.end(),
157 return a.room_id < b.room_id;
162 json arr = json::array();
163 for (
const auto& r : sorted) {
170 std::unordered_map<int, int> tile_map;
171 tile_map.reserve(r.tiles.size());
172 for (
const auto& t : r.tiles) {
173 if (t.offset >= kCollisionMapTiles) {
176 tile_map[
static_cast<int>(t.offset)] =
static_cast<int>(t.value);
179 std::vector<std::pair<int, int>> tiles;
180 tiles.reserve(tile_map.size());
181 for (
const auto& [off, val] : tile_map) {
185 tiles.emplace_back(off, val);
190 std::sort(tiles.begin(), tiles.end(),
191 [](
const auto& a,
const auto& b) { return a.first < b.first; });
194 item[
"room_id"] = absl::StrFormat(
"0x%02X", r.room_id);
195 json tiles_arr = json::array();
196 for (
const auto& [off, val] : tiles) {
197 tiles_arr.push_back(json::array({off, val}));
199 item[
"tiles"] = std::move(tiles_arr);
200 arr.push_back(std::move(item));
202 root[
"rooms"] = std::move(arr);
209#if !defined(YAZE_WITH_JSON)
210 return absl::UnimplementedError(
211 "JSON support not enabled. Build with -DYAZE_WITH_JSON=ON");
213 using json = nlohmann::json;
217 root = json::parse(json_content);
218 }
catch (
const json::parse_error& e) {
219 return absl::InvalidArgumentError(
220 std::string(
"JSON parse error: ") + e.what());
223 const int version = root.value(
"version", 1);
225 return absl::InvalidArgumentError(
226 absl::StrFormat(
"Unsupported custom collision JSON version: %d",
229 if (!root.contains(
"rooms") || !root[
"rooms"].is_array()) {
230 return absl::InvalidArgumentError(
"Missing or invalid 'rooms' array");
233 auto parse_int = [](
const json& v) -> std::optional<int> {
234 if (v.is_number_integer()) {
237 if (v.is_number_unsigned()) {
238 const auto u = v.get<
unsigned int>();
239 if (u >
static_cast<unsigned int>(std::numeric_limits<int>::max())) {
242 return static_cast<int>(u);
247 const std::string s = v.get<std::string>();
248 const int parsed = std::stoi(s, &idx, 0);
249 if (idx == s.size()) {
258 std::vector<CustomCollisionRoomEntry> out;
259 out.reserve(root[
"rooms"].size());
261 std::unordered_map<int, bool> seen_rooms;
262 for (
const auto& item : root[
"rooms"]) {
263 if (!item.is_object()) {
268 item.contains(
"room_id") ? item[
"room_id"]
269 : item.contains(
"room") ? item[
"room"]
271 const auto room_id_opt = parse_int(room_v);
272 if (!room_id_opt.has_value() || *room_id_opt < 0 ||
274 return absl::InvalidArgumentError(
275 "Invalid room_id in custom collision JSON");
277 const int room_id = *room_id_opt;
278 if (seen_rooms.contains(room_id)) {
279 return absl::InvalidArgumentError(
280 absl::StrFormat(
"Duplicate room_id in custom collision JSON: 0x%02X",
283 seen_rooms[room_id] =
true;
285 const json& tiles_v =
286 item.contains(
"tiles") ? item[
"tiles"]
287 : item.contains(
"entries") ? item[
"entries"]
289 if (!tiles_v.is_array()) {
290 return absl::InvalidArgumentError(
291 absl::StrFormat(
"Invalid tiles array for room 0x%02X", room_id));
295 std::unordered_map<int, int> tile_map;
296 tile_map.reserve(tiles_v.size());
297 for (
const auto& entry : tiles_v) {
300 if (entry.is_array() && entry.size() >= 2) {
301 const auto off_opt = parse_int(entry[0]);
302 const auto val_opt = parse_int(entry[1]);
303 if (!off_opt.has_value() || !val_opt.has_value()) {
304 return absl::InvalidArgumentError(
305 absl::StrFormat(
"Invalid tile entry for room 0x%02X", room_id));
309 }
else if (entry.is_object()) {
311 entry.contains(
"offset") ? entry[
"offset"]
312 : entry.contains(
"off") ? entry[
"off"]
315 entry.contains(
"value") ? entry[
"value"]
316 : entry.contains(
"val") ? entry[
"val"]
318 const auto off_opt = parse_int(off_v);
319 const auto val_opt = parse_int(val_v);
320 if (!off_opt.has_value() || !val_opt.has_value()) {
321 return absl::InvalidArgumentError(
322 absl::StrFormat(
"Invalid tile entry for room 0x%02X", room_id));
327 return absl::InvalidArgumentError(
328 absl::StrFormat(
"Invalid tile entry for room 0x%02X", room_id));
331 if (off < 0 || off >= kCollisionMapTiles) {
332 return absl::InvalidArgumentError(
333 absl::StrFormat(
"Invalid tile offset for room 0x%02X", room_id));
335 if (val < 0 || val > 0xFF) {
336 return absl::InvalidArgumentError(
337 absl::StrFormat(
"Invalid tile value for room 0x%02X", room_id));
342 std::vector<std::pair<int, int>> tiles;
343 tiles.reserve(tile_map.size());
344 for (
const auto& [off, val] : tile_map) {
345 tiles.emplace_back(off, val);
347 std::sort(tiles.begin(), tiles.end(),
348 [](
const auto& a,
const auto& b) { return a.first < b.first; });
352 r.
tiles.reserve(tiles.size());
353 for (
const auto& [off, val] : tiles) {
355 t.
offset =
static_cast<uint16_t
>(off);
356 t.
value =
static_cast<uint8_t
>(val);
357 r.
tiles.push_back(std::move(t));
359 out.push_back(std::move(r));
362 std::sort(out.begin(), out.end(),
365 return a.room_id < b.room_id;
std::vector< CustomCollisionTileEntry > tiles