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(
76 absl::StrFormat(
"Collision data for room 0x%02X overlaps WaterFill "
77 "reserved region (pc=0x%06X)",
81 size_t cursor =
static_cast<size_t>(pc_ptr);
82 bool single_tiles_mode =
false;
83 bool found_end_marker =
false;
85 while (cursor + 1 < collision_safe_end) {
86 uint16_t offset = data[cursor] | (data[cursor + 1] << 8);
89 if (offset == kCollisionEndMarker) {
90 found_end_marker =
true;
94 if (offset == kCollisionSingleTileMarker) {
95 single_tiles_mode =
true;
99 if (!single_tiles_mode) {
100 if (cursor + 1 >= collision_safe_end) {
101 return absl::OutOfRangeError(
"Collision rectangle header out of range");
103 uint8_t width = data[cursor];
104 uint8_t height = data[cursor + 1];
107 if (width == 0 || height == 0) {
111 for (uint8_t row = 0; row < height; ++row) {
112 int row_offset =
static_cast<int>(offset) + (row * kCollisionMapWidth);
113 for (uint8_t col = 0; col < width; ++col) {
114 if (cursor >= collision_safe_end) {
115 return absl::OutOfRangeError(
116 "Collision rectangle data out of range");
118 uint8_t tile = data[cursor++];
119 int idx = row_offset + col;
121 result.
SetOverride(
static_cast<size_t>(idx), tile);
126 if (cursor >= collision_safe_end) {
127 return absl::OutOfRangeError(
"Collision single tile out of range");
129 uint8_t tile = data[cursor++];
130 int idx =
static_cast<int>(offset);
132 result.
SetOverride(
static_cast<size_t>(idx), tile);
137 if (has_water_fill_reserved_region && !found_end_marker) {
138 return absl::FailedPreconditionError(
139 absl::StrFormat(
"Collision data for room 0x%02X is unterminated before "
140 "WaterFill reserved region",
148 const std::vector<CustomCollisionRoomEntry>& rooms) {
149#if !defined(YAZE_WITH_JSON)
150 return absl::UnimplementedError(
151 "JSON support not enabled. Build with -DYAZE_WITH_JSON=ON");
153 using json = nlohmann::json;
155 std::vector<CustomCollisionRoomEntry> sorted = rooms;
157 sorted.begin(), sorted.end(),
159 return a.room_id < b.room_id;
164 json arr = json::array();
165 for (
const auto& r : sorted) {
172 std::unordered_map<int, int> tile_map;
173 tile_map.reserve(r.tiles.size());
174 for (
const auto& t : r.tiles) {
178 tile_map[
static_cast<int>(t.offset)] =
static_cast<int>(t.value);
181 std::vector<std::pair<int, int>> tiles;
182 tiles.reserve(tile_map.size());
183 for (
const auto& [off, val] : tile_map) {
184 tiles.emplace_back(off, val);
189 std::sort(tiles.begin(), tiles.end(),
190 [](
const auto& a,
const auto& b) { return a.first < b.first; });
193 item[
"room_id"] = absl::StrFormat(
"0x%02X", r.room_id);
194 json tiles_arr = json::array();
195 for (
const auto& [off, val] : tiles) {
196 tiles_arr.push_back(json::array({off, val}));
198 item[
"tiles"] = std::move(tiles_arr);
199 arr.push_back(std::move(item));
201 root[
"rooms"] = std::move(arr);
208#if !defined(YAZE_WITH_JSON)
209 return absl::UnimplementedError(
210 "JSON support not enabled. Build with -DYAZE_WITH_JSON=ON");
212 using json = nlohmann::json;
216 root = json::parse(json_content);
217 }
catch (
const json::parse_error& e) {
218 return absl::InvalidArgumentError(std::string(
"JSON parse error: ") +
222 const int version = root.value(
"version", 1);
224 return absl::InvalidArgumentError(absl::StrFormat(
225 "Unsupported custom collision JSON version: %d", version));
227 if (!root.contains(
"rooms") || !root[
"rooms"].is_array()) {
228 return absl::InvalidArgumentError(
"Missing or invalid 'rooms' array");
231 auto parse_int = [](
const json& v) -> std::optional<int> {
232 if (v.is_number_integer()) {
235 if (v.is_number_unsigned()) {
236 const auto u = v.get<
unsigned int>();
237 if (u >
static_cast<unsigned int>(std::numeric_limits<int>::max())) {
240 return static_cast<int>(u);
245 const std::string s = v.get<std::string>();
246 const int parsed = std::stoi(s, &idx, 0);
247 if (idx == s.size()) {
255 std::vector<CustomCollisionRoomEntry> out;
256 out.reserve(root[
"rooms"].size());
258 std::unordered_map<int, bool> seen_rooms;
259 for (
const auto& item : root[
"rooms"]) {
260 if (!item.is_object()) {
264 const json& room_v = item.contains(
"room_id") ? item[
"room_id"]
265 : item.contains(
"room") ? item[
"room"]
267 const auto room_id_opt = parse_int(room_v);
268 if (!room_id_opt.has_value() || *room_id_opt < 0 ||
270 return absl::InvalidArgumentError(
271 "Invalid room_id in custom collision JSON");
273 const int room_id = *room_id_opt;
274 if (seen_rooms.contains(room_id)) {
275 return absl::InvalidArgumentError(absl::StrFormat(
276 "Duplicate room_id in custom collision JSON: 0x%02X", room_id));
278 seen_rooms[room_id] =
true;
280 const json& tiles_v = item.contains(
"tiles") ? item[
"tiles"]
281 : item.contains(
"entries") ? item[
"entries"]
283 if (!tiles_v.is_array()) {
284 return absl::InvalidArgumentError(
285 absl::StrFormat(
"Invalid tiles array for room 0x%02X", room_id));
289 std::unordered_map<int, int> tile_map;
290 tile_map.reserve(tiles_v.size());
291 for (
const auto& entry : tiles_v) {
294 if (entry.is_array() && entry.size() >= 2) {
295 const auto off_opt = parse_int(entry[0]);
296 const auto val_opt = parse_int(entry[1]);
297 if (!off_opt.has_value() || !val_opt.has_value()) {
298 return absl::InvalidArgumentError(
299 absl::StrFormat(
"Invalid tile entry for room 0x%02X", room_id));
303 }
else if (entry.is_object()) {
304 const json& off_v = entry.contains(
"offset") ? entry[
"offset"]
305 : entry.contains(
"off") ? entry[
"off"]
307 const json& val_v = entry.contains(
"value") ? entry[
"value"]
308 : entry.contains(
"val") ? entry[
"val"]
310 const auto off_opt = parse_int(off_v);
311 const auto val_opt = parse_int(val_v);
312 if (!off_opt.has_value() || !val_opt.has_value()) {
313 return absl::InvalidArgumentError(
314 absl::StrFormat(
"Invalid tile entry for room 0x%02X", room_id));
319 return absl::InvalidArgumentError(
320 absl::StrFormat(
"Invalid tile entry for room 0x%02X", room_id));
324 return absl::InvalidArgumentError(
325 absl::StrFormat(
"Invalid tile offset for room 0x%02X", room_id));
327 if (val < 0 || val > 0xFF) {
328 return absl::InvalidArgumentError(
329 absl::StrFormat(
"Invalid tile value for room 0x%02X", room_id));
334 std::vector<std::pair<int, int>> tiles;
335 tiles.reserve(tile_map.size());
336 for (
const auto& [off, val] : tile_map) {
337 tiles.emplace_back(off, val);
339 std::sort(tiles.begin(), tiles.end(),
340 [](
const auto& a,
const auto& b) { return a.first < b.first; });
344 r.
tiles.reserve(tiles.size());
345 for (
const auto& [off, val] : tiles) {
347 t.
offset =
static_cast<uint16_t
>(off);
348 t.
value =
static_cast<uint8_t
>(val);
349 r.
tiles.push_back(std::move(t));
351 out.push_back(std::move(r));
355 out.begin(), out.end(),
357 return a.room_id < b.room_id;
std::vector< CustomCollisionTileEntry > tiles