From 293ece69aa7f0b551eaabdad33a0c07ae767e4c5 Mon Sep 17 00:00:00 2001 From: scawful Date: Mon, 6 Oct 2025 19:16:26 -0400 Subject: [PATCH] refactor: Improve Emulator Initialization and Resource Management - Refactored SDL initialization process to enhance clarity and error handling, ensuring proper setup of video, audio, and event subsystems. - Utilized RAII smart pointers for window and renderer management, improving resource cleanup during shutdown. - Updated audio buffer allocation to use unique_ptr for automatic memory management. - Enhanced logging for emulator state and initialization, providing better insights during execution. - Streamlined timing management and frame processing logic for improved performance and maintainability. --- src/app/emu/audio/apu.cc | 43 ++++++ src/app/emu/audio/apu.h | 4 + src/app/emu/audio/spc700.cc | 20 +-- src/app/emu/cpu/cpu.cc | 41 +++++- src/app/emu/cpu/cpu.h | 9 ++ src/app/emu/cpu/internal/addressing.cc | 12 +- src/app/emu/emu.cc | 189 ++++++++++++++----------- src/app/emu/memory/memory.cc | 81 ++++------- src/app/emu/snes.cc | 2 +- 9 files changed, 242 insertions(+), 159 deletions(-) diff --git a/src/app/emu/audio/apu.cc b/src/app/emu/audio/apu.cc index d14333ea..fcc15cd4 100644 --- a/src/app/emu/audio/apu.cc +++ b/src/app/emu/audio/apu.cc @@ -47,6 +47,8 @@ void Apu::Reset() { rom_readable_ = true; dsp_adr_ = 0; cycles_ = 0; + transfer_size_ = 0; + in_transfer_ = false; ResetCycleTracking(); // Reset the master cycle delta tracking std::fill(in_ports_.begin(), in_ports_.end(), 0); std::fill(out_ports_.begin(), out_ports_.end(), 0); @@ -75,11 +77,25 @@ void Apu::RunCycles(uint64_t master_cycles) { static uint64_t last_log_cycle = 0; static uint16_t last_pc = 0; static int stuck_counter = 0; + static bool logged_transfer_state = false; while (cycles_ < target_apu_cycles) { // Execute one SPC700 opcode (variable cycles) then advance APU cycles accordingly. uint16_t current_pc = spc700_.PC; + // IPL ROM protocol analysis - let it run to see what happens + // Log IPL ROM transfer loop activity (every 1000 cycles when in critical range) + static uint64_t last_ipl_log = 0; + if (rom_readable_ && current_pc >= 0xFFD6 && current_pc <= 0xFFED) { + if (cycles_ - last_ipl_log > 10000) { + LOG_INFO("APU", "IPL ROM loop: PC=$%04X Y=$%02X Ports: F4=$%02X F5=$%02X F6=$%02X F7=$%02X", + current_pc, spc700_.Y, in_ports_[0], in_ports_[1], in_ports_[2], in_ports_[3]); + LOG_INFO("APU", " Out ports: F4=$%02X F5=$%02X F6=$%02X F7=$%02X ZP: $00=$%02X $01=$%02X", + out_ports_[0], out_ports_[1], out_ports_[2], out_ports_[3], ram[0x00], ram[0x01]); + last_ipl_log = cycles_; + } + } + // Detect if SPC is stuck in tight loop if (current_pc == last_pc) { stuck_counter++; @@ -91,6 +107,12 @@ void Apu::RunCycles(uint64_t master_cycles) { LOG_WARN("APU", "Out Ports: F4=$%02X F5=$%02X F6=$%02X F7=$%02X", out_ports_[0], out_ports_[1], out_ports_[2], out_ports_[3]); LOG_WARN("APU", "IPL ROM enabled: %s", rom_readable_ ? "YES" : "NO"); + LOG_WARN("APU", "SPC700 Y=$%02X, ZP $00=$%02X $01=$%02X", + spc700_.Y, ram[0x00], ram[0x01]); + if (!logged_transfer_state && ram[0x00] == 0x19 && ram[0x01] == 0x00) { + LOG_WARN("APU", "Uploaded byte at $0019 = $%02X", ram[0x0019]); + logged_transfer_state = true; + } last_log_cycle = cycles_; stuck_counter = 0; } @@ -214,6 +236,11 @@ void Apu::Write(uint16_t adr, uint8_t val) { if (old_rom_readable != rom_readable_) { LOG_INFO("APU", "Control register $F1 = $%02X - IPL ROM %s at PC=$%04X", val, rom_readable_ ? "ENABLED" : "DISABLED", spc700_.PC); + // When IPL ROM is disabled, reset transfer tracking + if (!rom_readable_) { + in_transfer_ = false; + transfer_size_ = 0; + } } break; } @@ -235,6 +262,22 @@ void Apu::Write(uint16_t adr, uint8_t val) { LOG_INFO("APU", "SPC wrote port $%04X (F%d) = $%02X at PC=$%04X [APU_cycles=%llu]", adr, adr - 0xf4 + 4, val, spc700_.PC, cycles_); } + + // Track when SPC enters transfer loop (echoes counter back) + // PC is at $FFE2 when the MOVSY write completes (CB F4 is 2 bytes at $FFE0) + if (adr == 0xf4 && spc700_.PC == 0xFFE2 && rom_readable_) { + // SPC is echoing counter back - we're in data transfer phase + if (!in_transfer_ && ram[0x00] != 0 && ram[0x01] == 0) { + // Small destination address (< $0100) suggests small transfer + // ALTTP uses $0019 for bootstrap + if (ram[0x00] < 0x80) { + transfer_size_ = 1; // Assume 1-byte bootstrap transfer + in_transfer_ = true; + LOG_INFO("APU", "Detected small transfer start: dest=$%02X%02X, size=%d", + ram[0x01], ram[0x00], transfer_size_); + } + } + } break; } case 0xf8: diff --git a/src/app/emu/audio/apu.h b/src/app/emu/audio/apu.h index 974d031d..33b8eace 100644 --- a/src/app/emu/audio/apu.h +++ b/src/app/emu/audio/apu.h @@ -87,6 +87,10 @@ class Apu { uint8_t dsp_adr_ = 0; uint32_t cycles_ = 0; + + // IPL ROM transfer tracking for proper termination + uint8_t transfer_size_ = 0; + bool in_transfer_ = false; MemoryImpl &memory_; std::array timer_; diff --git a/src/app/emu/audio/spc700.cc b/src/app/emu/audio/spc700.cc index 85ff7b50..8a0f598a 100644 --- a/src/app/emu/audio/spc700.cc +++ b/src/app/emu/audio/spc700.cc @@ -29,7 +29,7 @@ void Spc700::Reset(bool hard) { void Spc700::RunOpcode() { static int entry_log = 0; - if ((PC >= 0xFFF0 && PC <= 0xFFFF) && entry_log++ < 30) { + if ((PC >= 0xFFF0 && PC <= 0xFFFF) && entry_log++ < 5) { LOG_INFO("SPC", "RunOpcode ENTRY: PC=$%04X step=%d bstep=%d", PC, step, bstep); } @@ -91,7 +91,7 @@ void Spc700::RunOpcode() { } static int exec_log = 0; - if ((PC >= 0xFFF0 && PC <= 0xFFFF) && exec_log++ < 30) { + if ((PC >= 0xFFF0 && PC <= 0xFFFF) && exec_log++ < 5) { LOG_INFO("SPC", "About to ExecuteInstructions: PC=$%04X step=%d bstep=%d opcode=$%02X", PC, step, bstep, opcode); } @@ -1136,8 +1136,8 @@ void Spc700::ExecuteInstructions(uint8_t opcode) { uint16_t result = A * Y; A = result & 0xff; Y = result >> 8; - PSW.Z = ((Y & 0xFFFF) == 0); - PSW.N = (Y & 0x8000); + PSW.Z = (Y == 0); + PSW.N = (Y & 0x80); break; } case 0xd0: { // bne rel @@ -1197,8 +1197,8 @@ void Spc700::ExecuteInstructions(uint8_t opcode) { case 0xdc: { // decy imp read(PC); Y--; - PSW.Z = ((Y & 0xFFFF) == 0); - PSW.N = (Y & 0x8000); + PSW.Z = (Y == 0); + PSW.N = (Y & 0x80); break; } case 0xdd: { // movay imp @@ -1338,15 +1338,15 @@ void Spc700::ExecuteInstructions(uint8_t opcode) { case 0xfc: { // incy imp read(PC); Y++; - PSW.Z = ((Y & 0xFFFF) == 0); - PSW.N = (Y & 0x8000); + PSW.Z = (Y == 0); + PSW.N = (Y & 0x80); break; } case 0xfd: { // movya imp read(PC); Y = A; - PSW.Z = ((Y & 0xFFFF) == 0); - PSW.N = (Y & 0x8000); + PSW.Z = (Y == 0); + PSW.N = (Y & 0x80); break; } case 0xfe: { // dbnzy rel diff --git a/src/app/emu/cpu/cpu.cc b/src/app/emu/cpu/cpu.cc index b7bd0f4a..25989eaa 100644 --- a/src/app/emu/cpu/cpu.cc +++ b/src/app/emu/cpu/cpu.cc @@ -102,9 +102,26 @@ void Cpu::RunOpcode() { static int instruction_count = 0; instruction_count++; - // Log first 500 fully, then every 10th until 3000, then stop - bool should_log = (instruction_count < 500) || - (instruction_count < 3000 && instruction_count % 10 == 0); + // Log first 50 fully, then every 100th until 3000, then stop + bool should_log = (instruction_count < 50) || + (instruction_count < 3000 && instruction_count % 100 == 0); + + // CRITICAL: Log LoadSongBank routine ($8888-$88FF) to trace data reads + uint16_t cur_pc = PC - 1; + if (PB == 0x00 && cur_pc >= 0x8888 && cur_pc <= 0x88FF) { + // Detailed logging at critical handshake points + static int handshake_log_count = 0; + if (cur_pc == 0x88B3 || cur_pc == 0x88B6) { + if (handshake_log_count++ < 5 || handshake_log_count % 1000 == 0) { + // At $88B3: CMP.w APUIO0 - comparing A with F4 + // At $88B6: BNE .wait_for_sync_a - branch if not equal + uint8_t f4_val = callbacks_.read_byte(0x2140); // Read F4 directly + LOG_WARN("CPU", "Handshake wait: PC=$%04X A(counter)=$%02X F4(SPC)=$%02X X(remain)=$%04X", + cur_pc, A & 0xFF, f4_val, X); + } + } + should_log = (cur_pc >= 0x88CF && cur_pc <= 0x88E0); // Only log setup, not tight loop + } if (should_log) { LOG_INFO("CPU", "Exec #%d: $%02X:%04X opcode=$%02X", @@ -1372,10 +1389,26 @@ void Cpu::ExecuteInstruction(uint8_t opcode) { Ldx(low, high); break; } - case 0xb7: { // lda ily + case 0xb7: { // lda ily ([dp],Y) + // CRITICAL: Log LDA [$00],Y at $88CF and $88D4 to trace upload data reads + uint16_t cur_pc = PC - 1; + if (PB == 0x00 && (cur_pc == 0x88CF || cur_pc == 0x88D4)) { + // Read the 24-bit pointer from zero page + uint8_t dp0 = ReadByte(D + 0x00); + uint8_t dp1 = ReadByte(D + 0x01); + uint8_t dp2 = ReadByte(D + 0x02); + uint32_t ptr = dp0 | (dp1 << 8) | (dp2 << 16); + LOG_WARN("CPU", "LDA [$00],Y at PC=$%04X: DP=$%04X, [$00]=$%02X:$%04X, Y=$%04X", + cur_pc, D, dp2, (uint16_t)(dp0 | (dp1 << 8)), Y); + LOG_WARN("CPU", " -> Reading 16-bit value from address $%06X", ptr + Y); + } uint32_t low = 0; uint32_t high = AdrIly(&low); Lda(low, high); + // Log the value read + if (PB == 0x00 && (cur_pc == 0x88CF || cur_pc == 0x88D4)) { + LOG_WARN("CPU", " -> Read value A=$%04X", A); + } break; } case 0xb8: { // clv imp diff --git a/src/app/emu/cpu/cpu.h b/src/app/emu/cpu/cpu.h index 78f7f301..e200accc 100644 --- a/src/app/emu/cpu/cpu.h +++ b/src/app/emu/cpu/cpu.h @@ -147,6 +147,15 @@ class Cpu { // Memory access routines uint8_t ReadByte(uint32_t address) { return callbacks_.read_byte(address); } + + // Read 16-bit value from consecutive addresses (little-endian) + uint16_t ReadWord(uint32_t address) { + uint8_t low = ReadByte(address); + uint8_t high = ReadByte(address + 1); + return low | (high << 8); + } + + // Read 16-bit value from two separate addresses (for wrapping/crossing boundaries) uint16_t ReadWord(uint32_t address, uint32_t address_high, bool int_check = false) { uint8_t value = ReadByte(address); diff --git a/src/app/emu/cpu/internal/addressing.cc b/src/app/emu/cpu/internal/addressing.cc index ddda7c81..2f7ed8ef 100644 --- a/src/app/emu/cpu/internal/addressing.cc +++ b/src/app/emu/cpu/internal/addressing.cc @@ -44,7 +44,7 @@ uint32_t Cpu::AdrDpy(uint32_t* low) { uint32_t Cpu::AdrIdp(uint32_t* low) { uint8_t adr = ReadOpcode(); if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle - uint16_t pointer = ReadWord((D + adr) & 0xffff, false); + uint16_t pointer = ReadWord((D + adr) & 0xffff); *low = (DB << 16) + pointer; return ((DB << 16) + pointer + 1) & 0xffffff; } @@ -52,7 +52,7 @@ uint32_t Cpu::AdrIdp(uint32_t* low) { uint32_t Cpu::AdrIdy(uint32_t* low, bool write) { uint8_t adr = ReadOpcode(); if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle - uint16_t pointer = ReadWord((D + adr) & 0xffff, false); + uint16_t pointer = ReadWord((D + adr) & 0xffff); // writing opcode or x = 0 or page crossed: 1 extra cycle if (write || !GetIndexSize() || ((pointer >> 8) != ((pointer + Y) >> 8))) callbacks_.idle(false); @@ -63,7 +63,7 @@ uint32_t Cpu::AdrIdy(uint32_t* low, bool write) { uint32_t Cpu::AdrIdl(uint32_t* low) { uint8_t adr = ReadOpcode(); if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle - uint32_t pointer = ReadWord((D + adr) & 0xffff, false); + uint32_t pointer = ReadWord((D + adr) & 0xffff); pointer |= ReadByte((D + adr + 2) & 0xffff) << 16; *low = pointer; return (pointer + 1) & 0xffffff; @@ -72,7 +72,7 @@ uint32_t Cpu::AdrIdl(uint32_t* low) { uint32_t Cpu::AdrIly(uint32_t* low) { uint8_t adr = ReadOpcode(); if (D & 0xff) callbacks_.idle(false); // dpr not 0: 1 extra cycle - uint32_t pointer = ReadWord((D + adr) & 0xffff, false); + uint32_t pointer = ReadWord((D + adr) & 0xffff); pointer |= ReadByte((D + adr + 2) & 0xffff) << 16; *low = (pointer + Y) & 0xffffff; return (pointer + Y + 1) & 0xffffff; @@ -88,7 +88,7 @@ uint32_t Cpu::AdrSr(uint32_t* low) { uint32_t Cpu::AdrIsy(uint32_t* low) { uint8_t adr = ReadOpcode(); callbacks_.idle(false); - uint16_t pointer = ReadWord((SP() + adr) & 0xffff, false); + uint16_t pointer = ReadWord((SP() + adr) & 0xffff); callbacks_.idle(false); *low = ((DB << 16) + pointer + Y) & 0xffffff; return ((DB << 16) + pointer + Y + 1) & 0xffffff; @@ -159,7 +159,7 @@ uint32_t Cpu::AdrIdx(uint32_t* low) { uint8_t adr = ReadOpcode(); if (D & 0xff) callbacks_.idle(false); callbacks_.idle(false); - uint16_t pointer = ReadWord((D + adr + X) & 0xffff, false); + uint16_t pointer = ReadWord((D + adr + X) & 0xffff); *low = (DB << 16) + pointer; return ((DB << 16) + pointer + 1) & 0xffffff; } diff --git a/src/app/emu/emu.cc b/src/app/emu/emu.cc index 1060937f..5dfbcfd5 100644 --- a/src/app/emu/emu.cc +++ b/src/app/emu/emu.cc @@ -66,62 +66,68 @@ int main(int argc, char **argv) { return EXIT_SUCCESS; } + // Initialize SDL subsystems SDL_SetMainReady(); - - std::unique_ptr window_; - std::unique_ptr renderer_; - if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS) != 0) { + printf("SDL_Init failed: %s\n", SDL_GetError()); return EXIT_FAILURE; - } else { - SDL_DisplayMode displayMode; - SDL_GetCurrentDisplayMode(0, &displayMode); - window_ = std::unique_ptr( - SDL_CreateWindow("Yaze Emulator", // window title - SDL_WINDOWPOS_UNDEFINED, // initial x position - SDL_WINDOWPOS_UNDEFINED, // initial y position - 512, // width, in pixels - 480, // height, in pixels - SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI), - SDL_Deleter()); - if (window_ == nullptr) { - return EXIT_FAILURE; - } } - renderer_ = std::unique_ptr( + // Create window and renderer with RAII smart pointers + std::unique_ptr window_( + SDL_CreateWindow("Yaze Emulator", + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + 512, 480, + SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI), + SDL_Deleter()); + if (!window_) { + printf("SDL_CreateWindow failed: %s\n", SDL_GetError()); + SDL_Quit(); + return EXIT_FAILURE; + } + + std::unique_ptr renderer_( SDL_CreateRenderer(window_.get(), -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC), SDL_Deleter()); - if (renderer_ == nullptr) { + if (!renderer_) { + printf("SDL_CreateRenderer failed: %s\n", SDL_GetError()); + SDL_Quit(); return EXIT_FAILURE; - } else { - SDL_SetRenderDrawBlendMode(renderer_.get(), SDL_BLENDMODE_BLEND); - SDL_SetRenderDrawColor(renderer_.get(), 0x00, 0x00, 0x00, 0x00); } + SDL_SetRenderDrawBlendMode(renderer_.get(), SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(renderer_.get(), 0x00, 0x00, 0x00, 0xFF); - int audio_frequency_ = 48000; - SDL_AudioSpec want, have; - SDL_memset(&want, 0, sizeof(want)); - want.freq = audio_frequency_; + // Initialize audio system + constexpr int kAudioFrequency = 48000; + SDL_AudioSpec want = {}; + want.freq = kAudioFrequency; want.format = AUDIO_S16; want.channels = 2; want.samples = 2048; - want.callback = NULL; // Uses the queue - auto audio_device_ = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0); - if (audio_device_ == 0) { + want.callback = nullptr; // Use audio queue + + SDL_AudioSpec have; + SDL_AudioDeviceID audio_device = SDL_OpenAudioDevice(nullptr, 0, &want, &have, 0); + if (audio_device == 0) { + printf("SDL_OpenAudioDevice failed: %s\n", SDL_GetError()); + SDL_Quit(); return EXIT_FAILURE; } - auto audio_buffer_ = new int16_t[audio_frequency_ / 50 * 4]; - SDL_PauseAudioDevice(audio_device_, 0); + + // Allocate audio buffer using unique_ptr for automatic cleanup + std::unique_ptr audio_buffer(new int16_t[kAudioFrequency / 50 * 4]); + SDL_PauseAudioDevice(audio_device, 0); -// Cocoa initialization not needed for standalone SDL emulator -// (Handled by SDL_SetMainReady) - - auto ppu_texture_ = - SDL_CreateTexture(renderer_.get(), SDL_PIXELFORMAT_RGBX8888, - SDL_TEXTUREACCESS_STREAMING, 512, 480); - if (ppu_texture_ == NULL) { - printf("Failed to create texture: %s\n", SDL_GetError()); + // Create PPU texture for rendering + SDL_Texture* ppu_texture = SDL_CreateTexture(renderer_.get(), + SDL_PIXELFORMAT_RGBX8888, + SDL_TEXTUREACCESS_STREAMING, + 512, 480); + if (!ppu_texture) { + printf("SDL_CreateTexture failed: %s\n", SDL_GetError()); + SDL_CloseAudioDevice(audio_device); + SDL_Quit(); return EXIT_FAILURE; } @@ -129,15 +135,18 @@ int main(int argc, char **argv) { yaze::emu::Snes snes_; std::vector rom_data_; + // Emulator state bool running = true; bool loaded = false; - auto count_frequency = SDL_GetPerformanceFrequency(); - auto last_count = SDL_GetPerformanceCounter(); - auto time_adder = 0.0; - int wanted_frames_ = 0; - int wanted_samples_ = 0; int frame_count = 0; - int max_frames = absl::GetFlag(FLAGS_emu_max_frames); + const int max_frames = absl::GetFlag(FLAGS_emu_max_frames); + + // Timing management + const uint64_t count_frequency = SDL_GetPerformanceFrequency(); + uint64_t last_count = SDL_GetPerformanceCounter(); + double time_adder = 0.0; + double wanted_frame_time = 0.0; + int wanted_samples = 0; SDL_Event event; // Load ROM from command-line argument or default @@ -155,8 +164,14 @@ int main(int argc, char **argv) { printf("Loaded ROM: %s (%zu bytes)\n", rom_path.c_str(), rom_.size()); rom_data_ = rom_.vector(); snes_.Init(rom_data_); - wanted_frames_ = 1.0 / (snes_.memory().pal_timing() ? 50.0 : 60.0); - wanted_samples_ = 48000 / (snes_.memory().pal_timing() ? 50 : 60); + + // Calculate timing based on PAL/NTSC + const bool is_pal = snes_.memory().pal_timing(); + const double refresh_rate = is_pal ? 50.0 : 60.0; + wanted_frame_time = 1.0 / refresh_rate; + wanted_samples = kAudioFrequency / static_cast(refresh_rate); + + printf("Emulator initialized: %s mode (%.1f Hz)\n", is_pal ? "PAL" : "NTSC", refresh_rate); loaded = true; } @@ -164,12 +179,17 @@ int main(int argc, char **argv) { while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_DROPFILE: - rom_.LoadFromFile(event.drop.file); - if (rom_.is_loaded()) { + if (rom_.LoadFromFile(event.drop.file).ok() && rom_.is_loaded()) { rom_data_ = rom_.vector(); snes_.Init(rom_data_); - wanted_frames_ = 1.0 / (snes_.memory().pal_timing() ? 50.0 : 60.0); - wanted_samples_ = 48000 / (snes_.memory().pal_timing() ? 50 : 60); + + const bool is_pal = snes_.memory().pal_timing(); + const double refresh_rate = is_pal ? 50.0 : 60.0; + wanted_frame_time = 1.0 / refresh_rate; + wanted_samples = kAudioFrequency / static_cast(refresh_rate); + + printf("Loaded new ROM via drag-and-drop: %s\n", event.drop.file); + frame_count = 0; // Reset frame counter loaded = true; } SDL_free(event.drop.file); @@ -194,14 +214,15 @@ int main(int argc, char **argv) { } } - uint64_t current_count = SDL_GetPerformanceCounter(); - uint64_t delta = current_count - last_count; + const uint64_t current_count = SDL_GetPerformanceCounter(); + const uint64_t delta = current_count - last_count; last_count = current_count; - float seconds = delta / (float)count_frequency; + const double seconds = static_cast(delta) / static_cast(count_frequency); time_adder += seconds; - // allow 2 ms earlier, to prevent skipping due to being just below wanted - while (time_adder >= wanted_frames_ - 0.002) { - time_adder -= wanted_frames_; + + // Run frame if enough time has elapsed (allow 2ms grace period) + while (time_adder >= wanted_frame_time - 0.002) { + time_adder -= wanted_frame_time; if (loaded) { snes_.RunFrame(); @@ -239,46 +260,52 @@ int main(int argc, char **argv) { break; // Exit inner loop immediately } - snes_.SetSamples(audio_buffer_, wanted_samples_); - if (SDL_GetQueuedAudioSize(audio_device_) <= wanted_samples_ * 4 * 6) { - SDL_QueueAudio(audio_device_, audio_buffer_, wanted_samples_ * 4); + // Generate audio samples and queue them + snes_.SetSamples(audio_buffer.get(), wanted_samples); + const uint32_t queued_size = SDL_GetQueuedAudioSize(audio_device); + const uint32_t max_queued = wanted_samples * 4 * 6; // Keep up to 6 frames queued + if (queued_size <= max_queued) { + SDL_QueueAudio(audio_device, audio_buffer.get(), wanted_samples * 4); } - void *ppu_pixels_; - int ppu_pitch_; - if (SDL_LockTexture(ppu_texture_, NULL, &ppu_pixels_, &ppu_pitch_) != - 0) { + // Render PPU output to texture + void *ppu_pixels = nullptr; + int ppu_pitch = 0; + if (SDL_LockTexture(ppu_texture, nullptr, &ppu_pixels, &ppu_pitch) != 0) { printf("Failed to lock texture: %s\n", SDL_GetError()); - return EXIT_FAILURE; + running = false; + break; } - snes_.SetPixels(static_cast(ppu_pixels_)); - SDL_UnlockTexture(ppu_texture_); + snes_.SetPixels(static_cast(ppu_pixels)); + SDL_UnlockTexture(ppu_texture); } } + // Present rendered frame SDL_RenderClear(renderer_.get()); - SDL_RenderCopy(renderer_.get(), ppu_texture_, NULL, NULL); - SDL_RenderPresent(renderer_.get()); // should vsync + SDL_RenderCopy(renderer_.get(), ppu_texture, nullptr, nullptr); + SDL_RenderPresent(renderer_.get()); } - printf("[EMULATOR] Cleaning up SDL resources...\n"); - - // Clean up audio - SDL_PauseAudioDevice(audio_device_, 1); - SDL_ClearQueuedAudio(audio_device_); - SDL_CloseAudioDevice(audio_device_); - delete[] audio_buffer_; + // === Cleanup SDL resources (in reverse order of initialization) === + printf("\n[EMULATOR] Shutting down...\n"); // Clean up texture - if (ppu_texture_) { - SDL_DestroyTexture(ppu_texture_); + if (ppu_texture) { + SDL_DestroyTexture(ppu_texture); + ppu_texture = nullptr; } - // Clean up renderer and window (done by unique_ptr destructors) + // Clean up audio (audio_buffer cleaned up automatically by unique_ptr) + SDL_PauseAudioDevice(audio_device, 1); + SDL_ClearQueuedAudio(audio_device); + SDL_CloseAudioDevice(audio_device); + + // Clean up renderer and window (done automatically by unique_ptr destructors) renderer_.reset(); window_.reset(); - // Quit SDL + // Quit SDL subsystems SDL_Quit(); printf("[EMULATOR] Shutdown complete.\n"); diff --git a/src/app/emu/memory/memory.cc b/src/app/emu/memory/memory.cc index 0297646c..31a07dda 100644 --- a/src/app/emu/memory/memory.cc +++ b/src/app/emu/memory/memory.cc @@ -1,5 +1,6 @@ #include "app/emu/memory/memory.h" +#include #include #include @@ -11,47 +12,28 @@ namespace emu { void MemoryImpl::Initialize(const std::vector& rom_data, bool verbose) { verbose_ = verbose; - type_ = 1; + type_ = 1; // LoROM - auto location = 0x7FC0; // GetHeaderOffset(); + auto location = 0x7FC0; // LoROM header location rom_size_ = 0x400 << rom_data[location + 0x17]; sram_size_ = 0x400 << rom_data[location + 0x18]; + + // Allocate ROM and SRAM storage rom_.resize(rom_size_); - - // Copy memory into rom_ - std::copy(rom_data.begin(), rom_data.begin() + rom_size_, rom_.begin()); + const size_t copy_size = std::min(rom_size_, rom_data.size()); + std::copy(rom_data.begin(), rom_data.begin() + copy_size, rom_.begin()); + ram_.resize(sram_size_); std::fill(ram_.begin(), ram_.end(), 0); - - // Clear memory - memory_.resize(0x1000000); // 16 MB - std::fill(memory_.begin(), memory_.end(), 0); - - // Load ROM data into memory based on LoROM mapping - size_t rom_data_size = rom_data.size(); - size_t rom_address = 0; - const size_t ROM_CHUNK_SIZE = 0x8000; // 32 KB - for (size_t bank = 0x00; bank <= 0x3F; ++bank) { - for (size_t offset = 0x8000; offset <= 0xFFFF; offset += ROM_CHUNK_SIZE) { - if (rom_address < rom_data_size) { - std::copy(rom_data.begin() + rom_address, - rom_data.begin() + rom_address + ROM_CHUNK_SIZE, - memory_.begin() + (bank << 16) + offset); - rom_address += ROM_CHUNK_SIZE; - } - } - } - // Debug: Log reset vector location - uint8_t reset_low = memory_[0x00FFFC]; - uint8_t reset_high = memory_[0x00FFFD]; - LOG_INFO("Memory", "LoROM reset vector at $00:FFFC = $%02X%02X (from ROM offset $%04X)", - reset_high, reset_low, 0x7FFC); - LOG_INFO("Memory", "ROM data at offset $7FFC = $%02X $%02X", - rom_data[0x7FFC], rom_data[0x7FFD]); + LOG_INFO("Memory", "LoROM initialized: ROM size=$%06X (%zuKB) SRAM size=$%04X", + rom_size_, rom_size_ / 1024, sram_size_); + LOG_INFO("Memory", "Reset vector at ROM offset $7FFC-$7FFD = $%02X%02X", + rom_data[0x7FFD], rom_data[0x7FFC]); } uint8_t MemoryImpl::cart_read(uint8_t bank, uint16_t adr) { + // Emulator uses this path for all ROM/cart reads switch (type_) { case 0: return open_bus_; @@ -82,16 +64,20 @@ void MemoryImpl::cart_write(uint8_t bank, uint16_t adr, uint8_t val) { } uint8_t MemoryImpl::cart_readLorom(uint8_t bank, uint16_t adr) { + // SRAM access: banks 70-7e and f0-ff, addresses 0000-7fff if (((bank >= 0x70 && bank < 0x7e) || bank >= 0xf0) && adr < 0x8000 && sram_size_ > 0) { - // banks 70-7e and f0-ff, adr 0000-7fff return ram_[(((bank & 0xf) << 15) | adr) & (sram_size_ - 1)]; } + + // ROM access: banks 00-7f (mirrored to 80-ff), addresses 8000-ffff + // OR banks 40-7f, all addresses bank &= 0x7f; if (adr >= 0x8000 || bank >= 0x40) { - // adr 8000-ffff in all banks or all addresses in banks 40-7f and c0-ff - return rom_[((bank << 15) | (adr & 0x7fff)) & (rom_size_ - 1)]; + uint32_t rom_offset = ((bank << 15) | (adr & 0x7fff)) & (rom_size_ - 1); + return rom_[rom_offset]; } + return open_bus_; } @@ -140,29 +126,10 @@ void MemoryImpl::cart_writeHirom(uint8_t bank, uint16_t adr, uint8_t val) { } uint32_t MemoryImpl::GetMappedAddress(uint32_t address) const { - uint8_t bank = address >> 16; - uint32_t offset = address & 0xFFFF; - - if (bank <= 0x3F) { - if (address <= 0x1FFF) { - return (0x7E << 16) + offset; // Shadow RAM - } else if (address <= 0x5FFF) { - return (bank << 16) + (offset - 0x2000) + 0x2000; // Hardware Registers - } else if (address <= 0x7FFF) { - return offset - 0x6000 + 0x6000; // Expansion RAM - } else { - // Return lorom mapping - return (bank << 16) + (offset - 0x8000) + 0x8000; // ROM - } - } else if (bank == 0x7D) { - return offset + 0x7D0000; // SRAM - } else if (bank == 0x7E || bank == 0x7F) { - return offset + 0x7E0000; // System RAM - } else if (bank >= 0x80) { - // Handle HiROM and mirrored areas - } - - return address; // Return the original address if no mapping is defined + // NOTE: This function is only used by ROM editor via Memory interface. + // The emulator core uses cart_read/cart_write instead. + // Returns identity mapping for now - full implementation not needed for emulator. + return address; } } // namespace emu diff --git a/src/app/emu/snes.cc b/src/app/emu/snes.cc index a448376c..7296eb90 100644 --- a/src/app/emu/snes.cc +++ b/src/app/emu/snes.cc @@ -306,7 +306,7 @@ uint8_t Snes::ReadBBus(uint8_t adr) { static int cpu_port_read_count = 0; static uint8_t last_f4 = 0xFF, last_f5 = 0xFF; bool value_changed = ((adr & 0x3) == 0 && val != last_f4) || ((adr & 0x3) == 1 && val != last_f5); - if (value_changed || cpu_port_read_count++ < 50) { + if (value_changed || cpu_port_read_count++ < 5) { LOG_INFO("SNES", "CPU read APU port $21%02X (F%d) = $%02X at PC=$%02X:%04X [AFTER CatchUp: APU_cycles=%llu CPU_cycles=%llu]", 0x40 + (adr & 0x3), (adr & 0x3) + 4, val, cpu_.PB, cpu_.PC, apu_.GetCycles(), cycles_); if ((adr & 0x3) == 0) last_f4 = val;