#include #include #include #include "rom/rom.h" #include "zelda3/dungeon/room.h" #include "zelda3/dungeon/dungeon_rom_addresses.h" namespace yaze { namespace zelda3 { namespace test { class DungeonSaveTest : public ::testing::Test { protected: void SetUp() override { rom_ = std::make_unique(); // Create a minimal ROM for testing (2MB) std::vector dummy_data(0x200000, 0); rom_->LoadFromData(dummy_data); SetupRoomObjectPointers(); SetupSpritePointers(); room_ = std::make_unique(0, rom_.get()); } void SetupRoomObjectPointers() { // 1. Setup kRoomObjectPointer (0x874C) to point to our table at 0xF8000 // The code reads 3 bytes from kRoomObjectPointer // int object_pointer = (rom_data[room_object_pointer + 2] << 16) + ... // We want object_pointer to be 0xF8000 (PC address) // 0xF8000 is 1F:8000 in LoROM (Bank 1F) // So we write 00 80 1F at 0x874C int ptr_loc = kRoomObjectPointer; rom_->mutable_data()[ptr_loc] = 0x00; rom_->mutable_data()[ptr_loc + 1] = 0x80; rom_->mutable_data()[ptr_loc + 2] = 0x1F; // 2. Setup Room 0 pointer at 0xF8000 // We want Room 0 data to be at 0x100000 (Bank 20, PC 0x100000) // 0x100000 is 20:8000 in LoROM // Write 00 80 20 at 0xF8000 int table_loc = 0xF8000; rom_->mutable_data()[table_loc] = 0x00; rom_->mutable_data()[table_loc + 1] = 0x80; rom_->mutable_data()[table_loc + 2] = 0x20; // 3. Setup Room 1 pointer at 0xF8003 (for size calculation) // We want Room 0 to have 0x100 bytes of space // So Room 1 starts at 0x100100 (20:8100) // Write 00 81 20 at 0xF8003 rom_->mutable_data()[table_loc + 3] = 0x00; rom_->mutable_data()[table_loc + 4] = 0x81; rom_->mutable_data()[table_loc + 5] = 0x20; // 4. Setup Room 0 Object Data Header at 0x100000 // The code reads tile_address from room_address (which is 0x100000) // int tile_address = (rom_data[room_address + 2] << 16) + ... // We want tile_address to be 0x100005 (just after this pointer) // 0x100005 is 20:8005 int room_data_loc = 0x100000; rom_->mutable_data()[room_data_loc] = 0x05; rom_->mutable_data()[room_data_loc + 1] = 0x80; rom_->mutable_data()[room_data_loc + 2] = 0x20; // 5. Setup actual object data at 0x100005 // Header (2 bytes) + Objects // 0x100005: Floor/Layout info (2 bytes) rom_->mutable_data()[0x100005] = 0x00; rom_->mutable_data()[0x100006] = 0x00; // 0x100007: Start of objects // Empty object list: FF FF (Layer 1) FF FF (Layer 2) FF FF (Layer 3) FF FF (End) // Total 8 bytes. // Available space is 0x100 - 5 = 0xFB bytes (approx) // Actually CalculateRoomSize uses the Room Pointers (0xF8000). // Room 0 Size = 0x100100 - 0x100000 = 0x100 (256 bytes). // Used by header/pointers: 5 bytes? No, CalculateRoomSize returns raw size between room starts. // So available is 256 bytes. // SaveObjects subtracts 2 for header. So 254 bytes for objects. } void SetupSpritePointers() { // 1. Setup kRoomsSpritePointer (0x4C298) // Points to table in Bank 09. Let's put table at 0x48000 (09:8000) int ptr_loc = kRoomsSpritePointer; rom_->mutable_data()[ptr_loc] = 0x00; rom_->mutable_data()[ptr_loc + 1] = 0x80; // Bank is hardcoded to 0x09 in code, so we only write low 2 bytes. // 2. Setup Sprite Pointer Table at 0x48000 (09:8000) // Room 0 pointer -> sprite list at 0x49000 (09:9000) // Write 00 90 at 0x48000 int table_loc = 0x48000; rom_->mutable_data()[table_loc] = 0x00; rom_->mutable_data()[table_loc + 1] = 0x90; // Room 1 pointer at 0x48002 (for size calculation) // Let's give 0x50 bytes for sprites. // Next room at 0x49050 (09:9050) // Write 50 90 at 0x48002 rom_->mutable_data()[table_loc + 2] = 0x50; rom_->mutable_data()[table_loc + 3] = 0x90; // 3. Setup Sprite Data at 0x49000 // Sortsprite byte (0 or 1) rom_->mutable_data()[0x49000] = 0x00; // End of sprites (0xFF) rom_->mutable_data()[0x49001] = 0xFF; } std::unique_ptr rom_; std::unique_ptr room_; }; TEST_F(DungeonSaveTest, SaveObjects_FitsInSpace) { // Add a few objects RoomObject obj1(0x10, 10, 10, 0, 0); room_->AddObject(obj1); auto status = room_->SaveObjects(); EXPECT_TRUE(status.ok()) << status.message(); } TEST_F(DungeonSaveTest, SaveObjects_TooLarge) { // Add MANY objects to exceed 256 bytes // Each object encodes to 3 bytes. // We need > 85 objects. for (int i = 0; i < 100; ++i) { RoomObject obj(0x10, 10, 10, 0, 0); room_->AddObject(obj); } auto status = room_->SaveObjects(); EXPECT_FALSE(status.ok()); EXPECT_EQ(status.code(), absl::StatusCode::kOutOfRange); } TEST_F(DungeonSaveTest, SaveSprites_FitsInSpace) { // Add a sprite zelda3::Sprite spr(0x10, 10, 10, 0, 0); room_->GetSprites().push_back(spr); auto status = room_->SaveSprites(); EXPECT_TRUE(status.ok()) << status.message(); } TEST_F(DungeonSaveTest, SaveSprites_TooLarge) { // Add MANY sprites to exceed 0x50 (80) bytes // Each sprite is 3 bytes. // We need > 26 sprites. for (int i = 0; i < 30; ++i) { zelda3::Sprite spr(0x10, 10, 10, 0, 0); room_->GetSprites().push_back(spr); } auto status = room_->SaveSprites(); EXPECT_FALSE(status.ok()); EXPECT_EQ(status.code(), absl::StatusCode::kOutOfRange); } } // namespace test } // namespace zelda3 } // namespace yaze