From 299770922c4c10e83441145efbfea573e069cba2 Mon Sep 17 00:00:00 2001 From: scawful Date: Mon, 13 Nov 2023 14:51:01 -0500 Subject: [PATCH] Add Debugger interface, RoomObject class - Log instructions to debugger using experiment flag - Use BitmapManager for more functionality - Draw framebuffer and integrated debugger --- src/app/core/common.h | 3 +- src/app/editor/dungeon_editor.cc | 13 ++- src/app/editor/dungeon_editor.h | 2 + src/app/editor/master_editor.cc | 7 +- src/app/editor/overworld_editor.cc | 15 +-- src/app/editor/overworld_editor.h | 3 + src/app/emu/cpu.cc | 64 +++++++++--- src/app/emu/cpu.h | 25 ++++- src/app/emu/emulator.cc | 138 +++++++++++++++++++++++-- src/app/emu/emulator.h | 11 ++ src/app/emu/memory/mock_memory.h | 149 +++++++++++++++++++++++++++ src/app/emu/snes.h | 3 + src/app/emu/video/ppu.cc | 13 ++- src/app/emu/video/ppu.h | 8 +- src/app/gfx/bitmap.cc | 8 +- src/app/gfx/bitmap.h | 30 ++++-- src/app/gui/input.cc | 117 +++++++++++++++++++-- src/app/gui/input.h | 6 +- src/app/rom.cc | 27 +++-- src/app/rom.h | 2 +- src/app/zelda3/dungeon/room.cc | 62 +++++++++++ src/app/zelda3/dungeon/room.h | 39 ++++--- src/app/zelda3/dungeon/room_object.h | 61 ++++++++++- src/app/zelda3/overworld.cc | 1 + test/CMakeLists.txt | 1 + test/emu/cpu_test.cc | 142 +------------------------ test/room_object_test.cc | 24 +++++ 27 files changed, 740 insertions(+), 234 deletions(-) create mode 100644 src/app/emu/memory/mock_memory.h create mode 100644 test/room_object_test.cc diff --git a/src/app/core/common.h b/src/app/core/common.h index 4a86cf13..9c7204e7 100644 --- a/src/app/core/common.h +++ b/src/app/core/common.h @@ -13,7 +13,8 @@ class ExperimentFlags { public: struct Flags { bool kDrawOverworldSprites = false; - bool kUseBitmapManager = false; + bool kUseBitmapManager = true; + bool kLogInstructions = true; }; ExperimentFlags() = default; diff --git a/src/app/editor/dungeon_editor.cc b/src/app/editor/dungeon_editor.cc index df57924c..3e08c3a1 100644 --- a/src/app/editor/dungeon_editor.cc +++ b/src/app/editor/dungeon_editor.cc @@ -19,7 +19,7 @@ void DungeonEditor::Update() { for (int i = 0; i < 0x100; i++) { rooms_.emplace_back(zelda3::dungeon::Room(i)); rooms_[i].LoadHeader(); - rooms_[i].LoadRoomGraphics(rooms_[i].blockset); + // rooms_[i].LoadRoomGraphics(rooms_[i].blockset); } is_loaded_ = true; } @@ -38,7 +38,7 @@ void DungeonEditor::Update() { ImGui::TableNextRow(); ImGui::TableNextColumn(); if (rom()->isLoaded()) { - if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)9); + if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)9); ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true, ImGuiWindowFlags_AlwaysVerticalScrollbar)) { int i = 0; @@ -56,6 +56,9 @@ void DungeonEditor::Update() { ImGui::TableNextColumn(); DrawDungeonTabView(); ImGui::TableNextColumn(); + if (ImGui::Button("dungeon object renderer")) { + object_renderer_.RenderObjectsAsBitmaps(); + } DrawTileSelector(); ImGui::EndTable(); } @@ -150,13 +153,13 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) { canvas_.DrawBackground(); canvas_.DrawContextMenu(); - canvas_.DrawBitmap(rooms_[room_id].current_graphics_, 2, is_loaded_); canvas_.DrawGrid(); canvas_.DrawOverlay(); } void DungeonEditor::DrawToolset() { - if (ImGui::BeginTable("DWToolset", 9, toolset_table_flags_, ImVec2(0, 0))) { + if (ImGui::BeginTable("DWToolset", 9, ImGuiTableFlags_SizingFixedFit, + ImVec2(0, 0))) { ImGui::TableSetupColumn("#undoTool"); ImGui::TableSetupColumn("#redoTool"); ImGui::TableSetupColumn("#history"); @@ -209,7 +212,7 @@ void DungeonEditor::DrawRoomGraphics() { void DungeonEditor::DrawTileSelector() { if (ImGui::BeginTabBar("##TabBar", ImGuiTabBarFlags_FittingPolicyScroll)) { if (ImGui::BeginTabItem("Room Graphics")) { - if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)3); + if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)3); ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true, ImGuiWindowFlags_AlwaysVerticalScrollbar)) { DrawRoomGraphics(); diff --git a/src/app/editor/dungeon_editor.h b/src/app/editor/dungeon_editor.h index 66baba58..75c8a624 100644 --- a/src/app/editor/dungeon_editor.h +++ b/src/app/editor/dungeon_editor.h @@ -8,6 +8,7 @@ #include "app/gui/icons.h" #include "app/rom.h" #include "zelda3/dungeon/room.h" +#include "zelda3/dungeon/room_object.h" namespace yaze { namespace app { @@ -32,6 +33,7 @@ class DungeonEditor : public SharedROM { ImVector active_rooms_; std::vector rooms_; + zelda3::dungeon::DungeonObjectRenderer object_renderer_; gui::Canvas canvas_; gui::Canvas room_gfx_canvas_; diff --git a/src/app/editor/master_editor.cc b/src/app/editor/master_editor.cc index 81a4c7d2..7ab686cb 100644 --- a/src/app/editor/master_editor.cc +++ b/src/app/editor/master_editor.cc @@ -187,10 +187,6 @@ void MasterEditor::DrawFileMenu() { static bool save_as_menu = false; if (ImGui::BeginMenu("File")) { - if (ImGui::MenuItem("New", "Ctrl+N")) { - // TODO: Implement new ROM creation. - } - if (ImGui::MenuItem("Open", "Ctrl+O")) { ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Open ROM", ".sfc,.smc", "."); @@ -209,10 +205,13 @@ void MasterEditor::DrawFileMenu() { if (ImGui::BeginMenu("Options")) { ImGui::MenuItem("Backup ROM", "", &backup_rom_); ImGui::Separator(); + ImGui::Text("Experiment Flags"); ImGui::Checkbox("Enable Overworld Sprites", &mutable_flags()->kDrawOverworldSprites); ImGui::Checkbox("Use Bitmap Manager", &mutable_flags()->kUseBitmapManager); + ImGui::Checkbox("Log Instructions to Debugger", + &mutable_flags()->kLogInstructions); ImGui::EndMenu(); } diff --git a/src/app/editor/overworld_editor.cc b/src/app/editor/overworld_editor.cc index 1db8c814..619d8260 100644 --- a/src/app/editor/overworld_editor.cc +++ b/src/app/editor/overworld_editor.cc @@ -32,7 +32,8 @@ absl::Status OverworldEditor::Update() { // Initialize overworld graphics, maps, and palettes if (rom()->isLoaded() && !all_gfx_loaded_) { RETURN_IF_ERROR(LoadGraphics()) - tile16_editor_.InitBlockset(tile16_blockset_bmp_); + tile16_editor_.InitBlockset(tile16_blockset_bmp_, tile16_individual_, + tile8_individual_); gfx_group_editor_.InitBlockset(tile16_blockset_bmp_); all_gfx_loaded_ = true; } @@ -349,10 +350,10 @@ void OverworldEditor::RenderUpdatedMapBitmap(const ImVec2 &click_position, void OverworldEditor::QueueROMChanges(int index, ushort new_tile16) { // Store the changes made by the user to the ROM (or project file) rom()->QueueChanges([&]() { - overworld_.SaveOverworldMaps(); + PRINT_IF_ERROR(overworld_.SaveOverworldMaps()); if (!overworld_.CreateTile32Tilemap()) { // overworld_.SaveMap16Tiles(); - overworld_.SaveMap32Tiles(); + PRINT_IF_ERROR(overworld_.SaveMap32Tiles()); } else { std::cout << "Failed to create tile32 tilemap" << std::endl; } @@ -410,14 +411,16 @@ void OverworldEditor::DrawTile8Selector() { ImVec2(0x100 + 1, kNumSheetsToLoad * 0x40 + 1)); graphics_bin_canvas_.DrawContextMenu(); if (all_gfx_loaded_) { - for (const auto &[key, value] : graphics_bin_) { + // for (const auto &[key, value] : graphics_bin_) { + for (auto &[key, value] : rom()->BitmapManager()) { int offset = 0x40 * (key + 1); int top_left_y = graphics_bin_canvas_.GetZeroPoint().y + 2; if (key >= 1) { top_left_y = graphics_bin_canvas_.GetZeroPoint().y + 0x40 * key; } + auto texture = value.get()->texture(); graphics_bin_canvas_.GetDrawList()->AddImage( - (void *)value.texture(), + (void *)texture, ImVec2(graphics_bin_canvas_.GetZeroPoint().x + 2, top_left_y), ImVec2(graphics_bin_canvas_.GetZeroPoint().x + 0x100, graphics_bin_canvas_.GetZeroPoint().y + offset)); @@ -521,7 +524,7 @@ absl::Status OverworldEditor::LoadGraphics() { } if (flags()->kDrawOverworldSprites) { - LoadSpriteGraphics(); + RETURN_IF_ERROR(LoadSpriteGraphics()); } return absl::OkStatus(); diff --git a/src/app/editor/overworld_editor.h b/src/app/editor/overworld_editor.h index e8968d05..585a9554 100644 --- a/src/app/editor/overworld_editor.h +++ b/src/app/editor/overworld_editor.h @@ -158,6 +158,9 @@ class OverworldEditor : public Editor, std::vector tile16_individual_data_; std::vector tile16_individual_; + std::vector tile8_individual_data_; + std::vector tile8_individual_; + Tile16Editor tile16_editor_; GfxGroupEditor gfx_group_editor_; PaletteEditor palette_editor_; diff --git a/src/app/emu/cpu.cc b/src/app/emu/cpu.cc index 2a2e5a9a..963c9a45 100644 --- a/src/app/emu/cpu.cc +++ b/src/app/emu/cpu.cc @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace yaze { @@ -1092,26 +1093,57 @@ void CPU::ExecuteInstruction(uint8_t opcode) { break; } - // Log the address and opcode. - std::cout << "$" << std::uppercase << std::setw(2) << std::setfill('0') - << static_cast(DB) << ":" << std::hex << PC << ": 0x" - << std::hex << static_cast(opcode) << " " - << opcode_to_mnemonic.at(opcode) << " "; + if (flags()->kLogInstructions) { + std::ostringstream oss; + oss << "$" << std::uppercase << std::setw(2) << std::setfill('0') + << static_cast(DB) << ":" << std::hex << PC << ": 0x" << std::hex + << static_cast(opcode) << " " << opcode_to_mnemonic.at(opcode) + << " "; - // Log the operand. - if (operand) { - if (immediate) { - std::cout << "#"; - } - std::cout << "$"; - if (accumulator_mode) { - std::cout << std::hex << std::setw(2) << std::setfill('0') << operand; - } else { - std::cout << std::hex << std::setw(4) << std::setfill('0') + // Log the operand. + std::string ops; + if (operand) { + if (immediate) { + ops += "#"; + } + std::ostringstream oss_ops; + oss_ops << "$"; + if (accumulator_mode) { + oss_ops << std::hex << std::setw(2) << std::setfill('0') << static_cast(operand); + } else { + oss_ops << std::hex << std::setw(4) << std::setfill('0') + << static_cast(operand); + } + ops = oss_ops.str(); } + + oss << ops << std::endl; + + InstructionEntry entry(PC, opcode, ops, oss.str()); + instruction_log_.push_back(entry); + } else { + // Log the address and opcode. + std::cout << "$" << std::uppercase << std::setw(2) << std::setfill('0') + << static_cast(DB) << ":" << std::hex << PC << ": 0x" + << std::hex << static_cast(opcode) << " " + << opcode_to_mnemonic.at(opcode) << " "; + + // Log the operand. + if (operand) { + if (immediate) { + std::cout << "#"; + } + std::cout << "$"; + if (accumulator_mode) { + std::cout << std::hex << std::setw(2) << std::setfill('0') << operand; + } else { + std::cout << std::hex << std::setw(4) << std::setfill('0') + << static_cast(operand); + } + } + std::cout << std::endl; } - std::cout << std::endl; } // Interrupt Vectors diff --git a/src/app/emu/cpu.h b/src/app/emu/cpu.h index e9f10528..5783e428 100644 --- a/src/app/emu/cpu.h +++ b/src/app/emu/cpu.h @@ -6,6 +6,7 @@ #include #include +#include "app/core/common.h" #include "app/emu/clock.h" #include "app/emu/debug/log.h" #include "app/emu/memory/memory.h" @@ -14,6 +15,25 @@ namespace yaze { namespace app { namespace emu { +class InstructionEntry { + public: + // Constructor + InstructionEntry(uint32_t addr, uint8_t op, const std::string& ops, + const std::string& instr) + : address(addr), opcode(op), operands(ops), instruction(instr) {} + + // Getters for the class members + uint32_t GetAddress() const { return address; } + uint8_t GetOpcode() const { return opcode; } + const std::string& GetOperands() const { return operands; } + const std::string& GetInstruction() const { return instruction; } + + uint32_t address; // Memory address of the instruction + uint8_t opcode; // Opcode of the instruction + std::string operands; // Operand(s) of the instruction, if any + std::string instruction; // Human-readable instruction text +}; + const std::unordered_map opcode_to_mnemonic = { {0x00, "BRK"}, {0x01, "ORA"}, {0x02, "COP"}, {0x03, "ORA"}, {0x04, "TSB"}, {0x05, "ORA"}, {0x06, "ASL"}, {0x07, "ORA"}, {0x08, "PHP"}, {0x09, "ORA"}, @@ -72,19 +92,20 @@ const std::unordered_map opcode_to_mnemonic = { const int kCpuClockSpeed = 21477272; // 21.477272 MHz -class CPU : public Memory, public Loggable { +class CPU : public Memory, public Loggable, public core::ExperimentFlags { public: explicit CPU(Memory& mem, Clock& vclock) : memory(mem), clock(vclock) {} void Init() { clock.SetFrequency(kCpuClockSpeed); - memory.ClearMemory(); } void Update(); void ExecuteInstruction(uint8_t opcode); void HandleInterrupts(); + std::vector instruction_log_; + // ========================================================================== // Addressing Modes diff --git a/src/app/emu/emulator.cc b/src/app/emu/emulator.cc index 7bc3adb9..9ea736a3 100644 --- a/src/app/emu/emulator.cc +++ b/src/app/emu/emulator.cc @@ -13,7 +13,7 @@ namespace emu { void Emulator::Run() { // Initialize the emulator if a ROM is loaded - if (!snes_.running()) { + if (!snes_.running() && loading_) { if (rom()->isLoaded()) { snes_.Init(*rom()); running_ = true; @@ -23,13 +23,17 @@ void Emulator::Run() { // Render the emulator output RenderNavBar(); - RenderEmulator(); - if (running_) { // Handle user input events HandleEvents(); // Update the emulator state UpdateEmulator(); + + RenderEmulator(); + } + + if (debugger_) { + RenderDebugger(); } } @@ -38,14 +42,32 @@ void Emulator::RenderEmulator() { // You can use the ImGui::Image function to display the emulator output as a // texture // ... + ImVec2 size = ImVec2(320, 240); + if (snes_.running()) { + ImGui::Image((void*)snes_.Ppu().GetScreen()->texture(), size, ImVec2(0, 0), + ImVec2(1, 1)); + ImGui::Separator(); + } else { + ImGui::Dummy(size); + ImGui::Separator(); + ImGui::Text("Emulator output not available."); + } } void Emulator::RenderNavBar() { MENU_BAR() if (ImGui::BeginMenu("Game")) { - MENU_ITEM("Power Off") {} - MENU_ITEM("Pause") {} + MENU_ITEM("Load ROM") { loading_ = true; } + MENU_ITEM("Power Off") { + running_ = false; + loading_ = false; + debugger_ = false; + } + MENU_ITEM("Pause") { + running_ = false; + debugger_ = false; + } MENU_ITEM("Reset") {} MENU_ITEM("Save State") {} @@ -56,7 +78,15 @@ void Emulator::RenderNavBar() { if (ImGui::BeginMenu("Debug")) { ImGui::MenuItem("PPU Register Viewer", nullptr, &show_ppu_reg_viewer_); - MENU_ITEM("Debugger") {} + MENU_ITEM("Debugger") { debugger_ = !debugger_; } + if (ImGui::MenuItem("Integrated Debugger", nullptr, + &integrated_debugger_mode_)) { + separate_debugger_mode_ = !integrated_debugger_mode_; + } + if (ImGui::MenuItem("Separate Debugger Windows", nullptr, + &separate_debugger_mode_)) { + integrated_debugger_mode_ = !separate_debugger_mode_; + } MENU_ITEM("Memory Viewer") {} MENU_ITEM("Tile Viewer") {} ImGui::EndMenu(); @@ -82,6 +112,102 @@ void Emulator::UpdateEmulator() { snes_.Run(); } +void Emulator::RenderDebugger() { + // Define a lambda with the actual debugger + auto debugger = [&]() { + if (ImGui::BeginTable( + "DebugTable", 2, + ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) { + ImGui::TableNextColumn(); + RenderCpuState(snes_.Cpu()); + + ImGui::TableNextColumn(); + RenderCPUInstructionLog(snes_.Cpu().instruction_log_); + + ImGui::EndTable(); + } + }; + + if (integrated_debugger_mode_) { + debugger(); + } else if (separate_debugger_mode_) { + ImGui::Begin("Debugger"); + debugger(); + ImGui::End(); + } +} + +void Emulator::RenderCpuState(CPU& cpu) { + ImGui::Columns(2, "RegistersColumns"); + ImGui::Separator(); + ImGui::Text("A: 0x%04X", cpu.A); + ImGui::NextColumn(); + ImGui::Text("D: 0x%04X", cpu.D); + ImGui::NextColumn(); + ImGui::Text("X: 0x%04X", cpu.X); + ImGui::NextColumn(); + ImGui::Text("DB: 0x%02X", cpu.DB); + ImGui::NextColumn(); + ImGui::Text("Y: 0x%04X", cpu.Y); + ImGui::NextColumn(); + ImGui::Text("PB: 0x%02X", cpu.PB); + ImGui::NextColumn(); + ImGui::Text("PC: 0x%04X", cpu.PC); + ImGui::NextColumn(); + ImGui::Text("E: %d", cpu.E); + ImGui::NextColumn(); + ImGui::Columns(1); + ImGui::Separator(); + // Call Stack + if (ImGui::CollapsingHeader("Call Stack")) { + // For each return address in the call stack: + ImGui::Text("Return Address: 0x%08X", 0xFFFFFF); // Placeholder + } +} + +void Emulator::RenderMemoryViewer() { + // Render memory viewer +} + +namespace { +bool ShouldDisplay(const InstructionEntry& entry, const char* filter, + bool showAll) { + // Implement logic to determine if the entry should be displayed based on the + // filter and showAll flag + return true; +} +} // namespace + +void Emulator::RenderCPUInstructionLog( + const std::vector& instructionLog) { + if (ImGui::CollapsingHeader("CPU Instruction Log")) { + // Filtering options + static char filterBuf[256]; + ImGui::InputText("Filter", filterBuf, IM_ARRAYSIZE(filterBuf)); + ImGui::SameLine(); + if (ImGui::Button("Clear")) { /* Clear filter logic */ + } + + // Toggle for showing all opcodes + static bool showAllOpcodes = false; + ImGui::Checkbox("Show All Opcodes", &showAllOpcodes); + + // Instruction list + ImGui::BeginChild("InstructionList"); + for (const auto& entry : instructionLog) { + if (ShouldDisplay(entry, filterBuf, showAllOpcodes)) { + if (ImGui::Selectable(absl::StrFormat("%04X: %02X %s %s", entry.address, + entry.opcode, entry.operands, + entry.instruction) + .c_str())) { + // Logic to handle click (e.g., jump to address, set breakpoint) + } + } + } + ImGui::EndChild(); + } +} + } // namespace emu } // namespace app } // namespace yaze diff --git a/src/app/emu/emulator.h b/src/app/emu/emulator.h index 05b2dc87..a6c73c52 100644 --- a/src/app/emu/emulator.h +++ b/src/app/emu/emulator.h @@ -29,11 +29,22 @@ class Emulator : public SharedROM { // Updates the emulator state (CPU, PPU, APU, etc.) void UpdateEmulator(); + void RenderDebugger(); + void RenderCpuState(CPU& cpu); + void RenderMemoryViewer(); + + void RenderCPUInstructionLog( + const std::vector& instructionLog); + // Member variables to store internal state and resources SNES snes_; bool running_ = false; + bool debugger_ = false; + bool loading_ = false; bool show_ppu_reg_viewer_ = false; + bool integrated_debugger_mode_ = true; + bool separate_debugger_mode_ = false; }; } // namespace emu diff --git a/src/app/emu/memory/mock_memory.h b/src/app/emu/memory/mock_memory.h new file mode 100644 index 00000000..21b72283 --- /dev/null +++ b/src/app/emu/memory/mock_memory.h @@ -0,0 +1,149 @@ +#ifndef YAZE_TEST_MOCK_MOCK_MEMORY_H +#define YAZE_TEST_MOCK_MOCK_MEMORY_H + +#include +#include + +#include "app/emu/clock.h" +#include "app/emu/cpu.h" +#include "app/emu/memory/memory.h" + +using yaze::app::emu::Clock; +using yaze::app::emu::CPU; +using yaze::app::emu::Memory; + +class MockClock : public Clock { + public: + MOCK_METHOD(void, UpdateClock, (double delta), (override)); + MOCK_METHOD(unsigned long long, GetCycleCount, (), (const, override)); + MOCK_METHOD(void, ResetAccumulatedTime, (), (override)); + MOCK_METHOD(void, SetFrequency, (float new_frequency), (override)); + MOCK_METHOD(float, GetFrequency, (), (const, override)); +}; + +class MockMemory : public Memory { + public: + MOCK_CONST_METHOD1(ReadByte, uint8_t(uint16_t address)); + MOCK_CONST_METHOD1(ReadWord, uint16_t(uint16_t address)); + MOCK_CONST_METHOD1(ReadWordLong, uint32_t(uint16_t address)); + MOCK_METHOD(std::vector, ReadByteVector, + (uint16_t address, uint16_t length), (const, override)); + + MOCK_METHOD2(WriteByte, void(uint32_t address, uint8_t value)); + MOCK_METHOD2(WriteWord, void(uint32_t address, uint16_t value)); + + MOCK_METHOD1(PushByte, void(uint8_t value)); + MOCK_METHOD0(PopByte, uint8_t()); + MOCK_METHOD1(PushWord, void(uint16_t value)); + MOCK_METHOD0(PopWord, uint16_t()); + MOCK_METHOD1(PushLong, void(uint32_t value)); + MOCK_METHOD0(PopLong, uint32_t()); + + MOCK_CONST_METHOD0(SP, int16_t()); + MOCK_METHOD1(SetSP, void(int16_t value)); + + MOCK_METHOD1(SetMemory, void(const std::vector& data)); + MOCK_METHOD1(LoadData, void(const std::vector& data)); + + MOCK_METHOD0(ClearMemory, void()); + + MOCK_CONST_METHOD1(at, uint8_t(int i)); + uint8_t operator[](int i) const override { return at(i); } + + void SetMemoryContents(const std::vector& data) { + memory_.resize(64000); + std::copy(data.begin(), data.end(), memory_.begin()); + } + + void SetMemoryContents(const std::vector& data) { + memory_.resize(64000); + int i = 0; + for (const auto& each : data) { + memory_[i] = each & 0xFF; + memory_[i + 1] = (each >> 8) & 0xFF; + i += 2; + } + } + + void InsertMemory(const uint64_t address, const std::vector& data) { + int i = 0; + for (const auto& each : data) { + memory_[address + i] = each; + i++; + } + } + + void Init() { + ON_CALL(*this, ReadByte(::testing::_)) + .WillByDefault( + [this](uint16_t address) { return memory_.at(address); }); + ON_CALL(*this, ReadWord(::testing::_)) + .WillByDefault([this](uint16_t address) { + return static_cast(memory_.at(address)) | + (static_cast(memory_.at(address + 1)) << 8); + }); + ON_CALL(*this, ReadWordLong(::testing::_)) + .WillByDefault([this](uint16_t address) { + return static_cast(memory_.at(address)) | + (static_cast(memory_.at(address + 1)) << 8) | + (static_cast(memory_.at(address + 2)) << 16); + }); + ON_CALL(*this, ReadByteVector(::testing::_, ::testing::_)) + .WillByDefault([this](uint16_t address, uint16_t length) { + std::vector data; + for (int i = 0; i < length; i++) { + data.push_back(memory_.at(address + i)); + } + return data; + }); + ON_CALL(*this, WriteByte(::testing::_, ::testing::_)) + .WillByDefault([this](uint32_t address, uint8_t value) { + memory_[address] = value; + }); + ON_CALL(*this, WriteWord(::testing::_, ::testing::_)) + .WillByDefault([this](uint32_t address, uint16_t value) { + memory_[address] = value & 0xFF; + memory_[address + 1] = (value >> 8) & 0xFF; + }); + ON_CALL(*this, PushByte(::testing::_)).WillByDefault([this](uint8_t value) { + memory_.at(SP_) = value; + }); + ON_CALL(*this, PopByte()).WillByDefault([this]() { + uint8_t value = memory_.at(SP_); + this->SetSP(SP_ + 1); + return value; + }); + ON_CALL(*this, PushWord(::testing::_)) + .WillByDefault([this](uint16_t value) { + memory_.at(SP_) = value & 0xFF; + memory_.at(SP_ + 1) = (value >> 8) & 0xFF; + }); + ON_CALL(*this, PopWord()).WillByDefault([this]() { + uint16_t value = static_cast(memory_.at(SP_)) | + (static_cast(memory_.at(SP_ + 1)) << 8); + this->SetSP(SP_ + 2); + return value; + }); + ON_CALL(*this, PushLong(::testing::_)) + .WillByDefault([this](uint32_t value) { + memory_.at(SP_) = value & 0xFF; + memory_.at(SP_ + 1) = (value >> 8) & 0xFF; + memory_.at(SP_ + 2) = (value >> 16) & 0xFF; + }); + ON_CALL(*this, PopLong()).WillByDefault([this]() { + uint32_t value = static_cast(memory_.at(SP_)) | + (static_cast(memory_.at(SP_ + 1)) << 8) | + (static_cast(memory_.at(SP_ + 2)) << 16); + this->SetSP(SP_ + 3); + return value; + }); + ON_CALL(*this, ClearMemory()).WillByDefault([this]() { + memory_.resize(64000, 0x00); + }); + } + + std::vector memory_; + uint16_t SP_ = 0x01FF; +}; + +#endif // YAZE_TEST_MOCK_MOCK_MEMORY_H \ No newline at end of file diff --git a/src/app/emu/snes.h b/src/app/emu/snes.h index 7978d2fc..7fce746f 100644 --- a/src/app/emu/snes.h +++ b/src/app/emu/snes.h @@ -63,6 +63,9 @@ class SNES : public DMA { bool running() const { return running_; } + auto Cpu() -> CPU& { return cpu; } + auto Ppu() -> PPU& { return ppu; } + private: void WriteToRegister(uint16_t address, uint8_t value) { memory_.WriteByte(address, value); diff --git a/src/app/emu/video/ppu.cc b/src/app/emu/video/ppu.cc index a1dd5f44..a3e1bea9 100644 --- a/src/app/emu/video/ppu.cc +++ b/src/app/emu/video/ppu.cc @@ -46,7 +46,11 @@ void PPU::UpdateInternalState(int cycles) { void PPU::RenderScanline() { for (int y = 0; y < 240; ++y) { for (int x = 0; x < 256; ++x) { - frame_buffer_[y * 256 + x] = (x + y) % 256; // Simple gradient pattern + // Calculate the color index based on the x and y coordinates + uint8_t color_index = (x + y) % 8; + + // Set the pixel in the frame buffer to the calculated color index + frame_buffer_[y * 256 + x] = color_index; } } @@ -267,7 +271,12 @@ void PPU::ApplyEffects() {} void PPU::ComposeLayers() {} -void PPU::DisplayFrameBuffer() {} +void PPU::DisplayFrameBuffer() { + if (!screen_->IsActive()) { + screen_->Create(256, 240, 24, frame_buffer_); + rom()->RenderBitmap(screen_.get()); + } +} } // namespace emu } // namespace app diff --git a/src/app/emu/video/ppu.h b/src/app/emu/video/ppu.h index 6f031505..3bc5b49d 100644 --- a/src/app/emu/video/ppu.h +++ b/src/app/emu/video/ppu.h @@ -8,6 +8,7 @@ #include "app/emu/clock.h" #include "app/emu/memory/memory.h" #include "app/emu/video/ppu_registers.h" +#include "app/rom.h" namespace yaze { namespace app { @@ -234,7 +235,7 @@ struct BackgroundLayer { const int kPpuClockSpeed = 5369318; // 5.369318 MHz -class PPU : public Observer { +class PPU : public Observer, public SharedROM { public: // Initializes the PPU with the necessary resources and dependencies PPU(Memory& memory, Clock& clock) : memory_(memory), clock_(clock) {} @@ -243,6 +244,8 @@ class PPU : public Observer { void Init() { clock_.SetFrequency(kPpuClockSpeed); frame_buffer_.resize(256 * 240, 0); + screen_ = std::make_shared(256, 240, 8, 0x100); + screen_->SetActive(false); } // Resets the PPU to its initial state @@ -261,6 +264,8 @@ class PPU : public Observer { // Returns the pixel data for the current frame const std::vector& GetFrameBuffer() const { return frame_buffer_; } + auto GetScreen() const { return screen_; } + private: // Updates internal state based on PPU register settings void UpdateModeSettings(); @@ -300,6 +305,7 @@ class PPU : public Observer { std::vector sprites_; std::vector tile_data_; std::vector frame_buffer_; + std::shared_ptr screen_; uint16_t oam_address_; uint16_t tile_data_size_; diff --git a/src/app/gfx/bitmap.cc b/src/app/gfx/bitmap.cc index e1469f00..9536dd8e 100644 --- a/src/app/gfx/bitmap.cc +++ b/src/app/gfx/bitmap.cc @@ -37,9 +37,9 @@ Bitmap::Bitmap(int width, int height, int depth, uchar *data, int data_size) { Create(width, height, depth, data, data_size); } -Bitmap::Bitmap(int width, int height, int depth, Bytes data) { - Create(width, height, depth, data); -} +// Bitmap::Bitmap(int width, int height, int depth, Bytes data) { +// Create(width, height, depth, data); +// } // Pass raw pixel data directly to the surface void Bitmap::Create(int width, int height, int depth, uchar *data) { @@ -89,7 +89,7 @@ void Bitmap::Create(int width, int height, int depth, uchar *data, int size) { GrayscalePalette(surface_->format->palette); } -void Bitmap::Create(int width, int height, int depth, Bytes data) { +void Bitmap::Create(int width, int height, int depth, const Bytes &data) { active_ = true; width_ = width; height_ = height; diff --git a/src/app/gfx/bitmap.h b/src/app/gfx/bitmap.h index 2dd9932e..812c573d 100644 --- a/src/app/gfx/bitmap.h +++ b/src/app/gfx/bitmap.h @@ -20,15 +20,33 @@ namespace gfx { class Bitmap { public: Bitmap() = default; + Bitmap(int width, int height, int depth, int data_size); - Bitmap(int width, int height, int depth, Bytes data); + // Bitmap(int width, int height, int depth, Bytes data); + + Bitmap(int width, int height, int depth, const Bytes &data) + : width_(width), height_(height), depth_(depth), data_(data) { + CreateTextureFromData(); + } + + // Function to create texture from pixel data + void CreateTextureFromData() { + // Safely create the texture from the raw data + // Assuming a function exists that converts Bytes to the appropriate format + auto raw_pixel_data = data_; + surface_ = std::shared_ptr( + SDL_CreateRGBSurfaceWithFormat(0, width_, height_, depth_, + SDL_PIXELFORMAT_INDEX8), + SDL_Surface_Deleter()); + surface_->pixels = data_.data(); + } [[deprecated]] Bitmap(int width, int height, int depth, uchar *data); [[deprecated]] Bitmap(int width, int height, int depth, uchar *data, int data_size); void Create(int width, int height, int depth, int data_size); - void Create(int width, int height, int depth, Bytes data); + void Create(int width, int height, int depth, const Bytes &data); [[deprecated]] void Create(int width, int height, int depth, uchar *data); [[deprecated]] void Create(int width, int height, int depth, uchar *data, @@ -73,8 +91,7 @@ class Bitmap { height_ = 0; depth_ = 0; data_size_ = 0; - palette_.Clear(); // assuming there's a Clear() method or similar on - // SNESPalette + palette_.Clear(); } int width() const { return width_; } @@ -84,6 +101,7 @@ class Bitmap { auto at(int i) const { return pixel_data_[i]; } auto texture() const { return texture_.get(); } auto IsActive() const { return active_; } + auto SetActive(bool active) { active_ = active; } private: struct SDL_Texture_Deleter { @@ -137,7 +155,7 @@ class BitmapManager { if (it != bitmap_cache_.end()) { return it->second; } - return nullptr; // or handle the error accordingly + return nullptr; } using value_type = std::pair>; @@ -153,8 +171,6 @@ class BitmapManager { const_iterator cbegin() const noexcept { return bitmap_cache_.cbegin(); } const_iterator cend() const noexcept { return bitmap_cache_.cend(); } - - std::shared_ptr GetBitmap(int id) { auto it = bitmap_cache_.find(id); if (it != bitmap_cache_.end()) { diff --git a/src/app/gui/input.cc b/src/app/gui/input.cc index 77eacc3a..beb1a400 100644 --- a/src/app/gui/input.cc +++ b/src/app/gui/input.cc @@ -4,6 +4,107 @@ #include #include "absl/strings/string_view.h" +namespace ImGui { + +static inline ImGuiInputTextFlags InputScalar_DefaultCharsFilter( + ImGuiDataType data_type, const char* format) { + if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) + return ImGuiInputTextFlags_CharsScientific; + const char format_last_char = format[0] ? format[strlen(format) - 1] : 0; + return (format_last_char == 'x' || format_last_char == 'X') + ? ImGuiInputTextFlags_CharsHexadecimal + : ImGuiInputTextFlags_CharsDecimal; +} + +bool InputScalarLeft(const char* label, ImGuiDataType data_type, void* p_data, + const void* p_step, const void* p_step_fast, + const char* format, float input_width, + ImGuiInputTextFlags flags) { + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) return false; + + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + + if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; + + char buf[64]; + DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format); + + // Testing ActiveId as a minor optimization as filtering is not needed until + // active + if (g.ActiveId == 0 && (flags & (ImGuiInputTextFlags_CharsDecimal | + ImGuiInputTextFlags_CharsHexadecimal | + ImGuiInputTextFlags_CharsScientific)) == 0) + flags |= InputScalar_DefaultCharsFilter(data_type, format); + flags |= + ImGuiInputTextFlags_AutoSelectAll | + ImGuiInputTextFlags_NoMarkEdited; // We call MarkItemEdited() ourselves + // by comparing the actual data rather + // than the string. + + bool value_changed = false; + if (p_step == NULL) { + ImGui::SetNextItemWidth(input_width); + if (InputText(label, buf, IM_ARRAYSIZE(buf), flags)) + value_changed = DataTypeApplyFromText(buf, data_type, p_data, format); + } else { + const float button_size = GetFrameHeight(); + + BeginGroup(); // The only purpose of the group here is to allow the caller + // to query item data e.g. IsItemActive() + PushID(label); + SetNextItemWidth(ImMax( + 1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2)); + + // Place the label on the left of the input field + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, + ImVec2{style.ItemSpacing.x, style.ItemSpacing.y * 2}); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, + ImVec2{style.FramePadding.x, style.FramePadding.y * 2}); + ImGui::AlignTextToFramePadding(); + ImGui::Text("%s", label); + ImGui::SameLine(); + ImGui::SetNextItemWidth(input_width); + if (InputText("", buf, IM_ARRAYSIZE(buf), + flags)) // PushId(label) + "" gives us the expected ID + // from outside point of view + value_changed = DataTypeApplyFromText(buf, data_type, p_data, format); + IMGUI_TEST_ENGINE_ITEM_INFO( + g.LastItemData.ID, label, + g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable); + + // Step buttons + const ImVec2 backup_frame_padding = style.FramePadding; + style.FramePadding.x = style.FramePadding.y; + ImGuiButtonFlags button_flags = + ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups; + if (flags & ImGuiInputTextFlags_ReadOnly) BeginDisabled(); + SameLine(0, style.ItemInnerSpacing.x); + if (ButtonEx("-", ImVec2(button_size, button_size), button_flags)) { + DataTypeApplyOp(data_type, '-', p_data, p_data, + g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step); + value_changed = true; + } + SameLine(0, style.ItemInnerSpacing.x); + if (ButtonEx("+", ImVec2(button_size, button_size), button_flags)) { + DataTypeApplyOp(data_type, '+', p_data, p_data, + g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step); + value_changed = true; + } + if (flags & ImGuiInputTextFlags_ReadOnly) EndDisabled(); + + style.FramePadding = backup_frame_padding; + + PopID(); + EndGroup(); + ImGui::PopStyleVar(2); + } + if (value_changed) MarkItemEdited(g.LastItemData.ID); + + return value_changed; +} +} // namespace ImGui namespace yaze { namespace app { @@ -24,16 +125,16 @@ bool InputHexShort(const char* label, uint32_t* data) { ImGuiInputTextFlags_CharsHexadecimal); } -bool InputHexWord(const char* label, uint16_t* data) { - return ImGui::InputScalar(label, ImGuiDataType_U16, data, &kStepOneHex, - &kStepFastHex, "%04X", - ImGuiInputTextFlags_CharsHexadecimal); +bool InputHexWord(const char* label, uint16_t* data, float input_width) { + return ImGui::InputScalarLeft(label, ImGuiDataType_U16, data, &kStepOneHex, + &kStepFastHex, "%04X", input_width, + ImGuiInputTextFlags_CharsHexadecimal); } -bool InputHexByte(const char* label, uint8_t* data) { - return ImGui::InputScalar(label, ImGuiDataType_U8, data, &kStepOneHex, - &kStepFastHex, "%02X", - ImGuiInputTextFlags_CharsHexadecimal); +bool InputHexByte(const char* label, uint8_t* data, float input_width) { + return ImGui::InputScalarLeft(label, ImGuiDataType_U8, data, &kStepOneHex, + &kStepFastHex, "%02X", input_width, + ImGuiInputTextFlags_CharsHexadecimal); } void ItemLabel(absl::string_view title, ItemLabelFlags flags) { diff --git a/src/app/gui/input.h b/src/app/gui/input.h index 4e273d5a..b8894a39 100644 --- a/src/app/gui/input.h +++ b/src/app/gui/input.h @@ -17,8 +17,10 @@ constexpr ImVec2 kZeroPos = ImVec2(0, 0); IMGUI_API bool InputHex(const char* label, uint64_t* data); IMGUI_API bool InputHexShort(const char* label, uint32_t* data); -IMGUI_API bool InputHexWord(const char* label, uint16_t* data); -IMGUI_API bool InputHexByte(const char* label, uint8_t* data); +IMGUI_API bool InputHexWord(const char* label, uint16_t* data, + float input_width = 50.f); +IMGUI_API bool InputHexByte(const char* label, uint8_t* data, + float input_width = 50.f); using ItemLabelFlags = enum ItemLabelFlag { Left = 1u << 0u, diff --git a/src/app/rom.cc b/src/app/rom.cc index 6700488b..af0742ca 100644 --- a/src/app/rom.cc +++ b/src/app/rom.cc @@ -249,33 +249,30 @@ absl::Status ROM::LoadAllGraphicsData() { core::kTilesheetHeight, core::kTilesheetDepth); graphics_manager_[i]->CreateTexture(renderer_); - - } else { - graphics_bin_[i] = - gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight, - core::kTilesheetDepth, converted_sheet.data(), 0x1000); - graphics_bin_.at(i).CreateTexture(renderer_); } + graphics_bin_[i] = + gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight, + core::kTilesheetDepth, converted_sheet.data(), 0x1000); + graphics_bin_.at(i).CreateTexture(renderer_); if (flags()->kUseBitmapManager) { for (int j = 0; j < graphics_manager_[i].get()->size(); ++j) { graphics_buffer_.push_back(graphics_manager_[i]->at(j)); } - } else { - for (int j = 0; j < graphics_bin_[i].size(); ++j) { - graphics_buffer_.push_back(graphics_bin_.at(i).at(j)); - } } + for (int j = 0; j < graphics_bin_[i].size(); ++j) { + graphics_buffer_.push_back(graphics_bin_.at(i).at(j)); + } + } else { if (flags()->kUseBitmapManager) { - for (int j = 0; j < graphics_manager_[i].get()->size(); ++j) { - graphics_buffer_.push_back(0xFF); - } - } else { - for (int j = 0; j < graphics_bin_[0].size(); ++j) { + for (int j = 0; j < graphics_manager_[0].get()->size(); ++j) { graphics_buffer_.push_back(0xFF); } } + for (int j = 0; j < graphics_bin_[0].size(); ++j) { + graphics_buffer_.push_back(0xFF); + } } } return absl::OkStatus(); diff --git a/src/app/rom.h b/src/app/rom.h index 5e06b535..6d567b7e 100644 --- a/src/app/rom.h +++ b/src/app/rom.h @@ -464,7 +464,7 @@ class ROM : public core::ExperimentFlags { bitmap->UpdateTexture(renderer_); } - auto BitmapManager() const { return graphics_manager_; } + auto BitmapManager() { return graphics_manager_; } std::vector> main_blockset_ids; std::vector> room_blockset_ids; diff --git a/src/app/zelda3/dungeon/room.cc b/src/app/zelda3/dungeon/room.cc index 5c6366a6..e41590be 100644 --- a/src/app/zelda3/dungeon/room.cc +++ b/src/app/zelda3/dungeon/room.cc @@ -78,6 +78,64 @@ void DrawDungeonRoomBG2(std::vector& tiles_bg2_buffer, } } +void Room::LoadHeader() { + // Address of the room header + int headerPointer = (rom()->data()[core::room_header_pointer + 2] << 16) + + (rom()->data()[core::room_header_pointer + 1] << 8) + + (rom()->data()[core::room_header_pointer]); + headerPointer = core::SnesToPc(headerPointer); + + int address = (rom()->data()[core::room_header_pointers_bank] << 16) + + (rom()->data()[(headerPointer + 1) + (room_id_ * 2)] << 8) + + rom()->data()[(headerPointer) + (room_id_ * 2)]; + + auto header_location = core::SnesToPc(address); + + // bg2 = (Background2)((rom()->data()[header_location] >> 5) & 0x07); + // collision = (CollisionKey)((rom()->data()[header_location] >> 2) & 0x07); + // light = ((rom()->data()[header_location]) & 0x01) == 1; + + if (light) { + bg2 = Background2::DarkRoom; + } + + palette = ((rom()->data()[header_location + 1] & 0x3F)); + blockset = (rom()->data()[header_location + 2]); + spriteset = (rom()->data()[header_location + 3]); + // effect = (EffectKey)((rom()->data()[header_location + 4])); + // tag1 = (TagKey)((rom()->data()[header_location + 5])); + // tag2 = (TagKey)((rom()->data()[header_location + 6])); + + // holewarpPlane = ((rom()->data()[header_location + 7]) & 0x03); + staircase_plane[0] = ((rom()->data()[header_location + 7] >> 2) & 0x03); + staircase_plane[1] = ((rom()->data()[header_location + 7] >> 4) & 0x03); + staircase_plane[2] = ((rom()->data()[header_location + 7] >> 6) & 0x03); + staircase_plane[3] = ((rom()->data()[header_location + 8]) & 0x03); + + // if (holewarpPlane == 2) { + // Console::WriteLine("Room Index Plane 1 : Used in room id = " + + // index.ToString("X2")); + // } else if (staircasePlane[0] == 2) { + // Console::WriteLine("Room Index Plane 1 : Used in room id = " + + // index.ToString("X2")); + // } else if (staircasePlane[1] == 2) { + // Console::WriteLine("Room Index Plane 1 : Used in room id = " + + // index.ToString("X2")); + // } else if (staircasePlane[2] == 2) { + // Console::WriteLine("Room Index Plane 1 : Used in room id = " + + // index.ToString("X2")); + // } else if (staircasePlane[3] == 2) { + // Console::WriteLine("Room Index Plane 1 : Used in room id = " + + // index.ToString("X2")); + // } + + // holewarp = (rom()->data()[header_location + 9]); + staircase_rooms[0] = (rom()->data()[header_location + 10]); + staircase_rooms[1] = (rom()->data()[header_location + 11]); + staircase_rooms[2] = (rom()->data()[header_location + 12]); + staircase_rooms[3] = (rom()->data()[header_location + 13]); +} + void Room::LoadSprites() { auto rom_data = rom()->vector(); int spritePointer = (0x04 << 16) + (rom_data[rooms_sprite_pointer + 1] << 8) + @@ -295,6 +353,10 @@ RoomObject Room::AddObject(short oid, uint8_t x, uint8_t y, uint8_t size, } void Room::LoadRoomGraphics(uchar entrance_blockset) { + auto mainGfx = rom()->main_blockset_ids; + auto roomGfx = rom()->room_blockset_ids; + auto spriteGfx = rom()->spriteset_ids; + for (int i = 0; i < 8; i++) { blocks[i] = mainGfx[BackgroundTileset][i]; if (i >= 6 && i <= 6) { diff --git a/src/app/zelda3/dungeon/room.h b/src/app/zelda3/dungeon/room.h index c610f939..9d3af320 100644 --- a/src/app/zelda3/dungeon/room.h +++ b/src/app/zelda3/dungeon/room.h @@ -80,6 +80,8 @@ void DrawDungeonRoomBG2(std::vector& tiles_bg2_buffer, class DungeonDestination { public: + DungeonDestination() = default; + ~DungeonDestination() = default; DungeonDestination(uint8_t i) : Index(i) {} uint8_t Index; @@ -89,6 +91,7 @@ class DungeonDestination { }; struct object_door { + object_door() = default; object_door(short id, uint8_t x, uint8_t y, uint8_t size, uint8_t layer) : id_(id), x_(x), y_(y), size_(size), layer_(layer) {} @@ -101,6 +104,7 @@ struct object_door { }; struct ChestData { + ChestData() = default; ChestData(uchar i, bool s) : id_(i), size_(s){}; uchar id_; @@ -111,6 +115,10 @@ struct StaircaseRooms {}; class Room : public SharedROM { public: + Room() = default; + Room(int room_id) : room_id_(room_id) {} + ~Room() = default; + void LoadHeader(); void LoadSprites(); void LoadChests(); @@ -124,19 +132,29 @@ class Room : public SharedROM { void LoadRoomFromROM(); + uint8_t floor1 = 0; + uint8_t floor2 = 0; + uint8_t blockset = 0; + uint8_t spriteset = 0; + uint8_t palette = 0; + uint8_t layout = 0; + + uint16_t message_id_ = 0; + + gfx::Bitmap current_graphics_; + private: int animated_frame = 0; int room_id_ = 0; - uint8_t floor1; - uint8_t floor2; - uint8_t blockset; - uint8_t spriteset; - uint8_t palette; - uint8_t layout; + bool light; + bool is_loaded_ = false; + Background2 bg2; + + uint8_t staircase_plane[4]; + uint8_t staircase_rooms[4]; - ushort message_id_ = 0; uchar BackgroundTileset; uchar SpriteTileset; uchar Layer2Behavior; @@ -147,11 +165,6 @@ class Room : public SharedROM { std::array blocks; std::array ChestList; - uint8_t mainGfx[37][8]; - uint8_t roomGfx[82][4]; - uint8_t spriteGfx[144][4]; - uint8_t paletteGfx[72][4]; - std::vector sprites_; std::vector staircaseRooms; @@ -171,8 +184,6 @@ class Room : public SharedROM { std::vector chests_in_room; std::vector current_gfx16_; std::vector tilesObjects; - - gfx::Bitmap current_graphics_; }; } // namespace dungeon diff --git a/src/app/zelda3/dungeon/room_object.h b/src/app/zelda3/dungeon/room_object.h index fded0e7c..4a2dda86 100644 --- a/src/app/zelda3/dungeon/room_object.h +++ b/src/app/zelda3/dungeon/room_object.h @@ -7,6 +7,8 @@ #include #include +#include "app/emu/cpu.h" +#include "app/emu/video/ppu.h" #include "app/gfx/snes_palette.h" #include "app/gfx/snes_tile.h" #include "app/rom.h" @@ -17,6 +19,61 @@ namespace app { namespace zelda3 { namespace dungeon { +class DungeonObjectRenderer : public SharedROM { + public: + + struct PseudoVram { + std::vector sheets; + }; + + void CreateVramFromRoomBlockset() { + auto bitmap_manager = rom()->BitmapManager(); + uint16_t room_id = 0; + auto room_blockset = rom()->room_blockset_ids[room_id]; + + for (const auto blockset_id : room_blockset) { + auto blockset = bitmap_manager[(uint16_t)blockset_id]; + vram_.sheets.push_back(*blockset.get()); + } + + } + + void RenderObjectsAsBitmaps() { + auto subtype1_ptr = core::subtype1_tiles; + auto subtype1_routine_ptr = + core::subtype1_tiles + 0x200; // Where the draw routines start + + auto subtype2_ptr = core::subtype2_tiles; + auto subtype2_routine_ptr = + core::subtype2_tiles + 0x80; // Where the draw routines start + + auto subtype3_ptr = core::subtype3_tiles; + auto subtype3_routine_ptr = + core::subtype3_tiles + 0x100; // Where the draw routines start + + auto data = (*rom()).vector(); + // Construct a copy of the rooms VRAM + // Jump to the routine that draws the object based on the ID + // Run the routine and get the VRAM data using the CPU and PPU + // Render the VRAM data to a bitmap + + memory_.Initialize(data); + cpu.PC = subtype1_routine_ptr; + cpu.JMP(cpu.FetchWord()); + auto dest = cpu.PC + 0x10; + while (cpu.PC < dest) { + cpu.ExecuteInstruction(cpu.FetchByte()); + } + } + + emu::MemoryImpl memory_; + emu::ClockImpl clock_; + emu::CPU cpu{memory_, clock_}; + emu::PPU ppu{memory_, clock_}; + gfx::Bitmap bitmap; + PseudoVram vram_; +}; + enum class SpecialObjectType { Chest, BigChest, InterroomStairs }; struct Tile {}; @@ -223,9 +280,7 @@ class Subtype2_Multiple : public RoomObject { // Other member functions and variables }; -class Subtype3 : public RoomObject { - -}; +class Subtype3 : public RoomObject {}; } // namespace dungeon } // namespace zelda3 diff --git a/src/app/zelda3/overworld.cc b/src/app/zelda3/overworld.cc index 0acfee9c..8ccc5947 100644 --- a/src/app/zelda3/overworld.cc +++ b/src/app/zelda3/overworld.cc @@ -11,6 +11,7 @@ #include "absl/container/flat_hash_map.h" #include "absl/status/status.h" #include "app/core/constants.h" +#include "app/core/common.h" #include "app/gfx/bitmap.h" #include "app/gfx/compression.h" #include "app/gfx/snes_tile.h" diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3674c2ff..0ec029eb 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable( emu/spc700_test.cc compression_test.cc snes_palette_test.cc + room_object_test.cc ../src/cli/patch.cc ../src/cli/command_handler.cc ../src/app/rom.cc diff --git a/test/emu/cpu_test.cc b/test/emu/cpu_test.cc index e0ef6b3c..b83f8fff 100644 --- a/test/emu/cpu_test.cc +++ b/test/emu/cpu_test.cc @@ -5,145 +5,12 @@ #include "app/emu/clock.h" #include "app/emu/memory/memory.h" +#include "app/emu/memory/mock_memory.h" namespace yaze { namespace app { namespace emu { -class MockClock : public Clock { - public: - MOCK_METHOD(void, UpdateClock, (double delta), (override)); - MOCK_METHOD(unsigned long long, GetCycleCount, (), (const, override)); - MOCK_METHOD(void, ResetAccumulatedTime, (), (override)); - MOCK_METHOD(void, SetFrequency, (float new_frequency), (override)); - MOCK_METHOD(float, GetFrequency, (), (const, override)); -}; - -class MockMemory : public Memory { - public: - MOCK_CONST_METHOD1(ReadByte, uint8_t(uint16_t address)); - MOCK_CONST_METHOD1(ReadWord, uint16_t(uint16_t address)); - MOCK_CONST_METHOD1(ReadWordLong, uint32_t(uint16_t address)); - MOCK_METHOD(std::vector, ReadByteVector, - (uint16_t address, uint16_t length), (const, override)); - - MOCK_METHOD2(WriteByte, void(uint32_t address, uint8_t value)); - MOCK_METHOD2(WriteWord, void(uint32_t address, uint16_t value)); - - MOCK_METHOD1(PushByte, void(uint8_t value)); - MOCK_METHOD0(PopByte, uint8_t()); - MOCK_METHOD1(PushWord, void(uint16_t value)); - MOCK_METHOD0(PopWord, uint16_t()); - MOCK_METHOD1(PushLong, void(uint32_t value)); - MOCK_METHOD0(PopLong, uint32_t()); - - MOCK_CONST_METHOD0(SP, int16_t()); - MOCK_METHOD1(SetSP, void(int16_t value)); - - MOCK_METHOD1(SetMemory, void(const std::vector& data)); - MOCK_METHOD1(LoadData, void(const std::vector& data)); - - MOCK_METHOD0(ClearMemory, void()); - - MOCK_CONST_METHOD1(at, uint8_t(int i)); - uint8_t operator[](int i) const override { return at(i); } - - void SetMemoryContents(const std::vector& data) { - memory_.resize(64000); - std::copy(data.begin(), data.end(), memory_.begin()); - } - - void SetMemoryContents(const std::vector& data) { - memory_.resize(64000); - int i = 0; - for (const auto& each : data) { - memory_[i] = each & 0xFF; - memory_[i + 1] = (each >> 8) & 0xFF; - i += 2; - } - } - - void InsertMemory(const uint64_t address, const std::vector& data) { - int i = 0; - for (const auto& each : data) { - memory_[address + i] = each; - i++; - } - } - - void Init() { - ON_CALL(*this, ReadByte(::testing::_)) - .WillByDefault( - [this](uint16_t address) { return memory_.at(address); }); - ON_CALL(*this, ReadWord(::testing::_)) - .WillByDefault([this](uint16_t address) { - return static_cast(memory_.at(address)) | - (static_cast(memory_.at(address + 1)) << 8); - }); - ON_CALL(*this, ReadWordLong(::testing::_)) - .WillByDefault([this](uint16_t address) { - return static_cast(memory_.at(address)) | - (static_cast(memory_.at(address + 1)) << 8) | - (static_cast(memory_.at(address + 2)) << 16); - }); - ON_CALL(*this, ReadByteVector(::testing::_, ::testing::_)) - .WillByDefault([this](uint16_t address, uint16_t length) { - std::vector data; - for (int i = 0; i < length; i++) { - data.push_back(memory_.at(address + i)); - } - return data; - }); - ON_CALL(*this, WriteByte(::testing::_, ::testing::_)) - .WillByDefault([this](uint32_t address, uint8_t value) { - memory_[address] = value; - }); - ON_CALL(*this, WriteWord(::testing::_, ::testing::_)) - .WillByDefault([this](uint32_t address, uint16_t value) { - memory_[address] = value & 0xFF; - memory_[address + 1] = (value >> 8) & 0xFF; - }); - ON_CALL(*this, PushByte(::testing::_)).WillByDefault([this](uint8_t value) { - memory_.at(SP_) = value; - }); - ON_CALL(*this, PopByte()).WillByDefault([this]() { - uint8_t value = memory_.at(SP_); - this->SetSP(SP_ + 1); - return value; - }); - ON_CALL(*this, PushWord(::testing::_)) - .WillByDefault([this](uint16_t value) { - memory_.at(SP_) = value & 0xFF; - memory_.at(SP_ + 1) = (value >> 8) & 0xFF; - }); - ON_CALL(*this, PopWord()).WillByDefault([this]() { - uint16_t value = static_cast(memory_.at(SP_)) | - (static_cast(memory_.at(SP_ + 1)) << 8); - this->SetSP(SP_ + 2); - return value; - }); - ON_CALL(*this, PushLong(::testing::_)) - .WillByDefault([this](uint32_t value) { - memory_.at(SP_) = value & 0xFF; - memory_.at(SP_ + 1) = (value >> 8) & 0xFF; - memory_.at(SP_ + 2) = (value >> 16) & 0xFF; - }); - ON_CALL(*this, PopLong()).WillByDefault([this]() { - uint32_t value = static_cast(memory_.at(SP_)) | - (static_cast(memory_.at(SP_ + 1)) << 8) | - (static_cast(memory_.at(SP_ + 2)) << 16); - this->SetSP(SP_ + 3); - return value; - }); - ON_CALL(*this, ClearMemory()).WillByDefault([this]() { - memory_.resize(64000, 0x00); - }); - } - - std::vector memory_; - uint16_t SP_ = 0x01FF; -}; - class CPUTest : public ::testing::Test { public: void SetUp() override { @@ -1068,7 +935,7 @@ TEST_F(CPUTest, EOR_DirectPageIndirectLong) { std::vector data = {0x47, 0x7F}; mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x007F, {0x00, 0x10, 0x00}); // [0x007F] = 0x1000 - mock_memory.InsertMemory(0x1000, {0b01010101}); // [0x1000] = 0b01010101 + mock_memory.InsertMemory(0x1000, {0b01010101}); // [0x1000] = 0b01010101 cpu.ExecuteInstruction(0x47); // EOR Direct Page Indirect Long EXPECT_EQ(cpu.A, 0b11111111); // A register should now be 0b11111111 @@ -1106,7 +973,7 @@ TEST_F(CPUTest, EOR_DirectPageIndirectLongIndexedY) { std::vector data = {0x51, 0x7E}; mock_memory.SetMemoryContents(data); mock_memory.InsertMemory(0x007E, {0x00, 0x10, 0x00}); // [0x007E] = 0x1000 - mock_memory.InsertMemory(0x1002, {0b01010101}); // [0x1002] = 0b01010101 + mock_memory.InsertMemory(0x1002, {0b01010101}); // [0x1002] = 0b01010101 cpu.ExecuteInstruction(0x51); // EOR DP Indirect Long Indexed, Y EXPECT_EQ(cpu.A, 0b11111111); // A register should now be 0b11111111 @@ -1134,7 +1001,8 @@ TEST_F(CPUTest, EOR_DirectPageIndirectIndexedY) { // std::vector data = {0x57, 0x7C}; // mock_memory.SetMemoryContents(data); // mock_memory.InsertMemory(0x007E, {0x00, 0x10, 0x00}); // [0x007E] = 0x1000 -// mock_memory.InsertMemory(0x1002, {0b01010101}); // [0x1002] = 0b01010101 +// mock_memory.InsertMemory(0x1002, {0b01010101}); // [0x1002] = +// 0b01010101 // cpu.ExecuteInstruction(0x57); // EOR DP Indirect Long Indexed, Y // EXPECT_EQ(cpu.A, 0b11111111); // A register should now be 0b11111111 diff --git a/test/room_object_test.cc b/test/room_object_test.cc new file mode 100644 index 00000000..29549906 --- /dev/null +++ b/test/room_object_test.cc @@ -0,0 +1,24 @@ +#include "app/zelda3/dungeon/room_object.h" + +#include +#include + +#include "app/emu/cpu.h" +#include "app/emu/memory/mock_memory.h" +#include "app/emu/video/ppu.h" +#include "app/gfx/bitmap.h" +#include "app/rom.h" + +namespace yaze { +namespace test { + +TEST(DungeonObjectTest, RenderObjectsAsBitmaps) { + app::ROM rom; + rom.LoadFromFile("/Users/scawful/Code/yaze/build/bin/zelda3.sfc"); + + app::zelda3::dungeon::DungeonObjectRenderer renderer; + +} + +} // namespace test +} // namespace yaze \ No newline at end of file