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.
This commit is contained in:
scawful
2025-10-06 19:16:26 -04:00
parent a5d4722d13
commit 293ece69aa
9 changed files with 242 additions and 159 deletions

View File

@@ -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:

View File

@@ -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, 3> timer_;

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -66,62 +66,68 @@ int main(int argc, char **argv) {
return EXIT_SUCCESS;
}
// Initialize SDL subsystems
SDL_SetMainReady();
std::unique_ptr<SDL_Window, SDL_Deleter> window_;
std::unique_ptr<SDL_Renderer, SDL_Deleter> 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_Window, SDL_Deleter>(
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<SDL_Renderer, SDL_Deleter>(
// Create window and renderer with RAII smart pointers
std::unique_ptr<SDL_Window, SDL_Deleter> 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<SDL_Renderer, SDL_Deleter> 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<int16_t[]> 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<uint8_t> 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<int>(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<int>(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<double>(delta) / static_cast<double>(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<uint8_t *>(ppu_pixels_));
SDL_UnlockTexture(ppu_texture_);
snes_.SetPixels(static_cast<uint8_t*>(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");

View File

@@ -1,5 +1,6 @@
#include "app/emu/memory/memory.h"
#include <algorithm>
#include <cstdint>
#include <vector>
@@ -11,47 +12,28 @@ namespace emu {
void MemoryImpl::Initialize(const std::vector<uint8_t>& 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<size_t>(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

View File

@@ -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;