TEST(BusyLoopTest, MultiThreaded) { Common::BlockingLoop loop; Common::Event e; for (int i = 0; i < 100; i++) { loop.Prepare(); std::thread loop_thread([&]() { loop.Run([&]() { e.Set(); }); }); // Ping - Pong for (int j = 0; j < 10; j++) { loop.Wakeup(); e.Wait(); // Just waste some time. So the main loop did fall back to the sleep state much more likely. Common::SleepCurrentThread(1); } for (int j = 0; j < 100; j++) { // We normally have to call Wakeup to assure the Event is triggered. // But this check is for an internal feature of the BlockingLoop. // It's implemented to fall back to a busy loop regulary. // If we're in the busy loop, the payload (and so the Event) is called all the time. // loop.Wakeup(); e.Wait(); } loop.Stop(); loop_thread.join(); } }
static void DVDThread() { Common::SetCurrentThreadName("DVD thread"); while (true) { s_request_queue_expanded.Wait(); if (s_dvd_thread_exiting.IsSet()) return; ReadRequest request; while (s_request_queue.Pop(request)) { FileMonitor::Log(*s_disc, request.partition, request.dvd_offset); std::vector<u8> buffer(request.length); if (!s_disc->Read(request.dvd_offset, request.length, buffer.data(), request.partition)) buffer.resize(0); request.realtime_done_us = Common::Timer::GetTimeUs(); s_result_queue.Push(ReadResult(std::move(request), std::move(buffer))); s_result_queue_expanded.Set(); if (s_dvd_thread_exiting.IsSet()) return; } } }
static void FinishRead(u64 id, s64 cycles_late) { // We can't simply pop s_result_queue and always get the ReadResult // we want, because the DVD thread may add ReadResults to the queue // in a different order than we want to get them. What we do instead // is to pop the queue until we find the ReadResult we want (the one // whose ID matches userdata), which means we may end up popping // ReadResults that we don't want. We can't add those unwanted results // back to the queue, because the queue can only have one writer. // Instead, we add them to a map that only is used by the CPU thread. // When this function is called again later, it will check the map for // the wanted ReadResult before it starts searching through the queue. ReadResult result; auto it = s_result_map.find(id); if (it != s_result_map.end()) { result = std::move(it->second); s_result_map.erase(it); } else { while (true) { while (!s_result_queue.Pop(result)) s_result_queue_expanded.Wait(); if (result.first.id == id) break; else s_result_map.emplace(result.first.id, std::move(result)); } } // We have now obtained the right ReadResult. const ReadRequest& request = result.first; const std::vector<u8>& buffer = result.second; DEBUG_LOG(DVDINTERFACE, "Disc has been read. Real time: %" PRIu64 " us. " "Real time including delay: %" PRIu64 " us. " "Emulated time including delay: %" PRIu64 " us.", request.realtime_done_us - request.realtime_started_us, Common::Timer::GetTimeUs() - request.realtime_started_us, (CoreTiming::GetTicks() - request.time_started_ticks) / (SystemTimers::GetTicksPerSecond() / 1000000)); if (buffer.size() != request.length) { PanicAlertT("The disc could not be read (at 0x%" PRIx64 " - 0x%" PRIx64 ").", request.dvd_offset, request.dvd_offset + request.length); } else { if (request.copy_to_ram) Memory::CopyToEmu(request.output_address, buffer.data(), request.length); } // Notify the emulated software that the command has been executed DVDInterface::FinishExecutingCommand(request.reply_type, DVDInterface::INT_TCINT, cycles_late, buffer); }
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv *env, jobject obj, jobject _surf) { surf = ANativeWindow_fromSurface(env, _surf); // Install our callbacks OSD::AddCallback(OSD::OSD_INIT, ButtonManager::Init); OSD::AddCallback(OSD::OSD_SHUTDOWN, ButtonManager::Shutdown); LogManager::Init(); SConfig::Init(); VideoBackend::PopulateList(); VideoBackend::ActivateBackend(SConfig::GetInstance().m_LocalCoreStartupParameter.m_strVideoBackend); WiimoteReal::LoadSettings(); // Load our Android specific settings IniFile ini; bool onscreencontrols = true; ini.Load(File::GetUserPath(D_CONFIG_IDX) + std::string("Dolphin.ini")); ini.Get("Android", "ScreenControls", &onscreencontrols, true); if (onscreencontrols) OSD::AddCallback(OSD::OSD_ONFRAME, ButtonManager::DrawButtons); // No use running the loop when booting fails if ( BootManager::BootCore( g_filename.c_str() ) ) while (PowerPC::GetState() != PowerPC::CPU_POWERDOWN) updateMainFrameEvent.Wait(); WiimoteReal::Shutdown(); VideoBackend::ClearList(); SConfig::Shutdown(); LogManager::Shutdown(); }
// Regular thread void DSPLLE::dsp_thread(DSPLLE *dsp_lle) { Common::SetCurrentThreadName("DSP thread"); while (dsp_lle->m_bIsRunning) { int cycles = (int)dsp_lle->m_cycle_count; if (cycles > 0) { std::lock_guard<std::mutex> lk(dsp_lle->m_csDSPThreadActive); if (dspjit) { DSPCore_RunCycles(cycles); } else { DSPInterpreter::RunCyclesThread(cycles); } Common::AtomicStore(dsp_lle->m_cycle_count, 0); } else { ppcEvent.Set(); dspEvent.Wait(); } } }
// Regular thread void DSPLLE::DSPThread(DSPLLE* dsp_lle) { Common::SetCurrentThreadName("DSP thread"); while (dsp_lle->m_bIsRunning.IsSet()) { const int cycles = static_cast<int>(dsp_lle->m_cycle_count.load()); if (cycles > 0) { std::lock_guard<std::mutex> dsp_thread_lock(dsp_lle->m_csDSPThreadActive); if (g_dsp_jit) { DSPCore_RunCycles(cycles); } else { DSPInterpreter::RunCyclesThread(cycles); } dsp_lle->m_cycle_count.store(0); } else { ppcEvent.Set(); dspEvent.Wait(); } } }
u32 VideoBackendHardware::Video_AccessEFB(EFBAccessType type, u32 x, u32 y, u32 InputData) { if (s_BackendInitialized && g_ActiveConfig.bEFBAccessEnable) { s_accessEFBArgs.type = type; s_accessEFBArgs.x = x; s_accessEFBArgs.y = y; s_accessEFBArgs.Data = InputData; s_efbAccessRequested.Set(); if (SConfig::GetInstance().m_LocalCoreStartupParameter.bCPUThread) { s_efbAccessReadyEvent.Reset(); if (s_FifoShuttingDown.IsSet()) return 0; s_efbAccessRequested.Set(); s_efbAccessReadyEvent.Wait(); } else VideoFifo_CheckEFBAccess(); return s_AccessEFBResult; } return 0; }
/* This function checks the emulated CPU - GPU distance and may wake up the GPU, * or block the CPU if required. It should be called by the CPU thread regularly. * @ticks The gone emulated CPU time. * @return A good time to call WaitForGpuThread() next. */ static int WaitForGpuThread(int ticks) { const SConfig& param = SConfig::GetInstance(); int old = s_sync_ticks.fetch_add(ticks); int now = old + ticks; // GPU is idle, so stop polling. if (old >= 0 && s_gpu_mainloop.IsDone()) return -1; // Wakeup GPU if (old < param.iSyncGpuMinDistance && now >= param.iSyncGpuMinDistance) RunGpu(); // If the GPU is still sleeping, wait for a longer time if (now < param.iSyncGpuMinDistance) return GPU_TIME_SLOT_SIZE + param.iSyncGpuMinDistance - now; // Wait for GPU if (now >= param.iSyncGpuMaxDistance) s_sync_wakeup_event.Wait(); return GPU_TIME_SLOT_SIZE; }
void WaitUntilIdle() { ASSERT(Core::IsCPUThread()); while (!s_request_queue.Empty()) s_result_queue_expanded.Wait(); StopDVDThread(); StartDVDThread(); }
// Delegate to JIT or interpreter as appropriate. // Handle state changes and stepping. int DSPCore_RunCycles(int cycles) { if (dspjit) { if (g_dsp.external_interrupt_waiting) { DSPCore_CheckExternalInterrupt(); DSPCore_CheckExceptions(); DSPCore_SetExternalInterrupt(false); } cyclesLeft = cycles; DSPCompiledCode pExecAddr = (DSPCompiledCode)dspjit->enterDispatcher; pExecAddr(); if (g_dsp.reset_dspjit_codespace) dspjit->ClearIRAMandDSPJITCodespaceReset(); return cyclesLeft; } while (cycles > 0) { switch (core_state) { case DSPCORE_RUNNING: // Seems to slow things down #if defined(_DEBUG) || defined(DEBUGFAST) cycles = DSPInterpreter::RunCyclesDebug(cycles); #else cycles = DSPInterpreter::RunCycles(cycles); #endif break; case DSPCORE_STEPPING: step_event.Wait(); if (core_state != DSPCORE_STEPPING) continue; DSPInterpreter::Step(); cycles--; DSPHost::UpdateDebugger(); break; case DSPCORE_STOP: break; } } return cycles; }
void DSPLLE::DSP_Update(int cycles) { int dsp_cycles = cycles / 6; if (dsp_cycles <= 0) return; // Sound stream update job has been handled by AudioDMA routine, which is more efficient /* // This gets called VERY OFTEN. The soundstream update might be expensive so only do it 200 times per second or something. int cycles_between_ss_update; if (g_dspInitialize.bWii) cycles_between_ss_update = 121500000 / 200; else cycles_between_ss_update = 81000000 / 200; m_cycle_count += cycles; if (m_cycle_count > cycles_between_ss_update) { while (m_cycle_count > cycles_between_ss_update) m_cycle_count -= cycles_between_ss_update; soundStream->Update(); } */ if (m_bDSPThread) { if (requestDisableThread || NetPlay::IsNetPlayRunning() || Movie::IsMovieActive() || Core::g_want_determinism) { DSP_StopSoundStream(); m_bDSPThread = false; requestDisableThread = false; SConfig::GetInstance().bDSPThread = false; } } // If we're not on a thread, run cycles here. if (!m_bDSPThread) { // ~1/6th as many cycles as the period PPC-side. DSPCore_RunCycles(dsp_cycles); } else { // Wait for DSP thread to complete its cycle. Note: this logic should be thought through. ppcEvent.Wait(); m_cycle_count.fetch_add(dsp_cycles); dspEvent.Set(); } }
void SaveAs(const std::string& filename) { // Pause the core while we save the state bool wasUnpaused = Core::PauseAndLock(true); // Measure the size of the buffer. u8 *ptr = NULL; PointerWrap p(&ptr, PointerWrap::MODE_MEASURE); DoState(p); const size_t buffer_size = reinterpret_cast<size_t>(ptr); // Then actually do the write. { std::lock_guard<std::mutex> lk(g_cs_current_buffer); g_current_buffer.resize(buffer_size); ptr = &g_current_buffer[0]; p.SetMode(PointerWrap::MODE_WRITE); DoState(p); } if (p.GetMode() == PointerWrap::MODE_WRITE) { Core::DisplayMessage("Saving State...", 1000); if ((Movie::IsRecordingInput() || Movie::IsPlayingInput()) && !Movie::IsJustStartingRecordingInputFromSaveState()) Movie::SaveRecording((filename + ".dtm").c_str()); else if (!Movie::IsRecordingInput() && !Movie::IsPlayingInput()) File::Delete(filename + ".dtm"); CompressAndDumpState_args save_args; save_args.buffer_vector = &g_current_buffer; save_args.buffer_mutex = &g_cs_current_buffer; save_args.filename = filename; Flush(); g_save_thread = std::thread(CompressAndDumpState, save_args); g_compressAndDumpStateSyncEvent.Wait(); g_last_filename = filename; } else { // someone aborted the save by changing the mode? Core::DisplayMessage("Unable to Save : Internal DoState Error", 4000); } // Resume the core and disable stepping Core::PauseAndLock(false, wasUnpaused); }
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_dolphinemuactivity_main(JNIEnv *env, jobject obj) { LogManager::Init(); SConfig::Init(); VideoBackend::PopulateList(); VideoBackend::ActivateBackend(SConfig::GetInstance(). m_LocalCoreStartupParameter.m_strVideoBackend); WiimoteReal::LoadSettings(); // No use running the loop when booting fails if (BootManager::BootCore("")) { while (PowerPC::GetState() != PowerPC::CPU_POWERDOWN) updateMainFrameEvent.Wait(); } WiimoteReal::Shutdown(); VideoBackend::ClearList(); SConfig::Shutdown(); LogManager::Shutdown(); }
void DSPLLE::DSP_Update(int cycles) { int dsp_cycles = cycles / 6; if (dsp_cycles <= 0) return; // Sound stream update job has been handled by AudioDMA routine, which is more efficient /* // This gets called VERY OFTEN. The soundstream update might be expensive so only do it 200 times per second or something. int cycles_between_ss_update; if (g_dspInitialize.bWii) cycles_between_ss_update = 121500000 / 200; else cycles_between_ss_update = 81000000 / 200; m_cycle_count += cycles; if (m_cycle_count > cycles_between_ss_update) { while (m_cycle_count > cycles_between_ss_update) m_cycle_count -= cycles_between_ss_update; soundStream->Update(); } */ // If we're not on a thread, run cycles here. if (!m_bDSPThread) { // ~1/6th as many cycles as the period PPC-side. DSPCore_RunCycles(dsp_cycles); } else { // Wait for dsp thread to complete its cycle. Note: this logic should be thought through. ppcEvent.Wait(); Common::AtomicStore(m_cycle_count, dsp_cycles); dspEvent.Set(); } }
u32 VideoBackendHardware::Video_GetQueryResult(PerfQueryType type) { if (!g_perf_query->ShouldEmulate()) { return 0; } // TODO: Is this check sane? if (!g_perf_query->IsFlushed()) { if (SConfig::GetInstance().m_LocalCoreStartupParameter.bCPUThread) { s_perfQueryReadyEvent.Reset(); if (s_FifoShuttingDown.IsSet()) return 0; s_perfQueryRequested.Set(); s_perfQueryReadyEvent.Wait(); } else g_perf_query->FlushResults(); } return g_perf_query->GetQueryResult(type); }
int Fifo_Update(int ticks) { const SConfig& param = SConfig::GetInstance(); if (ticks == 0) { FlushGpu(); return param.iSyncGpuMaxDistance; } // GPU is sleeping, so no need for synchronization if (s_gpu_mainloop.IsDone() || g_use_deterministic_gpu_thread) { if (s_sync_ticks.load() < 0) { int old = s_sync_ticks.fetch_add(ticks); if (old < param.iSyncGpuMinDistance && old + ticks >= param.iSyncGpuMinDistance) RunGpu(); } return param.iSyncGpuMaxDistance; } int old = s_sync_ticks.fetch_add(ticks); if (old < param.iSyncGpuMinDistance && old + ticks >= param.iSyncGpuMinDistance) RunGpu(); if (s_sync_ticks.load() >= param.iSyncGpuMaxDistance) { while (s_sync_ticks.load() > 0) { s_sync_wakeup_event.Wait(); } } return param.iSyncGpuMaxDistance - s_sync_ticks.load(); }
// Delegate to JIT or interpreter as appropriate. // Handle state changes and stepping. int DSPCore_RunCycles(int cycles) { if (g_dsp_jit) { return g_dsp_jit->RunCycles(static_cast<u16>(cycles)); } while (cycles > 0) { switch (core_state) { case State::Running: // Seems to slow things down #if defined(_DEBUG) || defined(DEBUGFAST) cycles = Interpreter::RunCyclesDebug(cycles); #else cycles = Interpreter::RunCycles(cycles); #endif break; case State::Stepping: step_event.Wait(); if (core_state != State::Stepping) continue; Interpreter::Step(); cycles--; Host::UpdateDebugger(); break; case State::Stopped: break; } } return cycles; }
int main(int argc, char* argv[]) { int ch, help = 0; struct option longopts[] = { { "exec", no_argument, nullptr, 'e' }, { "help", no_argument, nullptr, 'h' }, { "version", no_argument, nullptr, 'v' }, { nullptr, 0, nullptr, 0 } }; while ((ch = getopt_long(argc, argv, "eh?v", longopts, 0)) != -1) { switch (ch) { case 'e': break; case 'h': case '?': help = 1; break; case 'v': fprintf(stderr, "%s\n", scm_rev_str); return 1; } } if (help == 1 || argc == optind) { fprintf(stderr, "%s\n\n", scm_rev_str); fprintf(stderr, "A multi-platform GameCube/Wii emulator\n\n"); fprintf(stderr, "Usage: %s [-e <file>] [-h] [-v]\n", argv[0]); fprintf(stderr, " -e, --exec Load the specified file\n"); fprintf(stderr, " -h, --help Show this help message\n"); fprintf(stderr, " -v, --help Print version and exit\n"); return 1; } platform = GetPlatform(); if (!platform) { fprintf(stderr, "No platform found\n"); return 1; } LogManager::Init(); SConfig::Init(); VideoBackend::PopulateList(); VideoBackend::ActivateBackend(SConfig::GetInstance(). m_LocalCoreStartupParameter.m_strVideoBackend); WiimoteReal::LoadSettings(); platform->Init(); if (!BootManager::BootCore(argv[optind])) { fprintf(stderr, "Could not boot %s\n", argv[optind]); return 1; } while (!Core::IsRunning()) updateMainFrameEvent.Wait(); platform->MainLoop(); Core::Stop(); while (PowerPC::GetState() != PowerPC::CPU_POWERDOWN) updateMainFrameEvent.Wait(); platform->Shutdown(); Core::Shutdown(); WiimoteReal::Shutdown(); VideoBackend::ClearList(); SConfig::Shutdown(); LogManager::Shutdown(); delete platform; return 0; }
void X11_MainLoop() { bool fullscreen = SConfig::GetInstance().m_LocalCoreStartupParameter.bFullscreen; while (Core::GetState() == Core::CORE_UNINITIALIZED) updateMainFrameEvent.Wait(); Display *dpy = XOpenDisplay(0); Window win = (Window)Core::GetWindowHandle(); XSelectInput(dpy, win, KeyPressMask | FocusChangeMask); if (SConfig::GetInstance().m_LocalCoreStartupParameter.bDisableScreenSaver) X11Utils::InhibitScreensaver(dpy, win, true); #if defined(HAVE_XRANDR) && HAVE_XRANDR X11Utils::XRRConfiguration *XRRConfig = new X11Utils::XRRConfiguration(dpy, win); #endif Cursor blankCursor = None; if (SConfig::GetInstance().m_LocalCoreStartupParameter.bHideCursor) { // make a blank cursor Pixmap Blank; XColor DummyColor; char ZeroData[1] = {0}; Blank = XCreateBitmapFromData (dpy, win, ZeroData, 1, 1); blankCursor = XCreatePixmapCursor(dpy, Blank, Blank, &DummyColor, &DummyColor, 0, 0); XFreePixmap (dpy, Blank); XDefineCursor(dpy, win, blankCursor); } if (fullscreen) { X11Utils::EWMH_Fullscreen(dpy, _NET_WM_STATE_TOGGLE); #if defined(HAVE_XRANDR) && HAVE_XRANDR XRRConfig->ToggleDisplayMode(True); #endif } // The actual loop while (running) { XEvent event; KeySym key; for (int num_events = XPending(dpy); num_events > 0; num_events--) { XNextEvent(dpy, &event); switch(event.type) { case KeyPress: key = XLookupKeysym((XKeyEvent*)&event, 0); if (key == XK_Escape) { if (Core::GetState() == Core::CORE_RUN) { if (SConfig::GetInstance().m_LocalCoreStartupParameter.bHideCursor) XUndefineCursor(dpy, win); Core::SetState(Core::CORE_PAUSE); } else { if (SConfig::GetInstance().m_LocalCoreStartupParameter.bHideCursor) XDefineCursor(dpy, win, blankCursor); Core::SetState(Core::CORE_RUN); } } else if ((key == XK_Return) && (event.xkey.state & Mod1Mask)) { fullscreen = !fullscreen; X11Utils::EWMH_Fullscreen(dpy, _NET_WM_STATE_TOGGLE); #if defined(HAVE_XRANDR) && HAVE_XRANDR XRRConfig->ToggleDisplayMode(fullscreen); #endif } else if (key >= XK_F1 && key <= XK_F8) { int slot_number = key - XK_F1 + 1; if (event.xkey.state & ShiftMask) State::Save(slot_number); else State::Load(slot_number); } else if (key == XK_F9) Core::SaveScreenShot(); else if (key == XK_F11) State::LoadLastSaved(); else if (key == XK_F12) { if (event.xkey.state & ShiftMask) State::UndoLoadState(); else State::UndoSaveState(); } break; case FocusIn: rendererHasFocus = true; if (SConfig::GetInstance().m_LocalCoreStartupParameter.bHideCursor && Core::GetState() != Core::CORE_PAUSE) XDefineCursor(dpy, win, blankCursor); break; case FocusOut: rendererHasFocus = false; if (SConfig::GetInstance().m_LocalCoreStartupParameter.bHideCursor) XUndefineCursor(dpy, win); break; } } if (!fullscreen) { Window winDummy; unsigned int borderDummy, depthDummy; XGetGeometry(dpy, win, &winDummy, &SConfig::GetInstance().m_LocalCoreStartupParameter.iRenderWindowXPos, &SConfig::GetInstance().m_LocalCoreStartupParameter.iRenderWindowYPos, (unsigned int *)&SConfig::GetInstance().m_LocalCoreStartupParameter.iRenderWindowWidth, (unsigned int *)&SConfig::GetInstance().m_LocalCoreStartupParameter.iRenderWindowHeight, &borderDummy, &depthDummy); } usleep(100000); } #if defined(HAVE_XRANDR) && HAVE_XRANDR delete XRRConfig; #endif if (SConfig::GetInstance().m_LocalCoreStartupParameter.bDisableScreenSaver) X11Utils::InhibitScreensaver(dpy, win, false); if (SConfig::GetInstance().m_LocalCoreStartupParameter.bHideCursor) XFreeCursor(dpy, blankCursor); XCloseDisplay(dpy); Core::Stop(); }
int main(int argc, char* argv[]) { auto parser = CommandLineParse::CreateParser(CommandLineParse::ParserOptions::OmitGUIOptions); optparse::Values& options = CommandLineParse::ParseArguments(parser.get(), argc, argv); std::vector<std::string> args = parser->args(); std::string boot_filename; if (options.is_set("exec")) { boot_filename = static_cast<const char*>(options.get("exec")); } else if (args.size()) { boot_filename = args.front(); args.erase(args.begin()); } else { parser->print_help(); return 0; } std::string user_directory; if (options.is_set("user")) { user_directory = static_cast<const char*>(options.get("user")); } platform = GetPlatform(); if (!platform) { fprintf(stderr, "No platform found\n"); return 1; } UICommon::SetUserDirectory(user_directory); UICommon::Init(); Core::SetOnStoppedCallback([]() { s_running.Clear(); }); platform->Init(); // Shut down cleanly on SIGINT and SIGTERM struct sigaction sa; sa.sa_handler = signal_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESETHAND; sigaction(SIGINT, &sa, nullptr); sigaction(SIGTERM, &sa, nullptr); DolphinAnalytics::Instance()->ReportDolphinStart("nogui"); if (!BootManager::BootCore(BootParameters::GenerateFromFile(boot_filename))) { fprintf(stderr, "Could not boot %s\n", boot_filename.c_str()); return 1; } while (!Core::IsRunning() && s_running.IsSet()) { Core::HostDispatchJobs(); updateMainFrameEvent.Wait(); } if (s_running.IsSet()) platform->MainLoop(); Core::Stop(); Core::Shutdown(); platform->Shutdown(); UICommon::Shutdown(); delete platform; return 0; }
int main(int argc, char* argv[]) { int ch, help = 0; struct option longopts[] = { { "exec", no_argument, nullptr, 'e' }, { "help", no_argument, nullptr, 'h' }, { "version", no_argument, nullptr, 'v' }, { nullptr, 0, nullptr, 0 } }; while ((ch = getopt_long(argc, argv, "eh?v", longopts, 0)) != -1) { switch (ch) { case 'e': break; case 'h': case '?': help = 1; break; case 'v': fprintf(stderr, "%s\n", scm_rev_str); return 1; } } if (help == 1 || argc == optind) { fprintf(stderr, "%s\n\n", scm_rev_str); fprintf(stderr, "A multi-platform GameCube/Wii emulator\n\n"); fprintf(stderr, "Usage: %s [-e <file>] [-h] [-v]\n", argv[0]); fprintf(stderr, " -e, --exec Load the specified file\n"); fprintf(stderr, " -h, --help Show this help message\n"); fprintf(stderr, " -v, --help Print version and exit\n"); return 1; } platform = GetPlatform(); if (!platform) { fprintf(stderr, "No platform found\n"); return 1; } UICommon::SetUserDirectory(""); // Auto-detect user folder UICommon::Init(); platform->Init(); if (!BootManager::BootCore(argv[optind])) { fprintf(stderr, "Could not boot %s\n", argv[optind]); return 1; } while (!Core::IsRunning()) updateMainFrameEvent.Wait(); platform->MainLoop(); Core::Stop(); while (PowerPC::GetState() != PowerPC::CPU_POWERDOWN) updateMainFrameEvent.Wait(); Core::Shutdown(); platform->Shutdown(); UICommon::Shutdown(); delete platform; return 0; }