Files
yaze/test/unit/zelda3/dungeon/dungeon_save_test.cc
2025-12-22 14:55:59 -05:00

164 lines
5.4 KiB
C++

#include <gtest/gtest.h>
#include <vector>
#include <memory>
#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<Rom>();
// Create a minimal ROM for testing (2MB)
std::vector<uint8_t> dummy_data(0x200000, 0);
rom_->LoadFromData(dummy_data);
SetupRoomObjectPointers();
SetupSpritePointers();
room_ = std::make_unique<Room>(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> rom_;
std::unique_ptr<Room> 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