39int main(
int argc,
char** argv) {
40 absl::InitializeSymbolizer(argv[0]);
42 absl::FailureSignalHandlerOptions options;
43 options.symbolize_stacktrace =
true;
44 options.use_alternate_stack =
46 options.alarm_on_failure_secs =
48 options.call_previous_handler =
true;
49 absl::InstallFailureSignalHandler(options);
51 absl::ParseCommandLine(argc, argv);
53 if (absl::GetFlag(FLAGS_emu_no_gui)) {
55 if (!rom.
LoadFromFile(absl::GetFlag(FLAGS_emu_rom)).ok()) {
60 std::vector<uint8_t> rom_data = rom.
vector();
63 if (!absl::GetFlag(FLAGS_emu_load_state).empty()) {
64 auto status = snes.
loadState(absl::GetFlag(FLAGS_emu_load_state));
66 printf(
"Failed to load state: %s\n", std::string(status.message()).c_str());
71 for (
int i = 0; i < absl::GetFlag(FLAGS_emu_frames); ++i) {
75 if (!absl::GetFlag(FLAGS_emu_dump_state).empty()) {
76 auto status = snes.
saveState(absl::GetFlag(FLAGS_emu_dump_state));
78 printf(
"Failed to save state: %s\n", std::string(status.message()).c_str());
91 config.
title =
"Yaze Emulator";
97 if (!window_backend->Initialize(config).ok()) {
98 printf(
"Failed to initialize window backend\n");
104 if (!window_backend->InitializeRenderer(renderer.get())) {
105 printf(
"Failed to initialize renderer\n");
106 window_backend->Shutdown();
111 if (!window_backend->InitializeImGui(renderer.get()).ok()) {
112 printf(
"Failed to initialize ImGui\n");
113 window_backend->Shutdown();
128 constexpr int kNativeSampleRate = 32040;
130 if (!audio_backend->Initialize(audio_config)) {
131 printf(
"Failed to initialize audio backend\n");
134 printf(
"Audio initialized: %s\n", audio_backend->GetBackendName().c_str());
137 if (audio_backend->SupportsAudioStream()) {
138 audio_backend->SetAudioStreamResampling(
true, kNativeSampleRate, 2);
139 printf(
"Audio resampling enabled: %dHz -> %dHz\n",
145 std::unique_ptr<int16_t[]> audio_buffer(
149 void* ppu_texture = renderer->CreateTexture(512, 480);
151 printf(
"SDL_CreateTexture failed: %s\n", SDL_GetError());
152 window_backend->Shutdown();
158 std::vector<uint8_t> rom_data_;
169 const int max_frames = absl::GetFlag(FLAGS_emu_max_frames);
170 bool fix_red_tint = absl::GetFlag(FLAGS_emu_fix_red_tint);
173 const uint64_t count_frequency = SDL_GetPerformanceFrequency();
174 uint64_t last_count = SDL_GetPerformanceCounter();
175 double time_adder = 0.0;
176 double wanted_frame_time = 0.0;
177 int wanted_samples = 0;
181 std::string rom_path = absl::GetFlag(FLAGS_emu_rom);
182 if (rom_path.empty()) {
183 rom_path =
"assets/zelda3.sfc";
187 printf(
"Failed to load ROM: %s\n", rom_path.c_str());
192 printf(
"Loaded ROM: %s (%zu bytes)\n", rom_path.c_str(), rom_.
size());
193 rom_data_ = rom_.
vector();
194 snes_.
Init(rom_data_);
197 const bool is_pal = snes_.
memory().pal_timing();
198 const double refresh_rate = is_pal ? 50.0 : 60.0;
199 wanted_frame_time = 1.0 / refresh_rate;
202 wanted_samples = kNativeSampleRate /
static_cast<int>(refresh_rate);
204 printf(
"Emulator initialized: %s mode (%.1f Hz)\n", is_pal ?
"PAL" :
"NTSC",
210 while (window_backend->PollEvent(event)) {
211 switch (event.
type) {
214 rom_data_ = rom_.
vector();
215 snes_.
Init(rom_data_);
217 const bool is_pal = snes_.
memory().pal_timing();
218 const double refresh_rate = is_pal ? 50.0 : 60.0;
219 wanted_frame_time = 1.0 / refresh_rate;
222 wanted_samples = kNativeSampleRate /
static_cast<int>(refresh_rate);
224 printf(
"Loaded new ROM via drag-and-drop: %s\n", event.
dropped_file.c_str());
238 const uint64_t current_count = SDL_GetPerformanceCounter();
239 const uint64_t delta = current_count - last_count;
240 last_count = current_count;
241 const double seconds =
242 static_cast<double>(delta) /
static_cast<double>(count_frequency);
243 time_adder += seconds;
246 while (time_adder >= wanted_frame_time - 0.002) {
247 time_adder -= wanted_frame_time;
251 input_manager_.
Poll(&snes_, 1);
256 static uint16_t last_cpu_pc = 0;
257 static int stuck_count = 0;
258 uint16_t current_cpu_pc = snes_.
cpu().PC;
260 if (current_cpu_pc == last_cpu_pc && current_cpu_pc >= 0x88B0 &&
261 current_cpu_pc <= 0x88C0) {
263 if (stuck_count > 180 && frame_count % 60 == 0) {
265 "[WARNING] CPU stuck at $%02X:%04X for %d frames (APU "
267 snes_.
cpu().PB, current_cpu_pc, stuck_count);
272 last_cpu_pc = current_cpu_pc;
275 if (frame_count % 60 == 0) {
276 printf(
"[Frame %d] CPU=$%02X:%04X SPC=$%04X APU_cycles=%llu\n",
277 frame_count, snes_.
cpu().PB, snes_.
cpu().PC,
278 snes_.
apu().spc700().PC, snes_.
apu().GetCycles());
282 if (max_frames > 0 && frame_count >= max_frames) {
283 printf(
"\n[EMULATOR] Reached max frames (%d), shutting down...\n",
285 printf(
"[EMULATOR] Final state: CPU=$%02X:%04X SPC=$%04X\n",
286 snes_.
cpu().PB, snes_.
cpu().PC, snes_.
apu().spc700().PC);
292 snes_.
SetSamples(audio_buffer.get(), wanted_samples);
294 if (audio_backend && audio_backend->IsInitialized()) {
295 auto status = audio_backend->GetStatus();
297 if (status.queued_frames <=
static_cast<uint32_t
>(wanted_samples * 6)) {
300 if (!audio_backend->QueueSamplesNative(audio_buffer.get(), wanted_samples,
301 2, kNativeSampleRate)) {
303 if (audio_backend->SupportsAudioStream()) {
304 audio_backend->SetAudioStreamResampling(
true, kNativeSampleRate, 2);
305 audio_backend->QueueSamplesNative(audio_buffer.get(), wanted_samples,
306 2, kNativeSampleRate);
313 void* ppu_pixels =
nullptr;
315 if (renderer->LockTexture(ppu_texture,
nullptr, &ppu_pixels,
317 uint8_t* pixels =
static_cast<uint8_t*
>(ppu_pixels);
325 for (
int i = 0; i < 512 * 480; ++i) {
326 uint8_t b = pixels[i * 4 + 0];
327 uint8_t r = pixels[i * 4 + 2];
328 pixels[i * 4 + 0] = r;
329 pixels[i * 4 + 2] = b;
333 renderer->UnlockTexture(ppu_texture);
339 window_backend->NewImGuiFrame();
342 ImGui::Begin(
"Emulator Stats");
343 ImGui::Text(
"Frame: %d", frame_count);
344 ImGui::Text(
"FPS: %.1f", ImGui::GetIO().Framerate);
345 ImGui::Checkbox(
"Fix Red Tint", &fix_red_tint);
348 ImGui::Text(
"CPU PC: $%02X:%04X", snes_.
cpu().PB, snes_.
cpu().PC);
349 ImGui::Text(
"SPC PC: $%04X", snes_.
apu().spc700().PC);
357 renderer->RenderCopy(ppu_texture,
nullptr,
nullptr);
360 window_backend->RenderImGui(renderer.get());
366 printf(
"\n[EMULATOR] Shutting down...\n");
370 renderer->DestroyTexture(ppu_texture);
371 ppu_texture =
nullptr;
376 audio_backend->Shutdown();
380 window_backend->ShutdownImGui();
381 renderer->Shutdown();
382 window_backend->Shutdown();
384 printf(
"[EMULATOR] Shutdown complete.\n");