void Emulator::Resume() { // Get pause start time const u64 time = m_pause_start_time.exchange(0); // Try to increment summary pause time if (time) { m_pause_amend_time += get_system_time() - time; } // Try to resume if (!m_status.compare_and_swap_test(Paused, Running)) { return; } if (!time) { LOG_ERROR(GENERAL, "Emulator::Resume() error: concurrent access"); } SendDbgCommand(DID_RESUME_EMU); for (auto& thread : get_all_cpu_threads()) { thread->state -= cpu_state::dbg_global_pause; thread->safe_notify(); } rpcs3::on_resume()(); SendDbgCommand(DID_RESUMED_EMU); }
void Emulator::Run() { if (!IsReady()) { Load(); if(!IsReady()) return; } if (IsRunning()) Stop(); if (IsPaused()) { Resume(); return; } rpcs3::on_run()(); SendDbgCommand(DID_START_EMU); m_pause_start_time = 0; m_pause_amend_time = 0; m_status = Running; idm::select<PPUThread, SPUThread, RawSPUThread, ARMv7Thread>([](u32, cpu_thread& cpu) { cpu.state -= cpu_state::stop; cpu->lock_notify(); }); SendDbgCommand(DID_STARTED_EMU); }
bool Emulator::Pause() { const u64 start = get_system_time(); // Try to pause if (!m_status.compare_and_swap_test(Running, Paused)) { return false; } rpcs3::on_pause()(); // Update pause start time if (m_pause_start_time.exchange(start)) { LOG_ERROR(GENERAL, "Emulator::Pause() error: concurrent access"); } SendDbgCommand(DID_PAUSE_EMU); for (auto& thread : get_all_cpu_threads()) { thread->state += cpu_state::dbg_global_pause; } SendDbgCommand(DID_PAUSED_EMU); return true; }
void Emulator::Run() { if (!IsReady()) { Load(); if(!IsReady()) return; } if (IsRunning()) Stop(); if (IsPaused()) { Resume(); return; } rpcs3::on_run()(); SendDbgCommand(DID_START_EMU); m_pause_start_time = 0; m_pause_amend_time = 0; m_status = Running; for (auto& thread : get_all_cpu_threads()) { thread->state -= cpu_state::stop; thread->safe_notify(); } SendDbgCommand(DID_STARTED_EMU); }
bool Emulator::Pause() { const u64 start = get_system_time(); // Try to pause if (!m_status.compare_and_swap_test(Running, Paused)) { return false; } rpcs3::on_pause()(); // Update pause start time if (m_pause_start_time.exchange(start)) { LOG_ERROR(GENERAL, "Emulator::Pause() error: concurrent access"); } SendDbgCommand(DID_PAUSE_EMU); idm::select<PPUThread, SPUThread, RawSPUThread, ARMv7Thread>([](u32, cpu_thread& cpu) { cpu.state += cpu_state::dbg_global_pause; }); SendDbgCommand(DID_PAUSED_EMU); return true; }
void Emulator::Resume() { // Get pause start time const u64 time = m_pause_start_time.exchange(0); // Try to increment summary pause time if (time) { m_pause_amend_time += get_system_time() - time; } // Try to resume if (!m_status.compare_and_swap_test(Paused, Running)) { return; } if (!time) { LOG_ERROR(GENERAL, "Emulator::Resume() error: concurrent access"); } SendDbgCommand(DID_RESUME_EMU); idm::select<PPUThread, SPUThread, RawSPUThread, ARMv7Thread>([](u32, cpu_thread& cpu) { cpu.state -= cpu_state::dbg_global_pause; cpu->lock_notify(); }); rpcs3::on_resume()(); SendDbgCommand(DID_RESUMED_EMU); }
void CPUThread::pause() { SendDbgCommand(DID_PAUSE_THREAD, this); m_state |= CPU_STATE_PAUSED; SendDbgCommand(DID_PAUSED_THREAD, this); }
void Emulator::Stop() { if (m_status.exchange(Stopped) == Stopped) { return; } LOG_NOTICE(GENERAL, "Stopping emulator..."); rpcs3::on_stop()(); SendDbgCommand(DID_STOP_EMU); { LV2_LOCK; idm::select<PPUThread, SPUThread, RawSPUThread, ARMv7Thread>([](u32, cpu_thread& cpu) { cpu.state += cpu_state::dbg_global_stop; cpu->lock(); cpu->set_exception(std::make_exception_ptr(EmulationStopped())); cpu->unlock(); cpu->notify(); }); } LOG_NOTICE(GENERAL, "All threads signaled..."); while (g_thread_count) { m_cb.process_events(); std::this_thread::sleep_for(10ms); } LOG_NOTICE(GENERAL, "All threads stopped..."); idm::clear(); fxm::clear(); LOG_NOTICE(GENERAL, "Objects cleared..."); GetCallbackManager().Clear(); RSXIOMem.Clear(); vm::close(); SendDbgCommand(DID_STOPPED_EMU); if (g_cfg_autoexit) { GetCallbacks().exit(); } else { Init(); } }
void CPUThread::run() { SendDbgCommand(DID_START_THREAD, this); init_stack(); init_regs(); do_run(); SendDbgCommand(DID_STARTED_THREAD, this); }
void Emulator::Stop() { if (m_status.exchange(Stopped) == Stopped) { return; } LOG_NOTICE(GENERAL, "Stopping emulator..."); rpcs3::on_stop()(); SendDbgCommand(DID_STOP_EMU); { LV2_LOCK; for (auto& thread : get_all_cpu_threads()) { thread->state += cpu_state::dbg_global_stop; thread->safe_notify(); } } LOG_NOTICE(GENERAL, "All threads signaled..."); while (g_thread_count) { m_cb.process_events(); std::this_thread::sleep_for(10ms); } LOG_NOTICE(GENERAL, "All threads stopped..."); idm::clear(); fxm::clear(); LOG_NOTICE(GENERAL, "Objects cleared..."); GetCallbackManager().Clear(); RSXIOMem.Clear(); vm::close(); SendDbgCommand(DID_STOPPED_EMU); if (g_cfg_autoexit) { GetCallbacks().exit(); } else { Init(); } }
void CPUThread::ExecOnce() { m_is_step = true; SendDbgCommand(DID_EXEC_THREAD, this); m_status = Running; ThreadBase::Start(); ThreadBase::Stop(true,false); m_status = Paused; SendDbgCommand(DID_PAUSE_THREAD, this); SendDbgCommand(DID_PAUSED_THREAD, this); }
void CPUThread::Pause() { if(!IsRunning()) return; SendDbgCommand(DID_PAUSE_THREAD, this); m_status = Paused; DoPause(); Emu.CheckStatus(); // ThreadBase::Stop(); // "Abort() called" exception SendDbgCommand(DID_PAUSED_THREAD, this); }
void CPUThread::Resume() { if(!IsPaused()) return; SendDbgCommand(DID_RESUME_THREAD, this); m_status = Running; DoResume(); Emu.CheckStatus(); ThreadBase::Start(); SendDbgCommand(DID_RESUMED_THREAD, this); }
void CPUThread::resume() { SendDbgCommand(DID_RESUME_THREAD, this); { // lock for reliable notification std::lock_guard<std::mutex> lock(mutex); m_state &= ~CPU_STATE_PAUSED; cv.notify_one(); } SendDbgCommand(DID_RESUMED_THREAD, this); }
void CPUThreadManager::RemoveThread(u32 id) { std::lock_guard<std::mutex> lock(m_mutex); std::shared_ptr<CPUThread> thr; u32 thread_index = 0; for (u32 i = 0; i < m_threads.size(); ++i) { if (m_threads[i]->GetId() != id) continue; thr = m_threads[i]; thread_index = i; } if (thr) { SendDbgCommand(DID_REMOVE_THREAD, thr.get()); thr->Close(); m_threads.erase(m_threads.begin() + thread_index); if (thr->GetType() == CPU_THREAD_RAW_SPU) { assert(thr->index < m_raw_spu.size()); m_raw_spu[thr->index] = nullptr; } } // Removing the ID should trigger the actual deletion of the thread Emu.GetIdManager().RemoveID<CPUThread>(id); Emu.CheckStatus(); }
void CPUThreadManager::RemoveThread(const u32 id) { std::lock_guard<std::mutex> lock(m_mtx_thread); CPUThread* thr = nullptr; u32 thread_index = 0; for (u32 i = 0; i < m_threads.size(); ++i) { if (m_threads[i]->m_wait_thread_id == id) { m_threads[i]->Wait(false); m_threads[i]->m_wait_thread_id = -1; } if (m_threads[i]->GetId() != id) continue; thr = m_threads[i]; thread_index = i; } if (thr) { SendDbgCommand(DID_REMOVE_THREAD, thr); thr->Close(); m_threads.erase(m_threads.begin() + thread_index); } // Removing the ID should trigger the actual deletion of the thread Emu.GetIdManager().RemoveID(id); Emu.CheckStatus(); }
void CPUThread::Exec() { m_is_step = false; SendDbgCommand(DID_EXEC_THREAD, this); if(IsRunning()) ThreadBase::Start(); }
void CPUThread::Stop() { if(IsStopped()) return; SendDbgCommand(DID_STOP_THREAD, this); m_status = Stopped; if(static_cast<NamedThreadBase*>(this) != GetCurrentNamedThread()) { ThreadBase::Stop(); } Emu.CheckStatus(); SendDbgCommand(DID_STOPED_THREAD, this); }
CPUThread::~CPUThread() { if (joinable()) { throw EXCEPTION("Thread not joined"); } SendDbgCommand(DID_REMOVE_THREAD, this); }
void CPUThread::Run() { if(!IsStopped()) Stop(); Reset(); SendDbgCommand(DID_START_THREAD, this); m_status = Running; SetPc(entry); InitStack(); InitRegs(); DoRun(); Emu.CheckStatus(); SendDbgCommand(DID_STARTED_THREAD, this); }
int cellSysutilUnregisterCallback(int slot) { cellSysutil->Warning("cellSysutilUnregisterCallback(slot=%d)", slot); Emu.GetCallbackManager().m_exit_callback.Unregister(slot); SendDbgCommand(DID_UNREGISTRED_CALLBACK); return CELL_OK; }
int cellSysutilRegisterCallback(int slot, u64 func_addr, u64 userdata) { cellSysutil->Warning("cellSysutilRegisterCallback(slot=%d, func_addr=0x%llx, userdata=0x%llx)", slot, func_addr, userdata); Emu.GetCallbackManager().m_exit_callback.Register(slot, func_addr, userdata); SendDbgCommand(DID_REGISTRED_CALLBACK); return CELL_OK; }
void CPUThread::Stop() { if(IsStopped()) return; SendDbgCommand(DID_STOP_THREAD, this); m_status = Stopped; if(CPUThread* thr = GetCurrentCPUThread()) { if(thr->GetId() != GetId()) ThreadBase::Stop(); } else ThreadBase::Stop(); Emu.CheckStatus(); SendDbgCommand(DID_STOPED_THREAD, this); }
void CPUThread::stop() { SendDbgCommand(DID_STOP_THREAD, this); if (is_current()) { throw CPUThreadStop{}; } else { // lock for reliable notification std::lock_guard<std::mutex> lock(mutex); m_state |= CPU_STATE_STOPPED; cv.notify_one(); } SendDbgCommand(DID_STOPED_THREAD, this); }
void CPUThread::exec() { SendDbgCommand(DID_EXEC_THREAD, this); { // lock for reliable notification std::lock_guard<std::mutex> lock(mutex); m_state &= ~CPU_STATE_STOPPED; cv.notify_one(); } }
std::shared_ptr<CPUThread> CPUThreadManager::AddThread(CPUThreadType type) { std::lock_guard<std::mutex> lock(m_mutex); std::shared_ptr<CPUThread> new_thread; switch(type) { case CPU_THREAD_PPU: { new_thread.reset(new PPUThread()); break; } case CPU_THREAD_SPU: { new_thread.reset(new SPUThread()); break; } case CPU_THREAD_RAW_SPU: { for (u32 i = 0; i < m_raw_spu.size(); i++) { if (!m_raw_spu[i]) { new_thread.reset(new RawSPUThread()); new_thread->index = i; m_raw_spu[i] = new_thread; break; } } break; } case CPU_THREAD_ARMv7: { new_thread.reset(new ARMv7Thread()); break; } default: assert(0); } if (new_thread) { new_thread->SetId(Emu.GetIdManager().GetNewID(new_thread)); m_threads.push_back(new_thread); SendDbgCommand(DID_CREATE_THREAD, new_thread.get()); } return new_thread; }
CPUThread& CPUThreadManager::AddThread(CPUThreadType type) { std::lock_guard<std::mutex> lock(m_mtx_thread); CPUThread* new_thread; switch(type) { case CPU_THREAD_PPU: new_thread = new PPUThread(); break; case CPU_THREAD_SPU: new_thread = new SPUThread(); break; case CPU_THREAD_RAW_SPU: new_thread = new RawSPUThread(m_raw_spu_num++); break; case CPU_THREAD_ARMv7: new_thread = new ARMv7Thread(); break; default: assert(0); } new_thread->SetId(Emu.GetIdManager().GetNewID(fmt::Format("%s Thread", new_thread->GetTypeString().c_str()), new_thread)); m_threads.push_back(new_thread); SendDbgCommand(DID_CREATE_THREAD, new_thread); return *new_thread; }
CPUThread::CPUThread(CPUThreadType type, const std::string& name, std::function<std::string()> thread_name) : m_state( { CPU_STATE_STOPPED }) , m_id(Emu.GetIdManager().get_current_id()) , m_type(type) , m_name(name) { start(std::move(thread_name), [this] { SendDbgCommand(DID_CREATE_THREAD, this); std::unique_lock<std::mutex> lock(mutex); // check thread status while (joinable() && is_alive()) { CHECK_EMU_STATUS; // check stop status if (!is_stopped()) { if (lock) lock.unlock(); try { task(); } catch (CPUThreadReturn) { ; } catch (CPUThreadStop) { m_state |= CPU_STATE_STOPPED; } catch (CPUThreadExit) { m_state |= CPU_STATE_DEAD; break; } catch (const fmt::exception&) { dump_info(); throw; } m_state &= ~CPU_STATE_RETURN; continue; } if (!lock) { lock.lock(); continue; } cv.wait(lock); } }); }
void Emulator::Load() { Stop(); try { Init(); if (!fs::is_file(m_path)) { LOG_ERROR(LOADER, "File not found: %s", m_path); return; } const std::string& elf_dir = fs::get_parent_dir(m_path); if (IsSelf(m_path)) { const std::size_t elf_ext_pos = m_path.find_last_of('.'); const std::string& elf_ext = fmt::to_upper(m_path.substr(elf_ext_pos != -1 ? elf_ext_pos : m_path.size())); const std::string& elf_name = m_path.substr(elf_dir.size()); if (elf_name.compare(elf_name.find_last_of("/\\", -1, 2) + 1, 9, "EBOOT.BIN", 9) == 0) { m_path.erase(m_path.size() - 9, 1); // change EBOOT.BIN to BOOT.BIN } else if (elf_ext == ".SELF" || elf_ext == ".SPRX") { m_path.erase(m_path.size() - 4, 1); // change *.self to *.elf, *.sprx to *.prx } else { m_path += ".decrypted.elf"; } if (!DecryptSelf(m_path, elf_dir + elf_name)) { LOG_ERROR(LOADER, "Failed to decrypt %s", elf_dir + elf_name); return; } } SetCPUThreadStop(0); LOG_NOTICE(LOADER, "Path: %s", m_path); // Load custom config if (fs::file cfg_file{ m_path + ".yml" }) { LOG_NOTICE(LOADER, "Custom config: %s.yml", m_path); cfg::root.from_string(cfg_file.to_string()); } const fs::file elf_file(m_path); ppu_exec_object ppu_exec; ppu_prx_object ppu_prx; spu_exec_object spu_exec; arm_exec_object arm_exec; if (!elf_file) { LOG_ERROR(LOADER, "Failed to open %s", m_path); return; } else if (ppu_exec.open(elf_file) == elf_error::ok) { // PS3 executable m_status = Ready; vm::ps3::init(); if (m_elf_path.empty()) { m_elf_path = "/host_root/" + m_path; LOG_NOTICE(LOADER, "Elf path: %s", m_elf_path); } // Load PARAM.SFO const auto _psf = psf::load_object(fs::file(elf_dir + "/../PARAM.SFO")); m_title = psf::get_string(_psf, "TITLE", m_path); m_title_id = psf::get_string(_psf, "TITLE_ID"); fs::get_data_dir(m_title_id, m_path); LOG_NOTICE(LOADER, "Title: %s", GetTitle()); LOG_NOTICE(LOADER, "Serial: %s", GetTitleID()); // Mount all devices const std::string& emu_dir_ = g_cfg_vfs_emulator_dir; const std::string& emu_dir = emu_dir_.empty() ? fs::get_executable_dir() : emu_dir_; const std::string& bdvd_dir = g_cfg_vfs_dev_bdvd; const std::string& home_dir = g_cfg_vfs_app_home; vfs::mount("dev_hdd0", fmt::replace_all(g_cfg_vfs_dev_hdd0, "$(EmulatorDir)", emu_dir)); vfs::mount("dev_hdd1", fmt::replace_all(g_cfg_vfs_dev_hdd1, "$(EmulatorDir)", emu_dir)); vfs::mount("dev_flash", fmt::replace_all(g_cfg_vfs_dev_flash, "$(EmulatorDir)", emu_dir)); vfs::mount("dev_usb", fmt::replace_all(g_cfg_vfs_dev_usb000, "$(EmulatorDir)", emu_dir)); vfs::mount("dev_usb000", fmt::replace_all(g_cfg_vfs_dev_usb000, "$(EmulatorDir)", emu_dir)); vfs::mount("app_home", home_dir.empty() ? elf_dir + '/' : fmt::replace_all(home_dir, "$(EmulatorDir)", emu_dir)); // Mount /dev_bdvd/ if necessary if (bdvd_dir.empty() && fs::is_file(elf_dir + "/../../PS3_DISC.SFB")) { const auto dir_list = fmt::split(elf_dir, { "/", "\\" }); // Check latest two directories if (dir_list.size() >= 2 && dir_list.back() == "USRDIR" && *(dir_list.end() - 2) == "PS3_GAME") { vfs::mount("dev_bdvd", elf_dir.substr(0, elf_dir.length() - 15)); } else { vfs::mount("dev_bdvd", elf_dir + "/../../"); } LOG_NOTICE(LOADER, "Disc: %s", vfs::get("/dev_bdvd")); } else if (bdvd_dir.size()) { vfs::mount("dev_bdvd", fmt::replace_all(bdvd_dir, "$(EmulatorDir)", emu_dir)); } // Mount /host_root/ if necessary if (g_cfg_vfs_allow_host_root) { vfs::mount("host_root", {}); } LOG_NOTICE(LOADER, "Used configuration:\n%s\n", cfg::root.to_string()); ppu_load_exec(ppu_exec); fxm::import<GSRender>(PURE_EXPR(Emu.GetCallbacks().get_gs_render())); // TODO: must be created in appropriate sys_rsx syscall } else if (ppu_prx.open(elf_file) == elf_error::ok) { // PPU PRX (experimental) m_status = Ready; vm::ps3::init(); ppu_load_prx(ppu_prx); } else if (spu_exec.open(elf_file) == elf_error::ok) { // SPU executable (experimental) m_status = Ready; vm::ps3::init(); spu_load_exec(spu_exec); } else if (arm_exec.open(elf_file) == elf_error::ok) { // ARMv7 executable m_status = Ready; vm::psv::init(); arm_load_exec(arm_exec); } else { LOG_ERROR(LOADER, "Invalid or unsupported file format: %s", m_path); LOG_WARNING(LOADER, "** ppu_exec -> %s", ppu_exec.get_error()); LOG_WARNING(LOADER, "** ppu_prx -> %s", ppu_prx.get_error()); LOG_WARNING(LOADER, "** spu_exec -> %s", spu_exec.get_error()); LOG_WARNING(LOADER, "** arm_exec -> %s", arm_exec.get_error()); return; } debug::autopause::reload(); SendDbgCommand(DID_READY_EMU); if (g_cfg_autostart) Run(); } catch (const std::exception& e) { LOG_FATAL(LOADER, "%s thrown: %s", typeid(e).name(), e.what()); Stop(); } }
void Emulator::Load() { Stop(); try { Init(); if (!fs::is_file(m_path)) { LOG_ERROR(LOADER, "File not found: %s", m_path); return; } const std::string& elf_dir = fs::get_parent_dir(m_path); if (IsSelf(m_path)) { const std::size_t elf_ext_pos = m_path.find_last_of('.'); const std::string& elf_ext = fmt::to_upper(m_path.substr(elf_ext_pos != -1 ? elf_ext_pos : m_path.size())); const std::string& elf_name = m_path.substr(elf_dir.size()); if (elf_name.compare(elf_name.find_last_of("/\\", -1, 2) + 1, 9, "EBOOT.BIN", 9) == 0) { m_path.erase(m_path.size() - 9, 1); // change EBOOT.BIN to BOOT.BIN } else if (elf_ext == ".SELF" || elf_ext == ".SPRX") { m_path.erase(m_path.size() - 4, 1); // change *.self to *.elf, *.sprx to *.prx } else { m_path += ".decrypted.elf"; } if (!DecryptSelf(m_path, elf_dir + elf_name)) { LOG_ERROR(LOADER, "Failed to decrypt %s", elf_dir + elf_name); return; } } ResetInfo(); LOG_NOTICE(LOADER, "Path: %s", m_path); // Load custom config if (fs::file cfg_file{ m_path + ".yml" }) { LOG_NOTICE(LOADER, "Custom config: %s.yml", m_path); cfg::root.from_string(cfg_file.to_string()); } const fs::file elf_file(m_path); ppu_exec_loader ppu_exec; ppu_prx_loader ppu_prx; spu_exec_loader spu_exec; arm_exec_loader arm_exec; if (!elf_file) { LOG_ERROR(LOADER, "Failed to open %s", m_path); return; } else if (ppu_exec.open(elf_file) == elf_error::ok) { // PS3 executable m_status = Ready; vm::ps3::init(); if (m_elf_path.empty()) { m_elf_path = "/host_root/" + m_path; LOG_NOTICE(LOADER, "Elf path: %s", m_elf_path); } // Load PARAM.SFO m_psf = psf::load_object(fs::file(elf_dir + "/../PARAM.SFO")); m_title = psf::get_string(m_psf, "TITLE", m_path); m_title_id = psf::get_string(m_psf, "TITLE_ID"); LOG_NOTICE(LOADER, "Title: %s", GetTitle()); LOG_NOTICE(LOADER, "Serial: %s", GetTitleID()); LOG_NOTICE(LOADER, ""); LOG_NOTICE(LOADER, "Used configuration:\n%s\n", cfg::root.to_string()); // Mount /dev_bdvd/ if (g_cfg_vfs_dev_bdvd.size() == 0 && fs::is_file(elf_dir + "/../../PS3_DISC.SFB")) { const auto dir_list = fmt::split(elf_dir, { "/", "\\" }); // Check latest two directories if (dir_list.size() >= 2 && dir_list.back() == "USRDIR" && *(dir_list.end() - 2) == "PS3_GAME") { g_cfg_vfs_dev_bdvd = elf_dir.substr(0, elf_dir.length() - 15); } else { g_cfg_vfs_dev_bdvd = elf_dir + "/../../"; } } // Mount /app_home/ if (g_cfg_vfs_app_home.size() == 0) { g_cfg_vfs_app_home = elf_dir + '/'; } vfs::dump(); ppu_exec.load(); Emu.GetCallbackManager().Init(); fxm::import<GSRender>(PURE_EXPR(Emu.GetCallbacks().get_gs_render())); // TODO: must be created in appropriate sys_rsx syscall } else if (ppu_prx.open(elf_file) == elf_error::ok) { // PPU PRX (experimental) m_status = Ready; vm::ps3::init(); ppu_prx.load(); GetCallbackManager().Init(); } else if (spu_exec.open(elf_file) == elf_error::ok) { // SPU executable (experimental) m_status = Ready; vm::ps3::init(); spu_exec.load(); } else if (arm_exec.open(elf_file) == elf_error::ok) { // ARMv7 executable m_status = Ready; vm::psv::init(); arm_exec.load(); } else { LOG_ERROR(LOADER, "Invalid or unsupported file format: %s", m_path); LOG_WARNING(LOADER, "** ppu_exec_loader -> %s", bijective_find<elf_error>(ppu_exec, "???")); LOG_WARNING(LOADER, "** ppu_prx_loader -> %s", bijective_find<elf_error>(ppu_prx, "???")); LOG_WARNING(LOADER, "** spu_exec_loader -> %s", bijective_find<elf_error>(spu_exec, "???")); LOG_WARNING(LOADER, "** arm_exec_loader -> %s", bijective_find<elf_error>(arm_exec, "???")); return; } debug::autopause::reload(); SendDbgCommand(DID_READY_EMU); if (g_cfg_autostart) Run(); } catch (const std::exception& e) { LOG_FATAL(LOADER, "%s thrown: %s", typeid(e).name(), e.what()); Stop(); } }