8#include "absl/strings/str_cat.h"
38 auto room_size_address = 0xF8000 + (room_id * 3);
41 uint8_t low = rom->
data()[room_size_address];
42 uint8_t high = rom->
data()[room_size_address + 1];
43 uint8_t bank = rom->
data()[room_size_address + 2];
46 int long_address = (bank << 16) | (high << 8) | low;
49 if (long_address == 0x0A8000) {
57 int next_room_address = 0xF8000 + ((room_id + 1) * 3);
60 uint8_t next_low = rom->
data()[next_room_address];
61 uint8_t next_high = rom->
data()[next_room_address + 1];
62 uint8_t next_bank = rom->
data()[next_room_address + 2];
65 int next_long_address = (next_bank << 16) | (next_high << 8) | next_low;
68 int actual_room_size = next_long_address - long_address;
76 Room room(room_id, rom);
81 header_pointer =
SnesToPc(header_pointer);
84 (rom->
data()[(header_pointer + 1) + (room_id * 2)] << 8) +
85 rom->
data()[(header_pointer) + (room_id * 2)];
87 auto header_location =
SnesToPc(address);
94 room.
SetBg2(background2::DarkRoom);
120 header_pointer_2 =
SnesToPc(header_pointer_2);
123 (rom->
data()[(header_pointer_2 + 1) + (room_id * 2)] << 8) +
124 rom->
data()[(header_pointer_2) + (room_id * 2)];
130 uint8_t b = rom->
data()[hpos];
155 b = rom->
data()[hpos];
178 int room_address = object_pointer + (room_id * 3);
179 int objects_location =
SnesToPc(room_address);
196 const auto &room_gfx =
rom()->room_blockset_ids;
197 const auto &sprite_gfx =
rom()->spriteset_ids;
199 for (
int i = 0; i < 8; i++) {
201 if (i >= 6 && i <= 6) {
203 if (entrance_blockset != 0xFF &&
204 room_gfx[entrance_blockset][i - 3] != 0) {
205 blocks_[i] = room_gfx[entrance_blockset][i - 3];
214 for (
int i = 0; i < 4; i++) {
230 printf(
"[CopyRoomGraphicsToBuffer] ROM not loaded\n");
234 auto gfx_buffer_data =
rom()->mutable_graphics_buffer();
235 if (!gfx_buffer_data || gfx_buffer_data->empty()) {
236 printf(
"[CopyRoomGraphicsToBuffer] Graphics buffer is null or empty\n");
240 printf(
"[CopyRoomGraphicsToBuffer] Room %d: Copying graphics from blocks\n",
room_id_);
244 int bytes_copied = 0;
245 for (
int i = 0; i < 16; i++) {
256 if (block_offset < 0 || block_offset >=
static_cast<int>(gfx_buffer_data->size())) {
262 int buffer_index = data + block_offset;
263 if (buffer_index >= 0 && buffer_index <
static_cast<int>(gfx_buffer_data->size())) {
264 uint8_t map_byte = (*gfx_buffer_data)[buffer_index];
273 int gfx_index = data + sheet_pos;
274 if (gfx_index >= 0 && gfx_index <
static_cast<int>(
sizeof(
current_gfx16_))) {
276 if (map_byte != 0) bytes_copied++;
285 printf(
"[CopyRoomGraphicsToBuffer] Room %d: Copied %d non-zero bytes to current_gfx16_\n",
room_id_, bytes_copied);
291 bool properties_changed =
false;
304 properties_changed =
true;
314 properties_changed =
true;
321 if (bg1_bmp.texture() && bg2_bmp.texture()) {
322 LOG_DEBUG(
"[RenderRoomGraphics]",
"Room %d: No changes detected, skipping render",
room_id_);
327 LOG_DEBUG(
"[RenderRoomGraphics]",
"Room %d: Rendering graphics (dirty_flags: g=%d o=%d l=%d t=%d)",
343 LOG_DEBUG(
"[RenderRoomGraphics]",
"Room %d: floor1=%d, floor2=%d, blocks_size=%zu",
355 if (!bg1_bmp.is_active() || bg1_bmp.width() == 0 || !bg2_bmp.is_active() || bg2_bmp.width() == 0) {
356 need_floor_draw =
true;
357 LOG_DEBUG(
"[RenderRoomGraphics]",
"Room %d: Bitmaps not created yet, forcing floor draw",
room_id_);
360 if (need_floor_draw) {
375 auto& dungeon_pal_group =
rom()->mutable_palette_group()->dungeon_main;
376 int num_palettes = dungeon_pal_group.size();
381 auto dungeon_palette_ptr =
rom()->paletteset_ids[
palette][0];
382 auto palette_word =
rom()->ReadWord(0xDEC4B + dungeon_palette_ptr);
383 if (palette_word.ok()) {
384 palette_id = palette_word.value() / 180;
385 LOG_DEBUG(
"[RenderRoomGraphics]",
"Palette lookup: byte=0x%02X → group_id=%d",
palette, palette_id);
390 if (palette_id < 0 || palette_id >= num_palettes) {
391 palette_id = palette_id % num_palettes;
394 auto bg1_palette = dungeon_pal_group[palette_id];
396 if (bg1_palette.size() > 0) {
398 bg1_bmp.SetPalette(bg1_palette);
399 bg2_bmp.SetPalette(bg1_palette);
409 if (bg1_bmp.texture()) {
411 LOG_DEBUG(
"[RenderRoomGraphics]",
"Queueing UPDATE for existing textures (deferred)");
418 LOG_DEBUG(
"[RenderRoomGraphics]",
"Queueing CREATE for new textures (deferred)");
432 LOG_DEBUG(
"[RenderRoomGraphics]",
"Texture commands queued for batch processing");
436 LOG_DEBUG(
"RenderRoomGraphics",
"LoadLayoutTilesToBuffer START for room %d",
room_id_);
439 LOG_DEBUG(
"RenderRoomGraphics",
"ROM not loaded, aborting");
444 LOG_DEBUG(
"RenderRoomGraphics",
"Layout has %zu objects", layout_objects.size());
445 if (layout_objects.empty()) {
449 int tiles_written_bg1 = 0;
450 int tiles_written_bg2 = 0;
451 int tiles_skipped = 0;
453 for (
const auto& layout_obj : layout_objects) {
454 uint8_t x = layout_obj.x();
455 uint8_t y = layout_obj.y();
457 auto tile_result = layout_obj.GetTile(0);
458 if (!tile_result.ok()) {
462 const auto* tile_info = tile_result.value();
465 if (layout_obj.GetLayerValue() == 1) {
474 LOG_DEBUG(
"RenderRoomGraphics",
"Layout tiles: BG1=%d BG2=%d skipped=%d", tiles_written_bg1, tiles_written_bg2, tiles_skipped);
478 LOG_DEBUG(
"[RenderObjectsToBackground]",
"Starting object rendering for room %d",
room_id_);
481 LOG_DEBUG(
"[RenderObjectsToBackground]",
"ROM not loaded, aborting");
489 bool bitmaps_exist = bg1_bmp.is_active() && bg1_bmp.width() > 0 && bg2_bmp.is_active() && bg2_bmp.width() > 0;
492 LOG_DEBUG(
"[RenderObjectsToBackground]",
"Room %d: Objects not dirty, skipping render",
room_id_);
497 auto& dungeon_pal_group =
rom()->mutable_palette_group()->dungeon_main;
498 int num_palettes = dungeon_pal_group.size();
503 auto dungeon_palette_ptr =
rom()->paletteset_ids[
palette][0];
504 auto palette_word =
rom()->ReadWord(0xDEC4B + dungeon_palette_ptr);
505 if (palette_word.ok()) {
506 palette_id = palette_word.value() / 180;
510 if (palette_id < 0 || palette_id >= num_palettes) {
514 auto room_palette = dungeon_pal_group[palette_id];
517 if (!palette_group_result.ok()) {
524 auto palette_group = palette_group_result.value();
534 LOG_DEBUG(
"[RenderObjectsToBackground]",
"ObjectDrawer failed: %s", std::string(status.message()).c_str());
538 LOG_DEBUG(
"[RenderObjectsToBackground]",
"Room %d: Objects rendered successfully",
room_id_);
550 auto gfx_buffer_data =
rom()->mutable_graphics_buffer();
551 if (!gfx_buffer_data || gfx_buffer_data->empty()) {
555 auto rom_data =
rom()->vector();
556 if (rom_data.empty()) {
561 if (animated_frame_ < 0 || animated_frame_ > 10) {
566 if (background_tileset_ < 0 || background_tileset_ > 255) {
570 int gfx_ptr =
SnesToPc(
rom()->version_constants().kGfxAnimatedPointer);
571 if (gfx_ptr < 0 || gfx_ptr >=
static_cast<int>(rom_data.size())) {
579 if (first_offset >= 0 && first_offset <
static_cast<int>(gfx_buffer_data->size())) {
580 uint8_t map_byte = (*gfx_buffer_data)[first_offset];
583 int gfx_offset = data + (7 * 2048);
584 if (gfx_offset >= 0 && gfx_offset <
static_cast<int>(
sizeof(
current_gfx16_))) {
591 int second_offset = data + (tileset_index * 2048) + (512 *
animated_frame_);
592 if (second_offset >= 0 && second_offset <
static_cast<int>(gfx_buffer_data->size())) {
593 uint8_t map_byte = (*gfx_buffer_data)[second_offset];
596 int gfx_offset = data + (7 * 2048) - 512;
597 if (gfx_offset >= 0 && gfx_offset <
static_cast<int>(
sizeof(
current_gfx16_))) {
608 auto rom_data =
rom()->vector();
614 object_pointer =
SnesToPc(object_pointer);
617 if (object_pointer < 0 || object_pointer >= (
int)
rom_->
size()) {
621 int room_address = object_pointer + (
room_id_ * 3);
624 if (room_address < 0 || room_address + 2 >= (
int)
rom_->
size()) {
629 (rom_data[room_address + 1] << 8) + rom_data[room_address];
634 if (objects_location < 0 || objects_location >= (
int)
rom_->
size()) {
639 if (objects_location + 1 < (
int)
rom_->
size()) {
642 floor2_graphics_ =
static_cast<uint8_t
>((rom_data[objects_location] >> 4) & 0x0F);
643 LOG_DEBUG(
"[LoadObjects]",
"Room %d: Set floor1_graphics_=%d, floor2_graphics_=%d",
647 layout =
static_cast<uint8_t
>((rom_data[objects_location + 1] >> 2) & 0x07);
657 auto rom_data =
rom()->vector();
660 int nbr_of_staircase = 0;
662 int pos = objects_location;
668 bool end_read =
false;
671 while (!end_read && pos < (
int)
rom_->
size()) {
678 b2 = rom_data[pos + 1];
680 if (b1 == 0xFF && b2 == 0xFF) {
690 if (b1 == 0xF0 && b2 == 0xFF) {
701 b3 = rom_data[pos + 2];
713 if (r.
id_ >= 0 && r.
id_ <= 0x3FF) {
733 std::vector<uint8_t> bytes;
736 std::vector<RoomObject> layer0_objects;
737 std::vector<RoomObject> layer1_objects;
738 std::vector<RoomObject> layer2_objects;
741 switch (obj.GetLayerValue()) {
742 case 0: layer0_objects.push_back(obj);
break;
743 case 1: layer1_objects.push_back(obj);
break;
744 case 2: layer2_objects.push_back(obj);
break;
749 for (
const auto& obj : layer0_objects) {
750 auto encoded = obj.EncodeObjectToBytes();
751 bytes.push_back(encoded.b1);
752 bytes.push_back(encoded.b2);
753 bytes.push_back(encoded.b3);
755 bytes.push_back(0xFF);
756 bytes.push_back(0xFF);
759 for (
const auto& obj : layer1_objects) {
760 auto encoded = obj.EncodeObjectToBytes();
761 bytes.push_back(encoded.b1);
762 bytes.push_back(encoded.b2);
763 bytes.push_back(encoded.b3);
765 bytes.push_back(0xFF);
766 bytes.push_back(0xFF);
769 for (
const auto& obj : layer2_objects) {
770 auto encoded = obj.EncodeObjectToBytes();
771 bytes.push_back(encoded.b1);
772 bytes.push_back(encoded.b2);
773 bytes.push_back(encoded.b3);
777 bytes.push_back(0xFF);
778 bytes.push_back(0xFF);
784 if (
rom_ ==
nullptr) {
785 return absl::InvalidArgumentError(
"ROM pointer is null");
788 auto rom_data =
rom()->vector();
794 object_pointer =
SnesToPc(object_pointer);
796 if (object_pointer < 0 || object_pointer >= (
int)
rom_->
size()) {
797 return absl::OutOfRangeError(
"Object pointer out of range");
800 int room_address = object_pointer + (
room_id_ * 3);
802 if (room_address < 0 || room_address + 2 >= (
int)
rom_->
size()) {
803 return absl::OutOfRangeError(
"Room address out of range");
807 (rom_data[room_address + 1] << 8) + rom_data[room_address];
811 if (objects_location < 0 || objects_location >= (
int)
rom_->
size()) {
812 return absl::OutOfRangeError(
"Objects location out of range");
816 int write_pos = objects_location + 2;
832 return absl::InvalidArgumentError(
"Invalid object parameters");
838 return absl::OkStatus();
843 return absl::OutOfRangeError(
"Object index out of range");
848 return absl::OkStatus();
853 return absl::OutOfRangeError(
"Object index out of range");
857 return absl::InvalidArgumentError(
"Invalid object parameters");
862 return absl::OkStatus();
868 if (obj.x() == x && obj.y() == y && obj.GetLayerValue() == layer) {
872 return absl::NotFoundError(
"No object found at position");
877 if (
object.x() < 0 ||
object.x() > 63)
return false;
878 if (
object.y() < 0 ||
object.y() > 63)
return false;
881 if (
object.GetLayerValue() < 0 ||
object.GetLayerValue() > 2)
return false;
884 if (
object.id_ < 0 || object.id_ > 0xFFF)
return false;
887 if (
object.id_ < 0x100 &&
object.size() > 15)
return false;
896 if (nbr_of_staircase < 4) {
920 }
else if (oid == 0xFB1) {
930 auto rom_data =
rom()->vector();
931 int sprite_pointer = (0x04 << 16) +
934 int sprite_address_snes =
935 (0x09 << 16) + (rom_data[sprite_pointer + (
room_id_ * 2) + 1] << 8) +
936 rom_data[sprite_pointer + (
room_id_ * 2)];
938 int sprite_address =
SnesToPc(sprite_address_snes);
939 if (rom_data[sprite_address] == 1) {
945 uint8_t b1 = rom_data[sprite_address];
946 uint8_t b2 = rom_data[sprite_address + 1];
947 uint8_t b3 = rom_data[sprite_address + 2];
953 sprites_.emplace_back(b3, (b2 & 0x1F), (b1 & 0x1F),
954 ((b2 & 0xE0) >> 5) + ((b1 & 0x60) >> 2),
961 if (spr.
id() == 0xE4 && spr.
x() == 0x00 && spr.
y() == 0x1E &&
967 if (spr.
id() == 0xE4 && spr.
x() == 0x00 && spr.
y() == 0x1D &&
979 auto rom_data =
rom()->vector();
986 for (
size_t i = 0; i < clength; i++) {
987 if ((((rom_data[cpos + (i * 3) + 1] << 8) + (rom_data[cpos + (i * 3)])) &
991 if ((((rom_data[cpos + (i * 3) + 1] << 8) + (rom_data[cpos + (i * 3)])) &
997 chest_data{rom_data[cpos + (i * 3) + 2], big});
1003 auto rom_data =
rom()->vector();
1012 LOG_DEBUG(
"Room",
"LoadDoors for room %d - doors are loaded via object stream",
room_id_);
1016 auto rom_data =
rom()->vector();
1022 LOG_DEBUG(
"Room",
"LoadTorches: room_id=%d, bytes_count=%d",
room_id_, bytes_count);
1025 for (
int i = 0; i < bytes_count; i += 2) {
1026 if (i + 1 >= bytes_count)
break;
1032 if (b1 == 0xFF && b2 == 0xFF) {
1037 uint16_t torch_room_id = (b2 << 8) | b1;
1041 while (i < bytes_count) {
1042 if (i + 1 >= bytes_count)
break;
1048 if (b1 == 0xFF && b2 == 0xFF) {
1053 int address = ((b2 & 0x1F) << 8 | b1) >> 1;
1054 uint8_t px = address % 64;
1055 uint8_t py = address >> 6;
1056 uint8_t layer = (b2 & 0x20) >> 5;
1057 bool lit = (b2 & 0x80) == 0x80;
1060 RoomObject torch_obj(0x150, px, py, 0, layer);
1067 LOG_DEBUG(
"Room",
"Loaded torch at (%d,%d) layer=%d lit=%d",
1068 px, py, layer, lit);
1076 while (i < bytes_count) {
1077 if (i + 1 >= bytes_count)
break;
1080 if (b1 == 0xFF && b2 == 0xFF) {
1090 auto rom_data =
rom()->vector();
1095 LOG_DEBUG(
"Room",
"LoadBlocks: room_id=%d, blocks_count=%d",
room_id_, blocks_count);
1098 std::vector<uint8_t> blocks_data(blocks_count);
1106 for (
int i = 0; i < 0x80 && i < blocks_count; i++) {
1107 blocks_data[i] = rom_data[pos1 + i];
1109 if (i + 0x80 < blocks_count) {
1110 blocks_data[i + 0x80] = rom_data[pos2 + i];
1112 if (i + 0x100 < blocks_count) {
1113 blocks_data[i + 0x100] = rom_data[pos3 + i];
1115 if (i + 0x180 < blocks_count) {
1116 blocks_data[i + 0x180] = rom_data[pos4 + i];
1121 for (
int i = 0; i < blocks_count; i += 4) {
1122 if (i + 3 >= blocks_count)
break;
1124 uint8_t b1 = blocks_data[i];
1125 uint8_t b2 = blocks_data[i + 1];
1126 uint8_t b3 = blocks_data[i + 2];
1127 uint8_t b4 = blocks_data[i + 3];
1130 uint16_t block_room_id = (b2 << 8) | b1;
1133 if (b3 == 0xFF && b4 == 0xFF) {
1138 int address = ((b4 & 0x1F) << 8 | b3) >> 1;
1139 uint8_t px = address % 64;
1140 uint8_t py = address >> 6;
1141 uint8_t layer = (b4 & 0x20) >> 5;
1144 RoomObject block_obj(0x0E00, px, py, 0, layer);
1150 LOG_DEBUG(
"Room",
"Loaded block at (%d,%d) layer=%d", px, py, layer);
1156 auto rom_data =
rom()->vector();
1159 int pit_entries = rom_data[
pit_count] / 2;
1165 int pit_data_addr =
SnesToPc(pit_ptr);
1167 LOG_DEBUG(
"Room",
"LoadPits: room_id=%d, pit_entries=%d, pit_ptr=0x%06X",
1176 LOG_DEBUG(
"Room",
"Pit destination - target=%d, target_layer=%d",
The Rom class is used to load, save, and modify Rom data.
absl::Status WriteVector(int addr, std::vector< uint8_t > data)
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
void DrawBackground(std::span< uint8_t > gfx16_data)
void SetTileAt(int x, int y, uint16_t value)
void DrawFloor(const std::vector< uint8_t > &rom_data, int tile_address, int tile_address_floor, uint8_t floor_graphics)
Draws dungeon objects to background buffers using game patterns.
absl::Status DrawObjectList(const std::vector< RoomObject > &objects, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group)
Draw all objects in a room.
const std::vector< RoomObject > & GetObjects() const
static RoomObject DecodeObjectFromBytes(uint8_t b1, uint8_t b2, uint8_t b3, uint8_t layer)
void set_options(ObjectOption options)
void SetBlockset(uint8_t blockset)
bool ValidateObject(const RoomObject &object) const
std::vector< RoomObject > tile_objects_
void SetSpriteset(uint8_t spriteset)
void SetTag2Direct(TagKey tag2)
absl::Status UpdateObject(size_t index, const RoomObject &object)
void SetStair4Target(uint8_t target)
void SetPitsTarget(uint8_t target)
void SetIsLight(bool is_light)
gfx::BackgroundBuffer bg2_buffer_
uint8_t cached_floor2_graphics_
std::vector< zelda3::Sprite > sprites_
void SetLoaded(bool loaded)
std::array< uint8_t, 0x4000 > current_gfx16_
void CopyRoomGraphicsToBuffer()
void LoadRoomGraphics(uint8_t entrance_blockset=0xFF)
void SetStaircaseRoom(int index, uint8_t room)
absl::Status RemoveObject(size_t index)
void SetStair1TargetLayer(uint8_t layer)
void SetLayer2Mode(uint8_t mode)
void LoadLayoutTilesToBuffer()
void SetTag2(TagKey tag2)
void ParseObjectsFromLocation(int objects_location)
uint8_t cached_floor1_graphics_
void SetTag1Direct(TagKey tag1)
void SetPaletteDirect(uint8_t palette)
void SetStair2Target(uint8_t target)
void SetCollision(CollisionKey collision)
gfx::BackgroundBuffer bg1_buffer_
absl::Status SaveObjects()
void SetStaircasePlane(int index, uint8_t plane)
void SetIsDark(bool is_dark)
void RenderRoomGraphics()
uint8_t staircase_rooms_[4]
std::vector< uint8_t > EncodeObjects() const
void SetEffect(EffectKey effect)
std::array< uint8_t, 16 > blocks_
void SetTag1(TagKey tag1)
void SetBackgroundTileset(uint8_t tileset)
void SetStair3TargetLayer(uint8_t layer)
void SetMessageIdDirect(uint16_t message_id)
void SetLayerMerging(LayerMergeType merging)
void SetPitsTargetLayer(uint8_t layer)
void SetSpriteTileset(uint8_t tileset)
void SetStair1Target(uint8_t target)
void SetBg2(background2 bg2)
void LoadAnimatedGraphics()
std::vector< chest_data > chests_in_room_
uint8_t cached_spriteset_
std::vector< staircase > z3_staircases_
void SetPalette(uint8_t palette)
void SetHolewarp(uint8_t holewarp)
uint8_t background_tileset_
void SetStair3Target(uint8_t target)
void HandleSpecialObjects(short oid, uint8_t posX, uint8_t posY, int &nbr_of_staircase)
void SetStair4TargetLayer(uint8_t layer)
absl::Status AddObject(const RoomObject &object)
absl::StatusOr< size_t > FindObjectAt(int x, int y, int layer) const
void SetStair2TargetLayer(uint8_t layer)
void RenderObjectsToBackground()
void SetLayer2Behavior(uint8_t behavior)
A class for managing sprites in the overworld and underworld.
auto set_key_drop(int key)
zelda3_bg2_effect
Background layer 2 effects.
#define LOG_DEBUG(category, format,...)
uint16_t TileInfoToWord(TileInfo tile_info)
absl::StatusOr< PaletteGroup > CreatePaletteGroupFromLargePalette(SnesPalette &palette, int num_colors)
Take a SNESPalette, divide it into palettes of 8 colors.
constexpr int kGfxBufferAnimatedFrameStride
constexpr int kGfxBufferAnimatedFrameOffset
constexpr int chests_length_pointer
constexpr int chests_data_pointer1
constexpr int blocks_pointer3
constexpr int blocks_pointer4
constexpr int kGfxBufferRoomOffset
constexpr int rooms_sprite_pointer
constexpr int kGfxBufferRoomSpriteOffset
RoomSize CalculateRoomSize(Rom *rom, int room_id)
constexpr int messages_id_dungeon
constexpr int blocks_length
constexpr int tile_address_floor
constexpr int blocks_pointer2
constexpr int kGfxBufferRoomSpriteStride
constexpr uint16_t stairsObjects[]
Room LoadRoomFromRom(Rom *rom, int room_id)
constexpr int blocks_pointer1
constexpr int torches_length_pointer
constexpr int kRoomHeaderPointer
constexpr int kRoomHeaderPointerBank
constexpr int kGfxBufferStride
constexpr int room_object_pointer
constexpr int kGfxBufferRoomSpriteLastLineOffset
constexpr int tile_address
constexpr int pit_pointer
constexpr int dungeon_spr_ptrs
constexpr int kGfxBufferOffset
Main namespace for the application.
uint32_t SnesToPc(uint32_t addr) noexcept
Legacy chest data structure.
Represents a group of palettes.
int64_t room_size_pointer
Yet Another Zelda3 Editor (YAZE) - Public C API.