yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
room_object.cc
Go to the documentation of this file.
1#include "room_object.h"
2
3#include "absl/status/status.h"
4#include "util/log.h"
6
7namespace yaze {
8namespace zelda3 {
9
10namespace {
12 int base_ptr; // base address of subtype table in ROM (PC)
13 int index_mask; // mask to apply to object id for index
14 int id_offset; // offset to subtract from object_id before masking
15
16 SubtypeTableInfo(int base, int mask, int offset = 0)
17 : base_ptr(base), index_mask(mask), id_offset(offset) {}
18};
19
21 // Heuristic: 0x00-0xFF => subtype1, 0x100-0x1FF => subtype2, >=0xF80 =>
22 // subtype3. Type 3 IDs from decoding are 0xF80-0xFFF (b3 0xF8-0xFF shifted).
23 if (object_id >= 0xF80) {
24 // Type 3: IDs 0xF80-0xFFF map to table indices 0-127
25 // Subtract 0xF80 first, then mask with 0x7F
26 return SubtypeTableInfo(kRoomObjectSubtype3, 0x7F, 0xF80);
27 } else if (object_id >= 0x100) {
28 // Type 2: IDs 0x100-0x1FF map to table indices 0-255
29 return SubtypeTableInfo(kRoomObjectSubtype2, 0xFF, 0x100);
30 } else {
31 // Type 1: IDs 0x00-0xFF map directly to table indices
33 }
34}
35} // namespace
36
38 return static_cast<ObjectOption>(static_cast<int>(lhs) |
39 static_cast<int>(rhs));
40}
41
43 return static_cast<ObjectOption>(static_cast<int>(lhs) &
44 static_cast<int>(rhs));
45}
46
48 return static_cast<ObjectOption>(static_cast<int>(lhs) ^
49 static_cast<int>(rhs));
50}
51
53 return static_cast<ObjectOption>(~static_cast<int>(option));
54}
55
56// NOTE: DrawTile was legacy ZScream code that is no longer used.
57// Modern rendering uses ObjectDrawer which draws directly to BackgroundBuffer
58// bitmaps.
59
61 if (tiles_loaded_) {
62 return;
63 }
64
65 if (rom_ == nullptr) {
66 // DEBUG: Use printf for wall/corner objects (LOG_DEBUG may be filtered)
67 if (id_ == 0x001 || id_ == 0x002 || id_ == 0x061 || id_ == 0x062 ||
68 (id_ >= 0x100 && id_ <= 0x103)) {
69 printf("[EnsureTilesLoaded] obj=0x%03X: ROM is NULL!\n", id_);
70 }
71 return;
72 }
73
74 // Try the new parser first - this is more efficient and accurate
75 auto parser_status = LoadTilesWithParser();
76 if (parser_status.ok()) {
77 tiles_loaded_ = true;
78 // DEBUG: Use printf for wall/corner objects
79 if (id_ == 0x001 || id_ == 0x002 || id_ == 0x061 || id_ == 0x062 ||
80 (id_ >= 0x100 && id_ <= 0x103)) {
81 printf("[EnsureTilesLoaded] obj=0x%03X: Loaded %zu tiles via parser\n",
82 id_, tiles_.size());
83 }
84 return;
85 }
86
87 // DEBUG: Log parser failure for wall/corner objects
88 if (id_ == 0x001 || id_ == 0x002 || id_ == 0x061 || id_ == 0x062 ||
89 (id_ >= 0x100 && id_ <= 0x103)) {
90 printf("[EnsureTilesLoaded] obj=0x%03X: Parser failed: %s, trying legacy\n",
91 id_, std::string(parser_status.message()).c_str());
92 }
93
94 // Fallback to legacy method for compatibility with enhanced validation
95 auto rom_data = rom_->data();
96
97 // Determine which subtype table to use and compute the tile data offset.
98 SubtypeTableInfo sti = GetSubtypeTable(id_);
99 // Apply offset first (for Type 2/3 objects), then mask
100 int index = ((id_ - sti.id_offset) & sti.index_mask);
101 int tile_ptr = sti.base_ptr + (index * 2);
102
103 // Enhanced bounds checking
104 if (tile_ptr < 0 || tile_ptr + 1 >= (int)rom_->size()) {
105 // Log error but don't crash
106 LOG_DEBUG("RoomObject", "Tile pointer out of bounds for object %04X", id_);
107 tiles_.clear();
108 tiles_loaded_ = true; // Mark as loaded (empty) to prevent retry
109 return;
110 }
111
112 int tile_rel = (int16_t)((rom_data[tile_ptr + 1] << 8) + rom_data[tile_ptr]);
113 int pos = kRoomObjectTileAddress + tile_rel;
114 tile_data_ptr_ = pos;
115
116 // Enhanced bounds checking for tile data
117 if (pos < 0 || pos + 7 >= (int)rom_->size()) {
118 // Log error but don't crash
119 LOG_DEBUG("RoomObject", "Tile data position out of bounds for object %04X", id_);
120 tiles_.clear();
121 tiles_loaded_ = true; // Mark as loaded (empty) to prevent retry
122 return;
123 }
124
125 // Read tile data with validation
126 uint16_t w0 = (uint16_t)(rom_data[pos] | (rom_data[pos + 1] << 8));
127 uint16_t w1 = (uint16_t)(rom_data[pos + 2] | (rom_data[pos + 3] << 8));
128 uint16_t w2 = (uint16_t)(rom_data[pos + 4] | (rom_data[pos + 5] << 8));
129 uint16_t w3 = (uint16_t)(rom_data[pos + 6] | (rom_data[pos + 7] << 8));
130
131 tiles_.clear();
132 tiles_.push_back(gfx::WordToTileInfo(w0));
133 tiles_.push_back(gfx::WordToTileInfo(w1));
134 tiles_.push_back(gfx::WordToTileInfo(w2));
135 tiles_.push_back(gfx::WordToTileInfo(w3));
136 tile_count_ = 1;
137 tiles_loaded_ = true;
138}
139
141 if (rom_ == nullptr) {
142 return absl::InvalidArgumentError("ROM is null");
143 }
144
145 ObjectParser parser(rom_);
146 auto result = parser.ParseObject(id_);
147 if (!result.ok()) {
148 return result.status();
149 }
150
151 tiles_ = std::move(result.value());
152 tile_count_ = tiles_.size();
153 return absl::OkStatus();
154}
155
156absl::StatusOr<std::span<const gfx::TileInfo>> RoomObject::GetTiles() const {
157 if (!tiles_loaded_) {
158 const_cast<RoomObject*>(this)->EnsureTilesLoaded();
159 }
160
161 if (tiles_.empty()) {
162 return absl::FailedPreconditionError("No tiles loaded for object");
163 }
164
165 return std::span<const gfx::TileInfo>(tiles_.data(), tiles_.size());
166}
167
168absl::StatusOr<const gfx::TileInfo*> RoomObject::GetTile(int index) const {
169 if (!tiles_loaded_) {
170 const_cast<RoomObject*>(this)->EnsureTilesLoaded();
171 }
172
173 if (index < 0 || index >= static_cast<int>(tiles_.size())) {
174 return absl::OutOfRangeError(absl::StrFormat(
175 "Tile index %d out of range (0-%d)", index, tiles_.size() - 1));
176 }
177
178 return &tiles_[index];
179}
180
182 if (!tiles_loaded_) {
183 const_cast<RoomObject*>(this)->EnsureTilesLoaded();
184 }
185
186 return tile_count_;
187}
188
189// ============================================================================
190// Object Encoding/Decoding Implementation (Phase 1, Task 1.1)
191// ============================================================================
192
193int RoomObject::DetermineObjectType(uint8_t b1, uint8_t b3) {
194 // IMPORTANT: Check Type 2 FIRST to avoid boundary collision with Type 3.
195 // Type 2 objects with certain Y positions can produce b3 >= 0xF8, which
196 // would incorrectly trigger Type 3 decoding if we checked b3 first.
197 //
198 // Type 2: 111111xx xxxxyyyy yyiiiiii
199 // Discriminator: b1 >= 0xFC (top 6 bits all 1)
200 if (b1 >= 0xFC) {
201 return 2;
202 }
203
204 // Type 3: Objects with ID >= 0xF00
205 // These have b3 >= 0xF8 (top nibble is 0xF)
206 if (b3 >= 0xF8) {
207 return 3;
208 }
209
210 // Type 1: Standard objects (ID 0x00-0xFF)
211 return 1;
212}
213
214RoomObject RoomObject::DecodeObjectFromBytes(uint8_t b1, uint8_t b2, uint8_t b3,
215 uint8_t layer) {
216 uint8_t x = 0;
217 uint8_t y = 0;
218 uint8_t size = 0;
219 uint16_t id = 0;
220
221 // IMPORTANT: Check Type 2 FIRST to avoid boundary collision with Type 3.
222 // Type 2 objects with certain Y positions can produce b3 >= 0xF8, which
223 // would incorrectly trigger Type 3 decoding if we checked b3 first.
224
225 // Type 2: 111111xx xxxxyyyy yyiiiiii
226 // Discriminator: b1 >= 0xFC (top 6 bits all 1)
227 if (b1 >= 0xFC) {
228 id = (b3 & 0x3F) | 0x100;
229 x = ((b2 & 0xF0) >> 4) | ((b1 & 0x03) << 4);
230 y = ((b2 & 0x0F) << 2) | ((b3 & 0xC0) >> 6);
231 size = 0;
232 LOG_DEBUG("ObjectParser",
233 "Type2: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d",
234 b1, b2, b3, id, x, y, size);
235 }
236 // Type 3: xxxxxxii yyyyyyii 11111iii
237 // Discriminator: b3 >= 0xF8 (top 5 bits all 1)
238 else if (b3 >= 0xF8) {
239 id = (static_cast<uint16_t>(b3) << 4) | 0x80 |
240 ((static_cast<uint16_t>(b2 & 0x03) << 2) + (b1 & 0x03));
241 x = (b1 & 0xFC) >> 2;
242 y = (b2 & 0xFC) >> 2;
243 size = ((b1 & 0x03) << 2) | (b2 & 0x03);
244 LOG_DEBUG("ObjectParser",
245 "Type3: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d",
246 b1, b2, b3, id, x, y, size);
247 }
248 // Type 1: xxxxxxss yyyyyyss iiiiiiii
249 else {
250 id = b3;
251 x = (b1 & 0xFC) >> 2;
252 y = (b2 & 0xFC) >> 2;
253 size = ((b1 & 0x03) << 2) | (b2 & 0x03);
254 LOG_DEBUG("ObjectParser",
255 "Type1: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d",
256 b1, b2, b3, id, x, y, size);
257 }
258
259 auto obj = RoomObject(static_cast<int16_t>(id), x, y, size, layer);
260
261 // Set all_bgs flag for objects that draw to both BG1 and BG2
262 // Based on ZScream/ObjectDrawer logic
263 if ((id >= 0x03 && id <= 0x04) || // Routine 3
264 (id >= 0x63 && id <= 0x64) || // Routine 9
265 // Routine 17 (Acute Diagonals)
266 id == 0x0C || id == 0x0D || id == 0x10 || id == 0x11 || id == 0x14 ||
267 id == 0x15 || id == 0x18 || id == 0x19 || id == 0x1C || id == 0x1D ||
268 id == 0x20 ||
269 // Routine 18 (Grave Diagonals)
270 id == 0x0E || id == 0x0F || id == 0x12 || id == 0x13 || id == 0x16 ||
271 id == 0x17 || id == 0x1A || id == 0x1B || id == 0x1E || id == 0x1F) {
272 obj.all_bgs_ = true;
273 }
274
275 return obj;
276}
277
279 ObjectBytes bytes;
280
281 // Determine type based on object ID
282 if (id_ >= 0x100 && id_ < 0x200) {
283 // Type 2: 111111xx xxxxyyyy yyiiiiii
284 bytes.b1 = 0xFC | ((x_ & 0x30) >> 4);
285 bytes.b2 = ((x_ & 0x0F) << 4) | ((y_ & 0x3C) >> 2);
286 bytes.b3 = ((y_ & 0x03) << 6) | (id_ & 0x3F);
287 } else if (id_ >= 0xF00) {
288 // Type 3: xxxxxxii yyyyyyii 11111iii
289 bytes.b1 = (x_ << 2) | (id_ & 0x03);
290 bytes.b2 = (y_ << 2) | ((id_ >> 2) & 0x03);
291 bytes.b3 = (id_ >> 4) & 0xFF;
292 } else {
293 // Type 1: xxxxxxss yyyyyyss iiiiiiii
294 uint8_t clamped_size = size_ > 15 ? 15 : size_;
295 bytes.b1 = (x_ << 2) | ((clamped_size >> 2) & 0x03);
296 bytes.b2 = (y_ << 2) | (clamped_size & 0x03);
297 bytes.b3 = static_cast<uint8_t>(id_);
298 }
299
300 return bytes;
301}
302
303} // namespace zelda3
304} // namespace yaze
auto data() const
Definition rom.h:135
auto size() const
Definition rom.h:134
Direct ROM parser for dungeon objects.
absl::StatusOr< std::vector< gfx::TileInfo > > ParseObject(int16_t object_id)
Parse object data directly from ROM.
absl::StatusOr< const gfx::TileInfo * > GetTile(int index) const
static RoomObject DecodeObjectFromBytes(uint8_t b1, uint8_t b2, uint8_t b3, uint8_t layer)
std::vector< gfx::TileInfo > tiles_
ObjectBytes EncodeObjectToBytes() const
static int DetermineObjectType(uint8_t b1, uint8_t b3)
absl::Status LoadTilesWithParser()
uint8_t size() const
Definition room_object.h:78
absl::StatusOr< std::span< const gfx::TileInfo > > GetTiles() const
RoomObject(int16_t id, uint8_t x, uint8_t y, uint8_t size, uint8_t layer=0)
Definition room_object.h:54
#define LOG_DEBUG(category, format,...)
Definition log.h:103
TileInfo WordToTileInfo(uint16_t word)
Definition snes_tile.cc:321
SubtypeTableInfo GetSubtypeTable(int object_id)
constexpr int kRoomObjectSubtype3
Definition room_object.h:46
ObjectOption operator|(ObjectOption lhs, ObjectOption rhs)
ObjectOption operator^(ObjectOption lhs, ObjectOption rhs)
constexpr int kRoomObjectSubtype1
Definition room_object.h:44
constexpr int kRoomObjectSubtype2
Definition room_object.h:45
constexpr int kRoomObjectTileAddress
Definition room_object.h:47
ObjectOption operator~(ObjectOption option)
ObjectOption operator&(ObjectOption lhs, ObjectOption rhs)