32int main(
int argc,
char **argv) {
33 absl::InitializeSymbolizer(argv[0]);
35 absl::FailureSignalHandlerOptions options;
36 options.symbolize_stacktrace =
true;
37 options.use_alternate_stack =
39 options.alarm_on_failure_secs =
41 options.call_previous_handler =
true;
42 absl::InstallFailureSignalHandler(options);
44 absl::ParseCommandLine(argc, argv);
46 if (absl::GetFlag(FLAGS_emu_no_gui)) {
48 if (!rom.
LoadFromFile(absl::GetFlag(FLAGS_emu_rom)).ok()) {
53 std::vector<uint8_t> rom_data = rom.
vector();
56 if (!absl::GetFlag(FLAGS_emu_load_state).empty()) {
57 snes.
loadState(absl::GetFlag(FLAGS_emu_load_state));
60 for (
int i = 0; i < absl::GetFlag(FLAGS_emu_frames); ++i) {
64 if (!absl::GetFlag(FLAGS_emu_dump_state).empty()) {
65 snes.
saveState(absl::GetFlag(FLAGS_emu_dump_state));
73 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS) != 0) {
74 printf(
"SDL_Init failed: %s\n", SDL_GetError());
79 std::unique_ptr<SDL_Window, SDL_Deleter> window_(
80 SDL_CreateWindow(
"Yaze Emulator",
81 SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
83 SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI),
86 printf(
"SDL_CreateWindow failed: %s\n", SDL_GetError());
92 auto renderer = std::make_unique<yaze::gfx::SDL2Renderer>();
93 if (!renderer->Initialize(window_.get())) {
94 printf(
"Failed to initialize renderer\n");
100 constexpr int kAudioFrequency = 48000;
101 SDL_AudioSpec want = {};
102 want.freq = kAudioFrequency;
103 want.format = AUDIO_S16;
106 want.callback =
nullptr;
109 SDL_AudioDeviceID audio_device = SDL_OpenAudioDevice(
nullptr, 0, &want, &have, 0);
110 if (audio_device == 0) {
111 printf(
"SDL_OpenAudioDevice failed: %s\n", SDL_GetError());
117 std::unique_ptr<int16_t[]> audio_buffer(
new int16_t[kAudioFrequency / 50 * 4]);
118 SDL_PauseAudioDevice(audio_device, 0);
121 void* ppu_texture = renderer->CreateTexture(512, 480);
123 printf(
"SDL_CreateTexture failed: %s\n", SDL_GetError());
124 SDL_CloseAudioDevice(audio_device);
131 std::vector<uint8_t> rom_data_;
137 const int max_frames = absl::GetFlag(FLAGS_emu_max_frames);
140 const uint64_t count_frequency = SDL_GetPerformanceFrequency();
141 uint64_t last_count = SDL_GetPerformanceCounter();
142 double time_adder = 0.0;
143 double wanted_frame_time = 0.0;
144 int wanted_samples = 0;
148 std::string rom_path = absl::GetFlag(FLAGS_emu_rom);
149 if (rom_path.empty()) {
150 rom_path =
"assets/zelda3.sfc";
154 printf(
"Failed to load ROM: %s\n", rom_path.c_str());
159 printf(
"Loaded ROM: %s (%zu bytes)\n", rom_path.c_str(), rom_.
size());
160 rom_data_ = rom_.
vector();
161 snes_.
Init(rom_data_);
164 const bool is_pal = snes_.
memory().pal_timing();
165 const double refresh_rate = is_pal ? 50.0 : 60.0;
166 wanted_frame_time = 1.0 / refresh_rate;
167 wanted_samples = kAudioFrequency /
static_cast<int>(refresh_rate);
169 printf(
"Emulator initialized: %s mode (%.1f Hz)\n", is_pal ?
"PAL" :
"NTSC", refresh_rate);
174 while (SDL_PollEvent(&event)) {
175 switch (event.type) {
178 rom_data_ = rom_.
vector();
179 snes_.
Init(rom_data_);
181 const bool is_pal = snes_.
memory().pal_timing();
182 const double refresh_rate = is_pal ? 50.0 : 60.0;
183 wanted_frame_time = 1.0 / refresh_rate;
184 wanted_samples = kAudioFrequency /
static_cast<int>(refresh_rate);
186 printf(
"Loaded new ROM via drag-and-drop: %s\n", event.drop.file);
190 SDL_free(event.drop.file);
196 case SDL_WINDOWEVENT:
197 switch (event.window.event) {
198 case SDL_WINDOWEVENT_CLOSE:
201 case SDL_WINDOWEVENT_SIZE_CHANGED:
212 const uint64_t current_count = SDL_GetPerformanceCounter();
213 const uint64_t delta = current_count - last_count;
214 last_count = current_count;
215 const double seconds =
static_cast<double>(delta) /
static_cast<double>(count_frequency);
216 time_adder += seconds;
219 while (time_adder >= wanted_frame_time - 0.002) {
220 time_adder -= wanted_frame_time;
227 static uint16_t last_cpu_pc = 0;
228 static int stuck_count = 0;
229 uint16_t current_cpu_pc = snes_.
cpu().PC;
231 if (current_cpu_pc == last_cpu_pc && current_cpu_pc >= 0x88B0 && current_cpu_pc <= 0x88C0) {
233 if (stuck_count > 180 && frame_count % 60 == 0) {
234 printf(
"[WARNING] CPU stuck at $%02X:%04X for %d frames (APU deadlock?)\n",
235 snes_.
cpu().PB, current_cpu_pc, stuck_count);
240 last_cpu_pc = current_cpu_pc;
243 if (frame_count % 60 == 0) {
244 printf(
"[Frame %d] CPU=$%02X:%04X SPC=$%04X APU_cycles=%llu\n",
245 frame_count, snes_.
cpu().PB, snes_.
cpu().PC,
246 snes_.
apu().spc700().PC, snes_.
apu().GetCycles());
250 if (max_frames > 0 && frame_count >= max_frames) {
251 printf(
"\n[EMULATOR] Reached max frames (%d), shutting down...\n", max_frames);
252 printf(
"[EMULATOR] Final state: CPU=$%02X:%04X SPC=$%04X\n",
253 snes_.
cpu().PB, snes_.
cpu().PC, snes_.
apu().spc700().PC);
259 snes_.
SetSamples(audio_buffer.get(), wanted_samples);
260 const uint32_t queued_size = SDL_GetQueuedAudioSize(audio_device);
261 const uint32_t max_queued = wanted_samples * 4 * 6;
262 if (queued_size <= max_queued) {
263 SDL_QueueAudio(audio_device, audio_buffer.get(), wanted_samples * 4);
267 void *ppu_pixels =
nullptr;
269 if (renderer->LockTexture(ppu_texture,
nullptr, &ppu_pixels, &ppu_pitch)) {
270 snes_.
SetPixels(
static_cast<uint8_t*
>(ppu_pixels));
271 renderer->UnlockTexture(ppu_texture);
278 renderer->RenderCopy(ppu_texture,
nullptr,
nullptr);
283 printf(
"\n[EMULATOR] Shutting down...\n");
287 renderer->DestroyTexture(ppu_texture);
288 ppu_texture =
nullptr;
292 SDL_PauseAudioDevice(audio_device, 1);
293 SDL_ClearQueuedAudio(audio_device);
294 SDL_CloseAudioDevice(audio_device);
297 renderer->Shutdown();
303 printf(
"[EMULATOR] Shutdown complete.\n");