203 ImGui::TextColored(theme.text_info,
"Object Selection");
208 ImGuiInputTextFlags_CharsHexadecimal);
210 ImGui::TextColored(theme.text_secondary_gray,
"($%03X)",
object_id_);
216 ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.panel_bg_darker);
217 ImGui::BeginChild(
"ObjectInfo", ImVec2(0, 60),
true);
218 ImGui::TextColored(theme.accent_color,
"Name:");
220 ImGui::TextWrapped(
"%s", name);
221 ImGui::TextColored(theme.accent_color,
"Type:");
223 ImGui::Text(
"%d", type);
225 ImGui::PopStyleColor();
230 if (ImGui::BeginCombo(
"Quick Select",
"Choose preset...")) {
232 if (ImGui::Selectable(preset.name,
object_id_ == preset.id)) {
236 ImGui::SetItemDefaultFocus();
254 ImGui::TextColored(theme.text_info,
"Position & Size");
261 ImGui::TextDisabled(
"(?)");
262 if (ImGui::IsItemHovered()) {
264 "Size parameter for scalable objects.\nMany objects ignore this value.");
271 ImGui::TextColored(theme.text_info,
"Rendering Context");
276 ImGui::TextDisabled(
"(?)");
277 if (ImGui::IsItemHovered()) {
278 ImGui::SetTooltip(
"Room ID for graphics and palette context");
284 ImGui::TextColored(theme.text_info,
"Render Mode");
286 if (ImGui::RadioButton(
"Static (ObjectDrawer)", &mode, 0)) {
291 ImGui::TextDisabled(
"(?)");
292 if (ImGui::IsItemHovered()) {
294 "Uses ObjectDrawer to render objects.\n"
295 "This is the reliable method that matches the main canvas.");
297 if (ImGui::RadioButton(
"Emulator (Experimental)", &mode, 1)) {
301 ImGui::TextDisabled(
"(?)");
302 if (ImGui::IsItemHovered()) {
304 "Attempts to run game drawing handlers via CPU emulation.\n"
305 "EXPERIMENTAL: Handlers require full game state to work.\n"
306 "Most objects will time out without rendering.");
349 if (result.ok() && result->success) {
355 void* pixels =
nullptr;
358 memcpy(pixels, result->rgba_pixels.data(), result->rgba_pixels.size());
361 printf(
"[SERVICE-EMU] Rendered object $%04X via EmulatorRenderService\n",
365 printf(
"[SERVICE-EMU] Emulated render failed, falling back to legacy: %s\n",
366 result.ok() ? result->error.c_str()
367 : std::string(result.status().message()).c_str());
375 last_error_ =
"Failed to initialize SNES emulator";
400 int palette_id = default_room.
palette();
401 if (palette_id < 0 ||
402 palette_id >=
static_cast<int>(dungeon_main_pal_group.size())) {
403 printf(
"[EMU] Warning: Room palette %d out of bounds, using palette 0\n",
410 auto base_palette = dungeon_main_pal_group[palette_id];
411 std::optional<gfx::SnesPalette> hud_palette_storage;
415 hud_palette = &*hud_palette_storage;
422 constexpr uint32_t kSpriteAuxPaletteSnes = 0x0DD308;
423 const uint32_t kSpriteAuxPalettePc =
SnesToPc(kSpriteAuxPaletteSnes);
424 for (
int i = 0; i < 30; ++i) {
425 uint32_t addr = kSpriteAuxPalettePc + i * 2;
431 printf(
"[EMU] Loaded full palette: 90 dungeon + 30 sprite aux = 120 colors\n");
440 std::vector<uint8_t> linear_data(gfx_buffer.begin(), gfx_buffer.end());
441 auto planar_data = ConvertLinear8bppToPlanar4bpp(linear_data);
444 for (
size_t i = 0; i < planar_data.size() / 2 && i < 0x8000; ++i) {
445 ppu.vram[i] = planar_data[i * 2] | (planar_data[i * 2 + 1] << 8);
448 printf(
"[EMU] Converted %zu bytes (8BPP linear) to %zu bytes (4BPP planar)\n",
449 gfx_buffer.size(), planar_data.size());
453 for (uint32_t i = 0; i < 0x2000; i++) {
467 constexpr uint8_t kPointerZeroPageAddrs[] = {0xBF, 0xC2, 0xC5, 0xC8, 0xCB,
468 0xCE, 0xD1, 0xD4, 0xD7, 0xDA,
473 constexpr uint32_t kBG1TilemapBase = 0x7E2000;
474 constexpr uint32_t kRowStride = 0x80;
476 for (
int i = 0; i < 11; ++i) {
477 uint32_t wram_addr = kBG1TilemapBase + (i * kRowStride);
478 uint8_t lo = wram_addr & 0xFF;
479 uint8_t mid = (wram_addr >> 8) & 0xFF;
480 uint8_t hi = (wram_addr >> 16) & 0xFF;
482 uint8_t zp_addr = kPointerZeroPageAddrs[i];
488 printf(
"[EMU] Tilemap ptr $%02X = $%06X\n", zp_addr, wram_addr);
507 apu.out_ports_[0] = 0xAA;
508 apu.out_ports_[1] = 0xBB;
509 apu.out_ports_[2] = 0x00;
510 apu.out_ports_[3] = 0x00;
511 printf(
"[EMU] APU mock: out_ports_[0]=$AA, out_ports_[1]=$BB (SPC→CPU)\n");
530 printf(
"[EMU] Object params: type=%d, y_offset=$%04X, size=%d\n",
537 const uint32_t object_data_addr = 0x7E1000;
554 uint32_t data_table_snes = 0;
555 uint32_t handler_table_snes = 0;
559 data_table_snes = 0x018000 + (
object_id_ * 2);
560 handler_table_snes = 0x018200 + (
object_id_ * 2);
563 data_table_snes = 0x018370 + ((
object_id_ - 0x100) * 2);
564 handler_table_snes = 0x018470 + ((
object_id_ - 0x100) * 2);
567 data_table_snes = 0x0184F0 + ((
object_id_ - 0x200) * 2);
568 handler_table_snes = 0x0185F0 + ((
object_id_ - 0x200) * 2);
572 uint32_t data_table_pc =
SnesToPc(data_table_snes);
573 uint32_t handler_table_pc =
SnesToPc(handler_table_snes);
575 uint16_t data_offset = 0;
576 uint16_t handler_addr = 0;
578 if (data_table_pc + 1 <
rom_->
size() && handler_table_pc + 1 <
rom_->
size()) {
579 data_offset = rom_data[data_table_pc] | (rom_data[data_table_pc + 1] << 8);
580 handler_addr = rom_data[handler_table_pc] | (rom_data[handler_table_pc + 1] << 8);
582 last_error_ =
"Object ID out of bounds for handler lookup";
586 if (handler_addr == 0x0000) {
588 snprintf(buf,
sizeof(buf),
"Object $%04X has no drawing routine",
594 printf(
"[EMU] Two-table lookup (PC: $%04X, $%04X): data_offset=$%04X, handler=$%04X\n",
595 data_table_pc, handler_table_pc, data_offset, handler_addr);
613 const uint16_t trap_addr = 0xFF00;
618 uint16_t sp = cpu.SP();
625 cpu.PC = handler_addr;
627 printf(
"[EMU] Rendering object $%04X at (%d,%d), handler=$%04X\n",
object_id_,
629 printf(
"[EMU] X=data_offset=$%04X, Y=tilemap_pos=$%04X, PB:PC=$%02X:%04X\n",
630 cpu.X, cpu.Y, cpu.PB, cpu.PC);
631 printf(
"[EMU] STP trap at $01:%04X for return detection\n", trap_addr);
635 int max_opcodes = 100000;
637 while (opcodes < max_opcodes) {
639 uint32_t current_addr = (cpu.PB << 16) | cpu.PC;
641 if (current_opcode == 0xDB) {
642 printf(
"[EMU] STP trap hit at $%02X:%04X - handler completed!\n",
650 if ((opcodes & 0x3F) == 0) {
651 apu.out_ports_[0] = 0xAA;
652 apu.out_ports_[1] = 0xBB;
657 if (cpu.PB == 0x00 && cpu.PC == 0x8891) {
660 static int apu_loop_count = 0;
661 if (++apu_loop_count > 100) {
662 printf(
"[EMU] WARNING: Stuck in APU loop at $00:8891, forcing skip\n");
673 if (opcodes == 10000) {
674 printf(
"[EMU] WRAM $7E2000 after 10k opcodes: ");
675 for (
int i = 0; i < 8; i++) {
685 printf(
"[EMU] Completed after %d opcodes, PC=$%02X:%04X\n", opcodes, cpu.PB,
688 if (opcodes >= max_opcodes) {
691 printf(
"[EMU] WRAM BG1 tilemap sample at $7E2000:\n");
692 for (
int i = 0; i < 16; i++) {
706 for (uint32_t i = 0; i < 0x800; i++) {
709 ppu.vram[0x4000 + i] = lo | (hi << 8);
712 for (uint32_t i = 0; i < 0x800; i++) {
715 ppu.vram[0x4800 + i] = lo | (hi << 8);
719 printf(
"[EMU] VRAM tilemap at $4000 (BG1): ");
720 for (
int i = 0; i < 8; i++) {
721 printf(
"%04X ", ppu.vram[0x4000 + i]);
726 ppu.HandleFrameStart();
727 for (
int line = 0; line < 224; line++) {
733 void* pixels =
nullptr;
762 if (result.ok() && result->success) {
767 void* pixels =
nullptr;
771 memcpy(pixels, result->rgba_pixels.data(), result->rgba_pixels.size());
774 printf(
"[SERVICE] Rendered object $%04X via EmulatorRenderService\n",
779 printf(
"[SERVICE] Render failed, falling back to legacy: %s\n",
780 result.ok() ? result->error.c_str()
781 : std::string(result.status().message()).c_str());
796 int palette_id = room.
palette();
797 if (palette_id < 0 ||
798 palette_id >=
static_cast<int>(dungeon_main_pal_group.size())) {
801 auto base_palette = dungeon_main_pal_group[palette_id];
809 for (
size_t i = 0; i < base_palette.size() && i < 90; ++i) {
813 while (palette.
size() < 90) {
820 constexpr uint32_t kSpriteAuxPaletteSnes = 0x0DD308;
821 const uint32_t kSpriteAuxPalettePc =
SnesToPc(kSpriteAuxPaletteSnes);
822 for (
int i = 0; i < 30; ++i) {
823 uint32_t addr = kSpriteAuxPalettePc + i * 2;
848 constexpr int kBgSize = 512;
850 std::vector<uint8_t>(kBgSize * kBgSize, 0));
852 std::vector<uint8_t>(kBgSize * kBgSize, 0));
863 preview_palette_group);
866 printf(
"[STATIC] DrawObject failed: %s\n",
last_error_.c_str());
870 printf(
"[STATIC] Drew object $%04X at (%d,%d) size=%d\n",
object_id_,
879 constexpr int kPreviewSize = 256;
880 constexpr uint8_t kTransparentMarker = 0xFF;
883 kPreviewSize, kPreviewSize, 8,
884 std::vector<uint8_t>(kPreviewSize * kPreviewSize, kTransparentMarker));
894 const auto& bg1_data = bg1_bitmap.vector();
895 const auto& bg2_data = bg2_bitmap.vector();
898 int offset_x = std::max(0, (
object_x_ * 8) - kPreviewSize / 2);
899 int offset_y = std::max(0, (
object_y_ * 8) - kPreviewSize / 2);
902 offset_x = std::min(offset_x, kBgSize - kPreviewSize);
903 offset_y = std::min(offset_y, kBgSize - kPreviewSize);
907 for (
int y = 0; y < kPreviewSize; ++y) {
908 for (
int x = 0; x < kPreviewSize; ++x) {
909 size_t src_idx = (offset_y + y) * kBgSize + (offset_x + x);
910 int dst_idx = y * kPreviewSize + x;
915 if (src_idx < bg2_data.size() && bg2_data[src_idx] != 0) {
916 preview_data[dst_idx] = bg2_data[src_idx];
919 if (src_idx < bg1_data.size() && bg1_data[src_idx] != 0) {
920 preview_data[dst_idx] = bg1_data[src_idx];
932 std::vector<uint8_t> rgba_data(kPreviewSize * kPreviewSize * 4);
933 for (
int y = 0; y < kPreviewSize; ++y) {
934 for (
int x = 0; x < kPreviewSize; ++x) {
935 size_t idx = y * kPreviewSize + x;
936 uint8_t color_idx = preview_data[idx];
938 if (color_idx == kTransparentMarker) {
940 rgba_data[idx * 4 + 0] = 32;
941 rgba_data[idx * 4 + 1] = 32;
942 rgba_data[idx * 4 + 2] = 48;
943 rgba_data[idx * 4 + 3] = 255;
944 }
else if (color_idx < palette.
size()) {
946 auto color = palette[color_idx];
947 rgba_data[idx * 4 + 0] = color.rgb().x;
948 rgba_data[idx * 4 + 1] = color.rgb().y;
949 rgba_data[idx * 4 + 2] = color.rgb().z;
950 rgba_data[idx * 4 + 3] = 255;
954 rgba_data[idx * 4 + 0] = 255;
955 rgba_data[idx * 4 + 1] = 0;
956 rgba_data[idx * 4 + 2] = 255;
957 rgba_data[idx * 4 + 3] = 255;
962 void* pixels =
nullptr;
965 memcpy(pixels, rgba_data.data(), rgba_data.size());
971 printf(
"[STATIC] Render complete\n");