208 ImGui::TextColored(theme.text_info, tr(
"Object Selection"));
213 ImGuiInputTextFlags_CharsHexadecimal);
215 ImGui::TextColored(theme.text_secondary_gray,
"($%03X)",
object_id_);
221 ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.panel_bg_darker);
222 ImGui::BeginChild(
"ObjectInfo", ImVec2(0, 60),
true);
223 ImGui::TextColored(theme.accent_color, tr(
"Name:"));
225 ImGui::TextWrapped(
"%s", name);
226 ImGui::TextColored(theme.accent_color, tr(
"Type:"));
228 ImGui::Text(
"%d", type);
230 ImGui::PopStyleColor();
235 if (ImGui::BeginCombo(tr(
"Quick Select"),
"Choose preset...")) {
237 if (ImGui::Selectable(preset.name,
object_id_ == preset.id)) {
241 ImGui::SetItemDefaultFocus();
259 ImGui::TextColored(theme.text_info, tr(
"Position & Size"));
266 ImGui::TextDisabled(
"(?)");
267 if (ImGui::IsItemHovered()) {
269 tr(
"Size parameter for scalable objects.\nMany objects ignore this "
277 ImGui::TextColored(theme.text_info, tr(
"Rendering Context"));
282 ImGui::TextDisabled(
"(?)");
283 if (ImGui::IsItemHovered()) {
284 ImGui::SetTooltip(tr(
"Room ID for graphics and palette context"));
290 ImGui::TextColored(theme.text_info, tr(
"Render Mode"));
292 if (ImGui::RadioButton(tr(
"Static (ObjectDrawer)"), &mode, 0)) {
297 ImGui::TextDisabled(
"(?)");
298 if (ImGui::IsItemHovered()) {
300 tr(
"Uses ObjectDrawer to render objects.\n"
301 "This is the reliable method that matches the main canvas."));
303 if (ImGui::RadioButton(tr(
"Emulator (Experimental)"), &mode, 1)) {
307 ImGui::TextDisabled(
"(?)");
308 if (ImGui::IsItemHovered()) {
310 tr(
"Attempts to run game drawing handlers via CPU emulation.\n"
311 "EXPERIMENTAL: Handlers require full game state to work.\n"
312 "Most objects will time out without rendering."));
355 if (result.ok() && result->success) {
361 void* pixels =
nullptr;
364 memcpy(pixels, result->rgba_pixels.data(), result->rgba_pixels.size());
367 printf(
"[SERVICE-EMU] Rendered object $%04X via EmulatorRenderService\n",
372 "[SERVICE-EMU] Emulated render failed, falling back to legacy: %s\n",
373 result.ok() ? result->error.c_str()
374 : std::string(result.status().message()).c_str());
382 last_error_ =
"Failed to initialize SNES emulator";
407 int palette_id = default_room.
palette();
408 if (palette_id < 0 ||
409 palette_id >=
static_cast<int>(dungeon_main_pal_group.size())) {
410 printf(
"[EMU] Warning: Room palette %d out of bounds, using palette 0\n",
417 auto base_palette = dungeon_main_pal_group[palette_id];
418 std::optional<gfx::SnesPalette> hud_palette_storage;
422 hud_palette = &*hud_palette_storage;
428 constexpr uint32_t kSpriteAuxPaletteSnes =
430 const uint32_t kSpriteAuxPalettePc =
432 for (
int i = 0; i < 30; ++i) {
433 uint32_t addr = kSpriteAuxPalettePc + i * 2;
440 "[EMU] Loaded full palette: 90 dungeon + 30 sprite aux = 120 colors\n");
449 std::vector<uint8_t> linear_data(gfx_buffer.begin(), gfx_buffer.end());
450 auto planar_data = ConvertLinear8bppToPlanar4bpp(linear_data);
453 for (
size_t i = 0; i < planar_data.size() / 2 && i < 0x8000; ++i) {
454 ppu.vram[i] = planar_data[i * 2] | (planar_data[i * 2 + 1] << 8);
457 printf(
"[EMU] Converted %zu bytes (8BPP linear) to %zu bytes (4BPP planar)\n",
458 gfx_buffer.size(), planar_data.size());
462 for (uint32_t i = 0; i < 0x2000; i++) {
476 constexpr uint8_t kPointerZeroPageAddrs[] = {
477 0xBF, 0xC2, 0xC5, 0xC8, 0xCB, 0xCE, 0xD1, 0xD4, 0xD7, 0xDA, 0xDD};
481 constexpr uint32_t kBG1TilemapBase = 0x7E2000;
482 constexpr uint32_t kRowStride = 0x80;
484 for (
int i = 0; i < 11; ++i) {
485 uint32_t wram_addr = kBG1TilemapBase + (i * kRowStride);
486 uint8_t lo = wram_addr & 0xFF;
487 uint8_t mid = (wram_addr >> 8) & 0xFF;
488 uint8_t hi = (wram_addr >> 16) & 0xFF;
490 uint8_t zp_addr = kPointerZeroPageAddrs[i];
496 printf(
"[EMU] Tilemap ptr $%02X = $%06X\n", zp_addr, wram_addr);
515 apu.out_ports_[0] = 0xAA;
516 apu.out_ports_[1] = 0xBB;
517 apu.out_ports_[2] = 0x00;
518 apu.out_ports_[3] = 0x00;
519 printf(
"[EMU] APU mock: out_ports_[0]=$AA, out_ports_[1]=$BB (SPC→CPU)\n");
538 printf(
"[EMU] Object params: type=%d, y_offset=$%04X, size=%d\n",
545 const uint32_t object_data_addr = 0x7E1000;
562 uint32_t data_table_snes = 0;
563 uint32_t handler_table_snes = 0;
567 data_table_snes = 0x018000 + (
object_id_ * 2);
568 handler_table_snes = 0x018200 + (
object_id_ * 2);
571 data_table_snes = 0x018370 + ((
object_id_ - 0x100) * 2);
572 handler_table_snes = 0x018470 + ((
object_id_ - 0x100) * 2);
575 data_table_snes = 0x0184F0 + ((
object_id_ - 0x200) * 2);
576 handler_table_snes = 0x0185F0 + ((
object_id_ - 0x200) * 2);
580 uint32_t data_table_pc =
SnesToPc(data_table_snes);
581 uint32_t handler_table_pc =
SnesToPc(handler_table_snes);
583 uint16_t data_offset = 0;
584 uint16_t handler_addr = 0;
586 if (data_table_pc + 1 <
rom_->
size() && handler_table_pc + 1 <
rom_->
size()) {
587 data_offset = rom_data[data_table_pc] | (rom_data[data_table_pc + 1] << 8);
589 rom_data[handler_table_pc] | (rom_data[handler_table_pc + 1] << 8);
591 last_error_ =
"Object ID out of bounds for handler lookup";
595 if (handler_addr == 0x0000) {
597 snprintf(buf,
sizeof(buf),
"Object $%04X has no drawing routine",
604 "[EMU] Two-table lookup (PC: $%04X, $%04X): data_offset=$%04X, "
606 data_table_pc, handler_table_pc, data_offset, handler_addr);
624 const uint16_t trap_addr = 0xFF00;
629 uint16_t sp = cpu.SP();
636 cpu.PC = handler_addr;
638 printf(
"[EMU] Rendering object $%04X at (%d,%d), handler=$%04X\n",
object_id_,
640 printf(
"[EMU] X=data_offset=$%04X, Y=tilemap_pos=$%04X, PB:PC=$%02X:%04X\n",
641 cpu.X, cpu.Y, cpu.PB, cpu.PC);
642 printf(
"[EMU] STP trap at $01:%04X for return detection\n", trap_addr);
646 int max_opcodes = 100000;
648 while (opcodes < max_opcodes) {
650 uint32_t current_addr = (cpu.PB << 16) | cpu.PC;
652 if (current_opcode == 0xDB) {
653 printf(
"[EMU] STP trap hit at $%02X:%04X - handler completed!\n", cpu.PB,
661 if ((opcodes & 0x3F) == 0) {
662 apu.out_ports_[0] = 0xAA;
663 apu.out_ports_[1] = 0xBB;
668 if (cpu.PB == 0x00 && cpu.PC == 0x8891) {
671 static int apu_loop_count = 0;
672 if (++apu_loop_count > 100) {
673 printf(
"[EMU] WARNING: Stuck in APU loop at $00:8891, forcing skip\n");
684 if (opcodes == 10000) {
685 printf(
"[EMU] WRAM $7E2000 after 10k opcodes: ");
686 for (
int i = 0; i < 8; i++) {
696 printf(
"[EMU] Completed after %d opcodes, PC=$%02X:%04X\n", opcodes, cpu.PB,
699 if (opcodes >= max_opcodes) {
702 printf(
"[EMU] WRAM BG1 tilemap sample at $7E2000:\n");
703 for (
int i = 0; i < 16; i++) {
717 for (uint32_t i = 0; i < 0x800; i++) {
720 ppu.vram[0x4000 + i] = lo | (hi << 8);
723 for (uint32_t i = 0; i < 0x800; i++) {
726 ppu.vram[0x4800 + i] = lo | (hi << 8);
730 printf(
"[EMU] VRAM tilemap at $4000 (BG1): ");
731 for (
int i = 0; i < 8; i++) {
732 printf(
"%04X ", ppu.vram[0x4000 + i]);
737 ppu.HandleFrameStart();
738 for (
int line = 0; line < 224; line++) {
744 void* pixels =
nullptr;
773 if (result.ok() && result->success) {
778 void* pixels =
nullptr;
782 memcpy(pixels, result->rgba_pixels.data(), result->rgba_pixels.size());
785 printf(
"[SERVICE] Rendered object $%04X via EmulatorRenderService\n",
790 printf(
"[SERVICE] Render failed, falling back to legacy: %s\n",
791 result.ok() ? result->error.c_str()
792 : std::string(result.status().message()).c_str());
807 int palette_id = room.
palette();
808 if (palette_id < 0 ||
809 palette_id >=
static_cast<int>(dungeon_main_pal_group.size())) {
812 auto base_palette = dungeon_main_pal_group[palette_id];
820 for (
size_t i = 0; i < base_palette.size() && i < 90; ++i) {
824 while (palette.
size() < 90) {
831 constexpr uint32_t kSpriteAuxPaletteSnes =
833 const uint32_t kSpriteAuxPalettePc =
835 for (
int i = 0; i < 30; ++i) {
836 uint32_t addr = kSpriteAuxPalettePc + i * 2;
861 constexpr int kBgSize = 512;
863 std::vector<uint8_t>(kBgSize * kBgSize, 0));
865 std::vector<uint8_t>(kBgSize * kBgSize, 0));
876 preview_palette_group);
879 printf(
"[STATIC] DrawObject failed: %s\n",
last_error_.c_str());
883 printf(
"[STATIC] Drew object $%04X at (%d,%d) size=%d\n",
object_id_,
892 constexpr int kPreviewSize = 256;
893 constexpr uint8_t kTransparentMarker = 0xFF;
896 kPreviewSize, kPreviewSize, 8,
897 std::vector<uint8_t>(kPreviewSize * kPreviewSize, kTransparentMarker));
907 const auto& bg1_data = bg1_bitmap.vector();
908 const auto& bg2_data = bg2_bitmap.vector();
911 int offset_x = std::max(0, (
object_x_ * 8) - kPreviewSize / 2);
912 int offset_y = std::max(0, (
object_y_ * 8) - kPreviewSize / 2);
915 offset_x = std::min(offset_x, kBgSize - kPreviewSize);
916 offset_y = std::min(offset_y, kBgSize - kPreviewSize);
920 for (
int y = 0; y < kPreviewSize; ++y) {
921 for (
int x = 0; x < kPreviewSize; ++x) {
922 size_t src_idx = (offset_y + y) * kBgSize + (offset_x + x);
923 int dst_idx = y * kPreviewSize + x;
928 if (src_idx < bg2_data.size() && bg2_data[src_idx] != 0) {
929 preview_data[dst_idx] = bg2_data[src_idx];
932 if (src_idx < bg1_data.size() && bg1_data[src_idx] != 0) {
933 preview_data[dst_idx] = bg1_data[src_idx];
945 std::vector<uint8_t> rgba_data(kPreviewSize * kPreviewSize * 4);
946 for (
int y = 0; y < kPreviewSize; ++y) {
947 for (
int x = 0; x < kPreviewSize; ++x) {
948 size_t idx = y * kPreviewSize + x;
949 uint8_t color_idx = preview_data[idx];
951 if (color_idx == kTransparentMarker) {
953 rgba_data[idx * 4 + 0] = 32;
954 rgba_data[idx * 4 + 1] = 32;
955 rgba_data[idx * 4 + 2] = 48;
956 rgba_data[idx * 4 + 3] = 255;
957 }
else if (color_idx < palette.
size()) {
959 auto color = palette[color_idx];
960 rgba_data[idx * 4 + 0] = color.rgb().x;
961 rgba_data[idx * 4 + 1] = color.rgb().y;
962 rgba_data[idx * 4 + 2] = color.rgb().z;
963 rgba_data[idx * 4 + 3] = 255;
967 rgba_data[idx * 4 + 0] = 255;
968 rgba_data[idx * 4 + 1] = 0;
969 rgba_data[idx * 4 + 2] = 255;
970 rgba_data[idx * 4 + 3] = 255;
975 void* pixels =
nullptr;
978 memcpy(pixels, rgba_data.data(), rgba_data.size());
984 printf(
"[STATIC] Render complete\n");