yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_object_emulator_preview.cc
Go to the documentation of this file.
3
7#include "app/core/window.h"
8#include <cstdio>
9
10namespace yaze {
11namespace gui {
12
16
18 // if (object_texture_) {
19 // renderer_->DestroyTexture(object_texture_);
20 // }
21}
22
24 renderer_ = renderer;
25 rom_ = rom;
26 snes_instance_ = std::make_unique<emu::Snes>();
27 std::vector<uint8_t> rom_data = rom->vector();
28 snes_instance_->Init(rom_data);
29
30 // object_texture_ = renderer_->CreateTexture(256, 256);
31}
32
34 if (!show_window_) return;
35
36 if (ImGui::Begin("Dungeon Object Emulator Preview", &show_window_, ImGuiWindowFlags_AlwaysAutoResize)) {
37 AutoWidgetScope scope("DungeonEditor/EmulatorPreview");
38
39 // ROM status indicator
40 if (rom_ && rom_->is_loaded()) {
41 ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "ROM: Loaded ✓");
42 } else {
43 ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "ROM: Not loaded ✗");
44 }
45
46 ImGui::Separator();
48 ImGui::Separator();
49
50 // Preview image with border
51 if (object_texture_) {
52 ImGui::BeginChild("PreviewRegion", ImVec2(260, 260), true, ImGuiWindowFlags_NoScrollbar);
53 ImGui::Image((ImTextureID)object_texture_, ImVec2(256, 256));
54 ImGui::EndChild();
55 } else {
56 ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "No texture available");
57 }
58
59 // Debug info section
60 ImGui::Separator();
61 ImGui::Text("Execution:");
62 ImGui::Indent();
63 ImGui::Text("Cycles: %d %s", last_cycle_count_,
64 last_cycle_count_ >= 100000 ? "(TIMEOUT)" : "");
65 ImGui::Unindent();
66
67 // Status with color coding
68 ImGui::Text("Status:");
69 ImGui::Indent();
70 if (last_error_.empty()) {
71 ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "✓ OK");
72 } else {
73 ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "✗ %s", last_error_.c_str());
74 }
75 ImGui::Unindent();
76
77 // Help text
78 ImGui::Separator();
79 ImGui::TextWrapped("This tool uses the SNES emulator to render objects by "
80 "executing the game's native drawing routines from bank $01.");
81 }
82 ImGui::End();
83}
84
86 ImGui::Text("Object Configuration:");
87 ImGui::Indent();
88
89 // Object ID with hex display
90 AutoInputInt("Object ID", &object_id_, 1, 10, ImGuiInputTextFlags_CharsHexadecimal);
91 ImGui::SameLine();
92 ImGui::TextDisabled("($%03X)", object_id_);
93
94 // Room context
95 AutoInputInt("Room Context", &room_id_, 1, 10);
96 ImGui::SameLine();
97 ImGui::TextDisabled("(for graphics/palette)");
98
99 // Position controls
100 AutoSliderInt("X Position", &object_x_, 0, 63);
101 AutoSliderInt("Y Position", &object_y_, 0, 63);
102
103 ImGui::Unindent();
104
105 // Render button - large and prominent
106 ImGui::Separator();
107 if (ImGui::Button("Render Object", ImVec2(-1, 0))) {
109 }
110
111 // Quick test buttons
112 if (ImGui::BeginPopup("QuickTests")) {
113 if (ImGui::MenuItem("Floor tile (0x00)")) { object_id_ = 0x00; TriggerEmulatedRender(); }
114 if (ImGui::MenuItem("Wall N (0x60)")) { object_id_ = 0x60; TriggerEmulatedRender(); }
115 if (ImGui::MenuItem("Door (0xF0)")) { object_id_ = 0xF0; TriggerEmulatedRender(); }
116 ImGui::EndPopup();
117 }
118 if (ImGui::Button("Quick Tests...", ImVec2(-1, 0))) {
119 ImGui::OpenPopup("QuickTests");
120 }
121}
122
124 if (!rom_ || !rom_->is_loaded()) {
125 last_error_ = "ROM not loaded";
126 return;
127 }
128
129 last_error_.clear();
131
132 // 1. Reset and configure the SNES state
133 snes_instance_->Reset(true);
134 auto& cpu = snes_instance_->cpu();
135 auto& ppu = snes_instance_->ppu();
136 auto& memory = snes_instance_->memory();
137
138 // 2. Load room context (graphics, palettes)
140
141 // 3. Load palette into CGRAM
142 auto dungeon_main_pal_group = rom_->palette_group().dungeon_main;
143
144 // Validate and clamp palette ID
145 int palette_id = default_room.palette;
146 if (palette_id < 0 || palette_id >= static_cast<int>(dungeon_main_pal_group.size())) {
147 printf("[EMU] Warning: Room palette %d out of bounds, using palette 0\n", palette_id);
148 palette_id = 0;
149 }
150
151 auto palette = dungeon_main_pal_group[palette_id];
152 for (size_t i = 0; i < palette.size() && i < 256; ++i) {
153 ppu.cgram[i] = palette[i].snes();
154 }
155
156 // 4. Load graphics into VRAM
157 default_room.LoadRoomGraphics(default_room.blockset);
158 default_room.CopyRoomGraphicsToBuffer();
159 const auto& gfx_buffer = default_room.get_gfx_buffer();
160 for (size_t i = 0; i < gfx_buffer.size() / 2 && i < 0x8000; ++i) {
161 ppu.vram[i] = gfx_buffer[i * 2] | (gfx_buffer[i * 2 + 1] << 8);
162 }
163
164 // 5. CRITICAL: Initialize tilemap buffers in WRAM
165 // Game uses $7E:2000 for BG1 tilemap buffer, $7E:4000 for BG2
166 for (uint32_t i = 0; i < 0x2000; i++) {
167 snes_instance_->Write(0x7E2000 + i, 0x00); // BG1 tilemap buffer
168 snes_instance_->Write(0x7E4000 + i, 0x00); // BG2 tilemap buffer
169 }
170
171 // 6. Setup PPU registers for dungeon rendering
172 snes_instance_->Write(0x002105, 0x09); // BG Mode 1 (4bpp for BG1/2)
173 snes_instance_->Write(0x002107, 0x40); // BG1 tilemap at VRAM $4000 (32x32)
174 snes_instance_->Write(0x002108, 0x48); // BG2 tilemap at VRAM $4800 (32x32)
175 snes_instance_->Write(0x002109, 0x00); // BG1 chr data at VRAM $0000
176 snes_instance_->Write(0x00210A, 0x00); // BG2 chr data at VRAM $0000
177 snes_instance_->Write(0x00212C, 0x03); // Enable BG1+BG2 on main screen
178 snes_instance_->Write(0x002100, 0x0F); // Screen display on, full brightness
179
180 // 7. Setup WRAM variables for drawing context
181 snes_instance_->Write(0x7E00AF, room_id_ & 0xFF);
182 snes_instance_->Write(0x7E049C, 0x00);
183 snes_instance_->Write(0x7E049E, 0x00);
184
185 // 8. Create object and encode to bytes
187 auto bytes = obj.EncodeObjectToBytes();
188
189 const uint32_t object_data_addr = 0x7E1000;
190 snes_instance_->Write(object_data_addr, bytes.b1);
191 snes_instance_->Write(object_data_addr + 1, bytes.b2);
192 snes_instance_->Write(object_data_addr + 2, bytes.b3);
193 snes_instance_->Write(object_data_addr + 3, 0xFF); // Terminator
194 snes_instance_->Write(object_data_addr + 4, 0xFF);
195
196 // 9. Setup object pointer in WRAM
197 snes_instance_->Write(0x7E00B7, object_data_addr & 0xFF);
198 snes_instance_->Write(0x7E00B8, (object_data_addr >> 8) & 0xFF);
199 snes_instance_->Write(0x7E00B9, (object_data_addr >> 16) & 0xFF);
200
201 // 10. Setup CPU state
202 cpu.PB = 0x01;
203 cpu.DB = 0x7E;
204 cpu.D = 0x0000;
205 cpu.SetSP(0x01FF);
206 cpu.status = 0x30; // 8-bit mode
207
208 // Calculate X register (tilemap position)
209 cpu.X = (object_y_ * 0x80) + (object_x_ * 2);
210 cpu.Y = 0; // Object data offset
211
212 // 11. Lookup the object's drawing handler
213 uint16_t handler_offset = 0;
214 auto rom_data = rom_->data();
215 uint32_t table_addr = 0;
216
217 if (object_id_ < 0x100) {
218 table_addr = 0x018200 + (object_id_ * 2);
219 } else if (object_id_ < 0x200) {
220 table_addr = 0x018470 + ((object_id_ - 0x100) * 2);
221 } else {
222 table_addr = 0x0185F0 + ((object_id_ - 0x200) * 2);
223 }
224
225 if (table_addr < rom_->size() - 1) {
226 uint8_t lo = rom_data[table_addr];
227 uint8_t hi = rom_data[table_addr + 1];
228 handler_offset = lo | (hi << 8);
229 } else {
230 last_error_ = "Object ID out of bounds for handler lookup";
231 return;
232 }
233
234 if (handler_offset == 0x0000) {
235 char buf[256];
236 snprintf(buf, sizeof(buf), "Object $%04X has no drawing routine", object_id_);
237 last_error_ = buf;
238 return;
239 }
240
241 // 12. Setup return address and jump to handler
242 const uint16_t return_addr = 0x8000;
243 snes_instance_->Write(0x018000, 0x6B); // RTL instruction (0x6B not 0x60!)
244
245 // Push return address for RTL (3 bytes: bank, high, low)
246 uint16_t sp = cpu.SP();
247 snes_instance_->Write(0x010000 | sp--, 0x01); // Bank byte
248 snes_instance_->Write(0x010000 | sp--, (return_addr - 1) >> 8); // High
249 snes_instance_->Write(0x010000 | sp--, (return_addr - 1) & 0xFF); // Low
250 cpu.SetSP(sp);
251
252 // Jump to handler (offset is relative to RoomDrawObjectData base)
253 cpu.PC = handler_offset;
254
255 printf("[EMU] Rendering object $%04X at (%d,%d), handler=$%04X\n",
256 object_id_, object_x_, object_y_, handler_offset);
257
258 // 13. Run emulator with timeout
259 int max_cycles = 100000;
260 int cycles = 0;
261 while (cycles < max_cycles) {
262 if (cpu.PB == 0x01 && cpu.PC == return_addr) {
263 break; // Hit return address
264 }
265 snes_instance_->RunCycle();
266 cycles++;
267 }
268
269 last_cycle_count_ = cycles;
270
271 printf("[EMU] Completed after %d cycles, PC=$%02X:%04X\n",
272 cycles, cpu.PB, cpu.PC);
273
274 if (cycles >= max_cycles) {
275 last_error_ = "Timeout: exceeded max cycles";
276 return;
277 }
278
279 // 14. Force PPU to render the tilemaps
280 ppu.HandleFrameStart();
281 for (int line = 0; line < 224; line++) {
282 ppu.RunLine(line);
283 }
284 ppu.HandleVblank();
285
286 // 15. Get the rendered pixels from PPU
287 void* pixels = nullptr;
288 int pitch = 0;
289 if (renderer_->LockTexture(object_texture_, nullptr, &pixels, &pitch)) {
290 snes_instance_->SetPixels(static_cast<uint8_t*>(pixels));
292 }
293}
294
295} // namespace gui
296} // namespace yaze
The Rom class is used to load, save, and modify Rom data.
Definition rom.h:71
auto palette_group() const
Definition rom.h:213
auto vector() const
Definition rom.h:207
auto data() const
Definition rom.h:203
bool is_loaded() const
Definition rom.h:197
Defines an abstract interface for all rendering operations.
Definition irenderer.h:35
virtual void UnlockTexture(TextureHandle texture)=0
virtual bool LockTexture(TextureHandle texture, SDL_Rect *rect, void **pixels, int *pitch)=0
RAII scope that enables automatic widget registration.
void Initialize(gfx::IRenderer *renderer, Rom *rom)
ObjectBytes EncodeObjectToBytes() const
void CopyRoomGraphicsToBuffer()
Definition room.cc:228
void LoadRoomGraphics(uint8_t entrance_blockset=0xFF)
Definition room.cc:195
uint8_t blockset
Definition room.h:349
const std::array< uint8_t, 0x4000 > & get_gfx_buffer() const
Definition room.h:386
uint8_t palette
Definition room.h:351
bool AutoInputInt(const char *label, int *v, int step=1, int step_fast=100, ImGuiInputTextFlags flags=0)
bool AutoSliderInt(const char *label, int *v, int v_min, int v_max, const char *format="%d", ImGuiSliderFlags flags=0)
Room LoadRoomFromRom(Rom *rom, int room_id)
Definition room.cc:75
Main namespace for the application.
Automatic widget registration helpers for ImGui Test Engine integration.