202 ImGui::TextColored(theme.text_info,
"Object Selection");
207 ImGuiInputTextFlags_CharsHexadecimal);
209 ImGui::TextColored(theme.text_secondary_gray,
"($%03X)",
object_id_);
215 ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.panel_bg_darker);
216 ImGui::BeginChild(
"ObjectInfo", ImVec2(0, 60),
true);
217 ImGui::TextColored(theme.accent_color,
"Name:");
219 ImGui::TextWrapped(
"%s", name);
220 ImGui::TextColored(theme.accent_color,
"Type:");
222 ImGui::Text(
"%d", type);
224 ImGui::PopStyleColor();
229 if (ImGui::BeginCombo(
"Quick Select",
"Choose preset...")) {
231 if (ImGui::Selectable(preset.name,
object_id_ == preset.id)) {
235 ImGui::SetItemDefaultFocus();
253 ImGui::TextColored(theme.text_info,
"Position & Size");
260 ImGui::TextDisabled(
"(?)");
261 if (ImGui::IsItemHovered()) {
263 "Size parameter for scalable objects.\nMany objects ignore this value.");
270 ImGui::TextColored(theme.text_info,
"Rendering Context");
275 ImGui::TextDisabled(
"(?)");
276 if (ImGui::IsItemHovered()) {
277 ImGui::SetTooltip(
"Room ID for graphics and palette context");
283 ImGui::TextColored(theme.text_info,
"Render Mode");
285 if (ImGui::RadioButton(
"Static (ObjectDrawer)", &mode, 0)) {
290 ImGui::TextDisabled(
"(?)");
291 if (ImGui::IsItemHovered()) {
293 "Uses ObjectDrawer to render objects.\n"
294 "This is the reliable method that matches the main canvas.");
296 if (ImGui::RadioButton(
"Emulator (Experimental)", &mode, 1)) {
300 ImGui::TextDisabled(
"(?)");
301 if (ImGui::IsItemHovered()) {
303 "Attempts to run game drawing handlers via CPU emulation.\n"
304 "EXPERIMENTAL: Handlers require full game state to work.\n"
305 "Most objects will time out without rendering.");
348 if (result.ok() && result->success) {
354 void* pixels =
nullptr;
357 memcpy(pixels, result->rgba_pixels.data(), result->rgba_pixels.size());
360 printf(
"[SERVICE-EMU] Rendered object $%04X via EmulatorRenderService\n",
364 printf(
"[SERVICE-EMU] Emulated render failed, falling back to legacy: %s\n",
365 result.ok() ? result->error.c_str()
366 : std::string(result.status().message()).c_str());
374 last_error_ =
"Failed to initialize SNES emulator";
399 int palette_id = default_room.
palette;
400 if (palette_id < 0 ||
401 palette_id >=
static_cast<int>(dungeon_main_pal_group.size())) {
402 printf(
"[EMU] Warning: Room palette %d out of bounds, using palette 0\n",
408 auto base_palette = dungeon_main_pal_group[palette_id];
409 for (
size_t i = 0; i < base_palette.size() && i < 90; ++i) {
410 ppu.cgram[i] = base_palette[i].snes();
415 constexpr uint32_t kSpriteAuxPaletteSnes = 0x0DD308;
416 const uint32_t kSpriteAuxPalettePc =
SnesToPc(kSpriteAuxPaletteSnes);
417 for (
int i = 0; i < 30; ++i) {
418 uint32_t addr = kSpriteAuxPalettePc + i * 2;
424 printf(
"[EMU] Loaded full palette: 90 dungeon + 30 sprite aux = 120 colors\n");
433 std::vector<uint8_t> linear_data(gfx_buffer.begin(), gfx_buffer.end());
434 auto planar_data = ConvertLinear8bppToPlanar4bpp(linear_data);
437 for (
size_t i = 0; i < planar_data.size() / 2 && i < 0x8000; ++i) {
438 ppu.vram[i] = planar_data[i * 2] | (planar_data[i * 2 + 1] << 8);
441 printf(
"[EMU] Converted %zu bytes (8BPP linear) to %zu bytes (4BPP planar)\n",
442 gfx_buffer.size(), planar_data.size());
446 for (uint32_t i = 0; i < 0x2000; i++) {
460 constexpr uint8_t kPointerZeroPageAddrs[] = {0xBF, 0xC2, 0xC5, 0xC8, 0xCB,
461 0xCE, 0xD1, 0xD4, 0xD7, 0xDA,
466 constexpr uint32_t kBG1TilemapBase = 0x7E2000;
467 constexpr uint32_t kRowStride = 0x80;
469 for (
int i = 0; i < 11; ++i) {
470 uint32_t wram_addr = kBG1TilemapBase + (i * kRowStride);
471 uint8_t lo = wram_addr & 0xFF;
472 uint8_t mid = (wram_addr >> 8) & 0xFF;
473 uint8_t hi = (wram_addr >> 16) & 0xFF;
475 uint8_t zp_addr = kPointerZeroPageAddrs[i];
481 printf(
"[EMU] Tilemap ptr $%02X = $%06X\n", zp_addr, wram_addr);
500 apu.out_ports_[0] = 0xAA;
501 apu.out_ports_[1] = 0xBB;
502 apu.out_ports_[2] = 0x00;
503 apu.out_ports_[3] = 0x00;
504 printf(
"[EMU] APU mock: out_ports_[0]=$AA, out_ports_[1]=$BB (SPC→CPU)\n");
523 printf(
"[EMU] Object params: type=%d, y_offset=$%04X, size=%d\n",
530 const uint32_t object_data_addr = 0x7E1000;
547 uint32_t data_table_snes = 0;
548 uint32_t handler_table_snes = 0;
552 data_table_snes = 0x018000 + (
object_id_ * 2);
553 handler_table_snes = 0x018200 + (
object_id_ * 2);
556 data_table_snes = 0x018370 + ((
object_id_ - 0x100) * 2);
557 handler_table_snes = 0x018470 + ((
object_id_ - 0x100) * 2);
560 data_table_snes = 0x0184F0 + ((
object_id_ - 0x200) * 2);
561 handler_table_snes = 0x0185F0 + ((
object_id_ - 0x200) * 2);
565 uint32_t data_table_pc =
SnesToPc(data_table_snes);
566 uint32_t handler_table_pc =
SnesToPc(handler_table_snes);
568 uint16_t data_offset = 0;
569 uint16_t handler_addr = 0;
571 if (data_table_pc + 1 <
rom_->
size() && handler_table_pc + 1 <
rom_->
size()) {
572 data_offset = rom_data[data_table_pc] | (rom_data[data_table_pc + 1] << 8);
573 handler_addr = rom_data[handler_table_pc] | (rom_data[handler_table_pc + 1] << 8);
575 last_error_ =
"Object ID out of bounds for handler lookup";
579 if (handler_addr == 0x0000) {
581 snprintf(buf,
sizeof(buf),
"Object $%04X has no drawing routine",
587 printf(
"[EMU] Two-table lookup (PC: $%04X, $%04X): data_offset=$%04X, handler=$%04X\n",
588 data_table_pc, handler_table_pc, data_offset, handler_addr);
606 const uint16_t trap_addr = 0xFF00;
611 uint16_t sp = cpu.SP();
618 cpu.PC = handler_addr;
620 printf(
"[EMU] Rendering object $%04X at (%d,%d), handler=$%04X\n",
object_id_,
622 printf(
"[EMU] X=data_offset=$%04X, Y=tilemap_pos=$%04X, PB:PC=$%02X:%04X\n",
623 cpu.X, cpu.Y, cpu.PB, cpu.PC);
624 printf(
"[EMU] STP trap at $01:%04X for return detection\n", trap_addr);
628 int max_opcodes = 100000;
630 while (opcodes < max_opcodes) {
632 uint32_t current_addr = (cpu.PB << 16) | cpu.PC;
634 if (current_opcode == 0xDB) {
635 printf(
"[EMU] STP trap hit at $%02X:%04X - handler completed!\n",
643 if ((opcodes & 0x3F) == 0) {
644 apu.out_ports_[0] = 0xAA;
645 apu.out_ports_[1] = 0xBB;
650 if (cpu.PB == 0x00 && cpu.PC == 0x8891) {
653 static int apu_loop_count = 0;
654 if (++apu_loop_count > 100) {
655 printf(
"[EMU] WARNING: Stuck in APU loop at $00:8891, forcing skip\n");
666 if (opcodes == 10000) {
667 printf(
"[EMU] WRAM $7E2000 after 10k opcodes: ");
668 for (
int i = 0; i < 8; i++) {
678 printf(
"[EMU] Completed after %d opcodes, PC=$%02X:%04X\n", opcodes, cpu.PB,
681 if (opcodes >= max_opcodes) {
684 printf(
"[EMU] WRAM BG1 tilemap sample at $7E2000:\n");
685 for (
int i = 0; i < 16; i++) {
699 for (uint32_t i = 0; i < 0x800; i++) {
702 ppu.vram[0x4000 + i] = lo | (hi << 8);
705 for (uint32_t i = 0; i < 0x800; i++) {
708 ppu.vram[0x4800 + i] = lo | (hi << 8);
712 printf(
"[EMU] VRAM tilemap at $4000 (BG1): ");
713 for (
int i = 0; i < 8; i++) {
714 printf(
"%04X ", ppu.vram[0x4000 + i]);
719 ppu.HandleFrameStart();
720 for (
int line = 0; line < 224; line++) {
726 void* pixels =
nullptr;
755 if (result.ok() && result->success) {
760 void* pixels =
nullptr;
764 memcpy(pixels, result->rgba_pixels.data(), result->rgba_pixels.size());
767 printf(
"[SERVICE] Rendered object $%04X via EmulatorRenderService\n",
772 printf(
"[SERVICE] Render failed, falling back to legacy: %s\n",
773 result.ok() ? result->error.c_str()
774 : std::string(result.status().message()).c_str());
790 if (palette_id < 0 ||
791 palette_id >=
static_cast<int>(dungeon_main_pal_group.size())) {
794 auto base_palette = dungeon_main_pal_group[palette_id];
802 for (
size_t i = 0; i < base_palette.size() && i < 90; ++i) {
806 while (palette.
size() < 90) {
813 constexpr uint32_t kSpriteAuxPaletteSnes = 0x0DD308;
814 const uint32_t kSpriteAuxPalettePc =
SnesToPc(kSpriteAuxPaletteSnes);
815 for (
int i = 0; i < 30; ++i) {
816 uint32_t addr = kSpriteAuxPalettePc + i * 2;
841 constexpr int kBgSize = 512;
843 std::vector<uint8_t>(kBgSize * kBgSize, 0));
845 std::vector<uint8_t>(kBgSize * kBgSize, 0));
856 preview_palette_group);
859 printf(
"[STATIC] DrawObject failed: %s\n",
last_error_.c_str());
863 printf(
"[STATIC] Drew object $%04X at (%d,%d) size=%d\n",
object_id_,
872 constexpr int kPreviewSize = 256;
873 constexpr uint8_t kTransparentMarker = 0xFF;
876 kPreviewSize, kPreviewSize, 8,
877 std::vector<uint8_t>(kPreviewSize * kPreviewSize, kTransparentMarker));
887 const auto& bg1_data = bg1_bitmap.vector();
888 const auto& bg2_data = bg2_bitmap.vector();
891 int offset_x = std::max(0, (
object_x_ * 8) - kPreviewSize / 2);
892 int offset_y = std::max(0, (
object_y_ * 8) - kPreviewSize / 2);
895 offset_x = std::min(offset_x, kBgSize - kPreviewSize);
896 offset_y = std::min(offset_y, kBgSize - kPreviewSize);
900 for (
int y = 0; y < kPreviewSize; ++y) {
901 for (
int x = 0; x < kPreviewSize; ++x) {
902 size_t src_idx = (offset_y + y) * kBgSize + (offset_x + x);
903 int dst_idx = y * kPreviewSize + x;
908 if (src_idx < bg2_data.size() && bg2_data[src_idx] != 0) {
909 preview_data[dst_idx] = bg2_data[src_idx];
912 if (src_idx < bg1_data.size() && bg1_data[src_idx] != 0) {
913 preview_data[dst_idx] = bg1_data[src_idx];
925 std::vector<uint8_t> rgba_data(kPreviewSize * kPreviewSize * 4);
926 for (
int y = 0; y < kPreviewSize; ++y) {
927 for (
int x = 0; x < kPreviewSize; ++x) {
928 size_t idx = y * kPreviewSize + x;
929 uint8_t color_idx = preview_data[idx];
931 if (color_idx == kTransparentMarker) {
933 rgba_data[idx * 4 + 0] = 32;
934 rgba_data[idx * 4 + 1] = 32;
935 rgba_data[idx * 4 + 2] = 48;
936 rgba_data[idx * 4 + 3] = 255;
937 }
else if (color_idx < palette.
size()) {
939 auto color = palette[color_idx];
940 rgba_data[idx * 4 + 0] = color.rgb().x;
941 rgba_data[idx * 4 + 1] = color.rgb().y;
942 rgba_data[idx * 4 + 2] = color.rgb().z;
943 rgba_data[idx * 4 + 3] = 255;
947 rgba_data[idx * 4 + 0] = 255;
948 rgba_data[idx * 4 + 1] = 0;
949 rgba_data[idx * 4 + 2] = 255;
950 rgba_data[idx * 4 + 3] = 255;
955 void* pixels =
nullptr;
958 memcpy(pixels, rgba_data.data(), rgba_data.size());
964 printf(
"[STATIC] Render complete\n");