8#include "absl/strings/str_format.h"
25#include "imgui/imgui.h"
38constexpr int kNativeSampleRate = 32040;
94 wasm_backend->HandleUserInteraction();
103 int safe_type = std::clamp(type, 0, 4);
104 snes_.
apu().dsp().interpolation_type =
111 return static_cast<int>(
snes_.
apu().dsp().interpolation_type);
115 const std::vector<uint8_t>& rom_data) {
121 const char* env_value = std::getenv(
"YAZE_USE_SDL_AUDIO_STREAM");
122 if (env_value && std::atoi(env_value) != 0) {
154 "Failed to initialize audio backend; falling back to Null");
158 LOG_ERROR(
"Emulator",
"Failed to initialize Null audio backend");
162 LOG_INFO(
"Emulator",
"Audio backend initialized: %s",
169 snes_.
cpu().on_breakpoint_hit_ = [
this](uint32_t pc) ->
bool {
175 snes_.
cpu().on_instruction_executed_ =
176 [
this](uint32_t address, uint8_t opcode,
177 const std::vector<uint8_t>& operands,
const std::string& mnemonic,
178 const std::string& operand_str) {
180 mnemonic, operand_str);
194 LOG_INFO(
"Emulator",
"Using external (shared) audio backend");
210 if (!backend->Initialize(config)) {
212 "Failed to initialize audio backend; falling back to Null");
215 if (!backend->Initialize(config)) {
216 LOG_ERROR(
"Emulator",
"Failed to initialize Null audio backend");
223 LOG_INFO(
"Emulator",
"Audio backend initialized for headless mode: %s",
235 const double frame_rate =
236 snes_.
memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
242 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
249 LOG_INFO(
"Emulator",
"SNES initialized for headless mode");
255 const double frame_rate =
256 snes_.
memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
259 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
266 if (rom_data.empty()) {
267 return absl::InvalidArgumentError(
"runtime ROM data is empty");
274 const double frame_rate =
275 snes_.
memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
278 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
293 return absl::OkStatus();
311 audio_backend_->SetAudioStreamResampling(
true, kNativeSampleRate, 2);
314 audio_backend_->SetAudioStreamResampling(
false, kNativeSampleRate, 2);
321 uint64_t current_count = SDL_GetPerformanceCounter();
335 int frames_processed = 0;
336 constexpr int kMaxFramesPerUpdate = 2;
339 static int16_t native_audio_buffer[2048];
342 frames_processed < kMaxFramesPerUpdate) {
355 const uint32_t max_buffer =
static_cast<uint32_t
>(
wanted_samples_ * 6);
357 if (status.queued_frames < max_buffer) {
363 static int log_counter = 0;
364 if (++log_counter % 60 == 0) {
367 "Resampling failed (Native=%d, Backend=%d) - Dropping "
368 "audio to prevent speedup/pitch shift",
369 kNativeSampleRate, backend_rate);
398 static int entry_count = 0;
399 if (entry_count < 5 || entry_count % 300 == 0) {
401 "RunAudioFrame ENTRY #%d: init=%d, running=%d, backend=%p "
402 "(external=%p, owned=%p)",
404 static_cast<void*
>(backend),
411 static int skip_count = 0;
412 if (skip_count < 5) {
413 LOG_WARN(
"Emulator",
"RunAudioFrame SKIPPED: init=%d, running=%d",
423 backend->SetAudioStreamResampling(
true, kNativeSampleRate, 2);
435 static int16_t audio_buffer[2048];
438 bool queued = backend->QueueSamplesNative(audio_buffer,
wanted_samples_, 2,
442 static int frame_log_count = 0;
443 if (frame_log_count < 5 || frame_log_count % 300 == 0) {
444 LOG_INFO(
"Emulator",
"RunAudioFrame: wanted=%d, queued=%s, stream=%s",
450 if (!queued && backend->SupportsAudioStream()) {
453 "RunAudioFrame: First queue failed, re-enabling resampling");
454 backend->SetAudioStreamResampling(
true, kNativeSampleRate, 2);
458 LOG_INFO(
"Emulator",
"RunAudioFrame: Retry queued=%s",
459 queued ?
"YES" :
"NO");
464 "RunAudioFrame: AUDIO DROPPED - resampling not working!");
471 const char* env_value = std::getenv(
"YAZE_USE_SDL_AUDIO_STREAM");
472 if (env_value && std::atoi(env_value) != 0) {
480 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
481 "Emulator renderer not initialized");
504 LOG_ERROR(
"Emulator",
"Failed to initialize audio backend");
506 LOG_INFO(
"Emulator",
"Audio backend initialized (lazy): %s",
517 LOG_ERROR(
"Emulator",
"Failed to initialize input manager");
520 LOG_INFO(
"Emulator",
"Input manager initialized: %s",
534 512, 480, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING);
536 printf(
"Failed to create PPU texture: %s\n", SDL_GetError());
555 const double frame_rate =
556 snes_.
memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
561 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
580 static bool was_running_before_resize =
false;
584 was_running_before_resize =
true;
587 was_running_before_resize) {
590 was_running_before_resize =
false;
602 uint64_t current_count = SDL_GetPerformanceCounter();
614 int frames_to_process = 0;
625 if (frames_to_process > max_frames) {
629 frames_to_process = max_frames;
634 constexpr int kTurboFrames = 8;
635 for (
int i = 0; i < kTurboFrames; i++) {
644 frames_to_process = 1;
650 for (
int i = 0; i < frames_to_process; i++) {
652 uint64_t frame_start = SDL_GetPerformanceCounter();
653 bool should_render = (i == frames_to_process - 1);
654 uint32_t queued_frames = 0;
655 float audio_rms_left = 0.0f;
656 float audio_rms_right = 0.0f;
671 int16_t temp_audio_buffer[2048];
672 int16_t* frame_buffer =
680 "Enabling audio stream resampling (32040Hz -> Device Rate)");
685 LOG_INFO(
"Emulator",
"Disabling audio stream resampling");
686 audio_backend_->SetAudioStreamResampling(
false, kNativeSampleRate,
694 queued_frames = audio_status.queued_frames;
697 const uint32_t max_buffer = samples_per_frame * 6;
698 const uint32_t optimal_buffer = 2048;
700 if (queued_frames < max_buffer) {
707 auto compute_rms = [&](
int total_samples) {
708 if (total_samples <= 0 || frame_buffer ==
nullptr) {
709 audio_rms_left = 0.0f;
710 audio_rms_right = 0.0f;
715 const int frames = total_samples / 2;
716 for (
int s = 0; s < frames; ++s) {
717 const float l =
static_cast<float>(frame_buffer[2 * s]);
718 const float r =
static_cast<float>(frame_buffer[2 * s + 1]);
723 frames > 0 ? std::sqrt(sum_l / frames) / 32768.0f : 0.0f;
725 frames > 0 ? std::sqrt(sum_r / frames) / 32768.0f : 0.0f;
727 compute_rms(num_samples);
731 int effective_rate = kNativeSampleRate;
732 if (queued_frames > optimal_buffer + 256) {
733 effective_rate += 60;
734 }
else if (queued_frames < optimal_buffer - 256) {
735 effective_rate -= 60;
752 static int error_count = 0;
753 if (++error_count % 300 == 0) {
756 "Resampling failed, dropping audio to prevent 1.5x speed "
763 static int overflow_count = 0;
764 if (++overflow_count % 60 == 0) {
766 "Audio buffer overflow (count: %d, queued: %u)",
767 overflow_count, queued_frames);
785 const uint64_t frame_end = SDL_GetPerformanceCounter();
786 const double elapsed_ms =
787 1000.0 * (
static_cast<double>(frame_end - frame_start) /
791 audio_rms_left, audio_rms_right);
802#ifndef __EMSCRIPTEN__
820 uint64_t dma_bytes, uint64_t vram_bytes,
821 float audio_rms_left,
float audio_rms_right) {
837 const std::array<float, Emulator::kMetricHistorySize>& data,
int head,
839 std::vector<float> out;
843 for (
int i = 0; i < count; ++i) {
845 out.push_back(data[idx]);
887 constexpr size_t kBankSize = 0x8000;
891 const size_t bank_count =
rom_data_.size() / kBankSize;
892 std::vector<float> free_bytes;
893 free_bytes.reserve(bank_count);
894 for (
size_t bank = 0; bank < bank_count; ++bank) {
895 size_t free_count = 0;
896 const size_t base = bank * kBankSize;
897 for (
size_t i = 0; i < kBankSize && (base + i) <
rom_data_.size(); ++i) {
902 free_bytes.push_back(
static_cast<float>(free_count));
934 if (cpu_visible && *cpu_visible) {
935 if (cpu_card.
Begin(cpu_visible)) {
943 if (ppu_visible && *ppu_visible) {
944 if (ppu_card.
Begin(ppu_visible)) {
951 bool* memory_visible =
953 if (memory_visible && *memory_visible) {
954 if (memory_card.
Begin(memory_visible)) {
960 bool* breakpoints_visible =
962 if (breakpoints_visible && *breakpoints_visible) {
963 if (breakpoints_card.
Begin(breakpoints_visible)) {
966 breakpoints_card.
End();
969 bool* performance_visible =
971 if (performance_visible && *performance_visible) {
972 if (performance_card.
Begin(performance_visible)) {
975 performance_card.
End();
978 bool* ai_agent_visible =
980 if (ai_agent_visible && *ai_agent_visible) {
981 if (ai_card.
Begin(ai_agent_visible)) {
987 bool* save_states_visible =
989 if (save_states_visible && *save_states_visible) {
990 if (save_states_card.
Begin(save_states_visible)) {
993 save_states_card.
End();
996 bool* keyboard_config_visible =
998 if (keyboard_config_visible && *keyboard_config_visible) {
999 if (keyboard_card.
Begin(keyboard_config_visible)) {
1002 keyboard_card.
End();
1008 bool* virtual_controller_visible =
1010 if (virtual_controller_visible && *virtual_controller_visible) {
1011 if (controller_card.
Begin(virtual_controller_visible)) {
1014 controller_card.
End();
1017 bool* apu_debugger_visible =
1019 if (apu_debugger_visible && *apu_debugger_visible) {
1020 if (apu_card.
Begin(apu_debugger_visible)) {
1026 bool* audio_mixer_visible =
1028 if (audio_mixer_visible && *audio_mixer_visible) {
1029 if (audio_card.
Begin(audio_mixer_visible)) {
1035 }
catch (
const std::exception& e) {
1037 ImGui::Text(
"Error loading emulator UI: %s", e.what());
1038 if (ImGui::Button(
"Retry")) {
1041 theme_manager.InitializeBuiltInThemes();
1074 const auto& theme = theme_manager.GetCurrentTheme();
1097 static char bp_addr[16] =
"00FFD9";
1099 ImGui::PushItemWidth(100);
1100 ImGui::InputText(
"##BPAddr", bp_addr, IM_ARRAYSIZE(bp_addr),
1101 ImGuiInputTextFlags_CharsHexadecimal |
1102 ImGuiInputTextFlags_CharsUppercase);
1103 ImGui::PopItemWidth();
1106 uint32_t addr = std::strtoul(bp_addr,
nullptr, 16);
1110 absl::StrFormat(
"BP at $%06X", addr));
1114 ImGui::BeginChild(
"##BPList", ImVec2(0, 100),
true);
1117 bool enabled = bp.enabled;
1118 if (ImGui::Checkbox(absl::StrFormat(
"##en%d", bp.id).c_str(),
1123 ImGui::Text(
"$%06X", bp.address);
1125 ImGui::TextDisabled(
"(hits: %d)", bp.hit_count);
1127 if (ImGui::SmallButton(
1137 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"CPU Status");
1140 "##CpuStatus", ImVec2(0, 180),
1141 {.bg = ConvertColorToImVec4(theme.child_bg)},
true);
1144 if (ImGui::BeginTable(
1146 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1147 ImGui::TableSetupColumn(
"Register", ImGuiTableColumnFlags_WidthFixed,
1149 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 80);
1150 ImGui::TableSetupColumn(
"Register", ImGuiTableColumnFlags_WidthFixed,
1152 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 80);
1153 ImGui::TableHeadersRow();
1155 ImGui::TableNextRow();
1156 ImGui::TableNextColumn();
1158 ImGui::TableNextColumn();
1159 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%04X",
1161 ImGui::TableNextColumn();
1163 ImGui::TableNextColumn();
1164 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%04X",
1167 ImGui::TableNextRow();
1168 ImGui::TableNextColumn();
1170 ImGui::TableNextColumn();
1171 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%04X",
1173 ImGui::TableNextColumn();
1175 ImGui::TableNextColumn();
1176 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1179 ImGui::TableNextRow();
1180 ImGui::TableNextColumn();
1182 ImGui::TableNextColumn();
1183 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%04X",
1185 ImGui::TableNextColumn();
1187 ImGui::TableNextColumn();
1188 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1191 ImGui::TableNextRow();
1192 ImGui::TableNextColumn();
1194 ImGui::TableNextColumn();
1195 ImGui::TextColored(ConvertColorToImVec4(theme.success),
"0x%04X",
1197 ImGui::TableNextColumn();
1199 ImGui::TableNextColumn();
1200 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1203 ImGui::TableNextRow();
1204 ImGui::TableNextColumn();
1206 ImGui::TableNextColumn();
1207 ImGui::TextColored(ConvertColorToImVec4(theme.warning),
"0x%02X",
1209 ImGui::TableNextColumn();
1210 ImGui::Text(
"Cycle");
1211 ImGui::TableNextColumn();
1212 ImGui::TextColored(ConvertColorToImVec4(theme.info),
"%llu",
1220 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"SPC700 Status");
1223 "##SpcStatus", ImVec2(0, 150),
1224 {.bg = ConvertColorToImVec4(theme.child_bg)},
true);
1226 if (ImGui::BeginTable(
1228 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1229 ImGui::TableSetupColumn(
"Register", ImGuiTableColumnFlags_WidthFixed,
1231 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 60);
1232 ImGui::TableSetupColumn(
"Register", ImGuiTableColumnFlags_WidthFixed,
1234 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 60);
1235 ImGui::TableHeadersRow();
1237 ImGui::TableNextRow();
1238 ImGui::TableNextColumn();
1240 ImGui::TableNextColumn();
1241 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1243 ImGui::TableNextColumn();
1245 ImGui::TableNextColumn();
1246 ImGui::TextColored(ConvertColorToImVec4(theme.success),
"0x%04X",
1249 ImGui::TableNextRow();
1250 ImGui::TableNextColumn();
1252 ImGui::TableNextColumn();
1253 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1255 ImGui::TableNextColumn();
1257 ImGui::TableNextColumn();
1258 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1261 ImGui::TableNextRow();
1262 ImGui::TableNextColumn();
1264 ImGui::TableNextColumn();
1265 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1267 ImGui::TableNextColumn();
1269 ImGui::TableNextColumn();
1271 ConvertColorToImVec4(theme.warning),
"0x%02X",
1279 if (ImGui::CollapsingHeader(
"Disassembly Viewer",
1280 ImGuiTreeNodeFlags_DefaultOpen)) {
1281 uint32_t current_pc =
1283 auto& disasm =
snes_.
cpu().disassembly_viewer();
1284 if (disasm.IsAvailable()) {
1285 disasm.Render(current_pc,
snes_.
cpu().breakpoints_);
1287 ImGui::TextColored(ConvertColorToImVec4(theme.error),
1288 "Disassembly viewer unavailable.");
1291 }
catch (
const std::exception& e) {
1293 ImGui::Text(
"CPU Debugger Error: %s", e.what());
1308 const std::vector<InstructionEntry>& instruction_log) {
1316 const auto& theme = theme_manager.GetCurrentTheme();
1318 ImGui::TextColored(ConvertColorToImVec4(theme.warning),
1320 ImGui::TextWrapped(
"Save state functionality will be implemented here.");
1345 if (ImGui::SliderFloat(
"Master Volume", &volume, 0.0f, 1.0f,
"%.2f")) {
1350 ImGui::Text(
"Channel Mutes (Debug)");
1354 if (ImGui::BeginTable(
"AudioChannels", 4)) {
1355 for (
int i = 0; i < 8; ++i) {
1356 ImGui::TableNextColumn();
1357 bool mute = dsp.GetChannelMute(i);
1358 std::string label =
"Ch " + std::to_string(i + 1);
1359 if (ImGui::Checkbox(label.c_str(), &mute)) {
1360 dsp.SetChannelMute(i, mute);
1367 if (ImGui::Button(
"Mute All")) {
1368 for (
int i = 0; i < 8; ++i)
1369 dsp.SetChannelMute(i,
true);
1372 if (ImGui::Button(
"Unmute All")) {
1373 for (
int i = 0; i < 8; ++i)
1374 dsp.SetChannelMute(i,
false);
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
const auto & vector() const
bool * GetWindowVisibilityFlag(size_t session_id, const std::string &base_window_id)
bool ShouldBreakOnExecute(uint32_t pc, CpuType cpu)
Check if execution should break at this address.
void RemoveBreakpoint(uint32_t id)
Remove a breakpoint by ID.
void ClearAll()
Clear all breakpoints.
void SetEnabled(uint32_t id, bool enabled)
Enable or disable a breakpoint.
std::vector< Breakpoint > GetAllBreakpoints() const
Get all breakpoints.
uint32_t AddBreakpoint(uint32_t address, Type type, CpuType cpu, const std::string &condition="", const std::string &description="")
Add a new breakpoint.
std::array< float, kMetricHistorySize > audio_queue_history_
gfx::IRenderer * renderer()
void Initialize(gfx::IRenderer *renderer, const std::vector< uint8_t > &rom_data)
void RenderModernCpuDebugger()
std::unique_ptr< audio::IAudioBackend > audio_backend_
std::vector< float > FrameTimeHistory() const
void SetInputConfig(const input::InputConfig &config)
std::array< float, kMetricHistorySize > vram_bytes_history_
int metric_history_count_
bool audio_stream_env_checked_
static constexpr int kMetricHistorySize
void set_use_sdl_audio_stream(bool enabled)
std::vector< float > RomBankFreeBytes() const
std::array< float, kMetricHistorySize > audio_rms_left_history_
std::vector< float > AudioRmsRightHistory() const
void RenderMemoryViewer()
audio::IAudioBackend * external_audio_backend_
bool audio_stream_config_dirty_
std::array< float, kMetricHistorySize > frame_time_history_
void set_interpolation_type(int type)
bool use_sdl_audio_stream_
std::vector< float > VramBytesHistory() const
std::array< float, kMetricHistorySize > fps_history_
std::vector< float > AudioQueueHistory() const
void RenderKeyboardConfig()
std::vector< float > DmaBytesHistory() const
debug::DisassemblyViewer disassembly_viewer_
std::function< void(const input::InputConfig &) input_config_changed_callback_)
void PushFrameMetrics(float frame_ms, uint32_t audio_frames, uint64_t dma_bytes, uint64_t vram_bytes, float audio_rms_left, float audio_rms_right)
std::vector< uint8_t > rom_data_
bool audio_stream_active_
void RenderPerformanceMonitor()
bool EnsureInitialized(Rom *rom)
void RenderAIAgentPanel()
input::InputConfig input_config_
void RenderBreakpointList()
BreakpointManager breakpoint_manager_
editor::WorkspaceWindowManager * window_manager_
std::vector< float > FpsHistory() const
input::InputManager input_manager_
std::array< float, kMetricHistorySize > audio_rms_right_history_
void RenderCpuInstructionLog(const std::vector< InstructionEntry > &instructionLog)
std::vector< float > AudioRmsLeftHistory() const
audio::IAudioBackend * audio_backend()
std::array< float, kMetricHistorySize > dma_bytes_history_
void RenderEmulatorInterface()
absl::Status ReloadRuntimeRom(const std::vector< uint8_t > &rom_data)
gfx::IRenderer * renderer_
int get_interpolation_type() const
auto mutable_cycles() -> uint64_t &
void SetSamples(int16_t *sample_data, int wanted_samples)
void Reset(bool hard=false)
uint64_t vram_bytes_frame() const
void Init(const std::vector< uint8_t > &rom_data)
uint64_t dma_bytes_frame() const
auto memory() -> MemoryImpl &
void SetPixels(uint8_t *pixel_data)
static std::unique_ptr< IAudioBackend > Create(BackendType type)
void Clear()
Clear all recorded instructions.
void RecordInstruction(uint32_t address, uint8_t opcode, const std::vector< uint8_t > &operands, const std::string &mnemonic, const std::string &operand_str)
Record an instruction execution.
Defines an abstract interface for all rendering operations.
virtual void UnlockTexture(TextureHandle texture)=0
virtual bool LockTexture(TextureHandle texture, SDL_Rect *rect, void **pixels, int *pitch)=0
virtual TextureHandle CreateTextureWithFormat(int width, int height, uint32_t format, int access)=0
Creates a new texture with a specific pixel format.
Draggable, dockable panel for editor sub-windows.
bool Begin(bool *p_open=nullptr)
void SetDefaultSize(float width, float height)
RAII guard for ImGui child windows with optional styling.
static ThemeManager & Get()
#define ICON_MD_PLAY_ARROW
#define ICON_MD_VIDEOGAME_ASSET
#define ICON_MD_BUG_REPORT
#define ICON_MD_AUDIOTRACK
#define ICON_MD_SKIP_NEXT
#define ICON_MD_SPORTS_ESPORTS
#define ICON_MD_AUDIO_FILE
#define ICON_MD_SMART_TOY
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
bool g_window_is_resizing
constexpr double kNtscFrameRate
constexpr double kSpeedCalibration
constexpr double kPalFrameRate
constexpr int kMusicEditorSampleRate
std::vector< float > CopyHistoryOrdered(const std::array< float, Emulator::kMetricHistorySize > &data, int head, int count)
void RenderKeyboardConfig(input::InputManager *manager, const std::function< void(const input::InputConfig &)> &on_config_changed)
Render keyboard configuration UI.
void RenderPerformanceMonitor(Emulator *emu)
Performance metrics (FPS, frame time, audio status)
void RenderAIAgentPanel(Emulator *emu)
AI Agent panel for automated testing/gameplay.
void RenderSnesPpu(Emulator *emu)
SNES PPU output display.
void RenderNavBar(Emulator *emu)
Navigation bar with play/pause, step, reset controls.
void RenderBreakpointList(Emulator *emu)
Breakpoint list and management.
void RenderApuDebugger(Emulator *emu)
APU/Audio debugger with handshake tracker.
void RenderMemoryViewer(Emulator *emu)
Memory viewer/editor.
void RenderCpuInstructionLog(Emulator *emu, uint32_t log_size)
CPU instruction log (legacy, prefer DisassemblyViewer)
void RenderVirtualController(Emulator *emu)
Virtual SNES controller for testing input without keyboard Useful for debugging input issues - bypass...