Ejemplo n.º 1
0
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);
}
Ejemplo n.º 2
0
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);
}
Ejemplo n.º 3
0
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;
}
Ejemplo n.º 4
0
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);
}
Ejemplo n.º 5
0
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;
}
Ejemplo n.º 6
0
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);
}
Ejemplo n.º 7
0
void CPUThread::pause()
{
    SendDbgCommand(DID_PAUSE_THREAD, this);

    m_state |= CPU_STATE_PAUSED;

    SendDbgCommand(DID_PAUSED_THREAD, this);
}
Ejemplo n.º 8
0
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();
	}
}
Ejemplo n.º 9
0
void CPUThread::run()
{
    SendDbgCommand(DID_START_THREAD, this);

    init_stack();
    init_regs();
    do_run();

    SendDbgCommand(DID_STARTED_THREAD, this);
}
Ejemplo n.º 10
0
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();
	}
}
Ejemplo n.º 11
0
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);
}
Ejemplo n.º 12
0
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);
}
Ejemplo n.º 13
0
void CPUThread::Resume()
{
	if(!IsPaused()) return;

	SendDbgCommand(DID_RESUME_THREAD, this);

	m_status = Running;
	DoResume();
	Emu.CheckStatus();

	ThreadBase::Start();

	SendDbgCommand(DID_RESUMED_THREAD, this);
}
Ejemplo n.º 14
0
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);
}
Ejemplo n.º 15
0
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();
}
Ejemplo n.º 16
0
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();
}
Ejemplo n.º 17
0
void CPUThread::Exec()
{
	m_is_step = false;
	SendDbgCommand(DID_EXEC_THREAD, this);

	if(IsRunning())
		ThreadBase::Start();
}
Ejemplo n.º 18
0
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);
}
Ejemplo n.º 19
0
CPUThread::~CPUThread()
{
    if (joinable())
    {
        throw EXCEPTION("Thread not joined");
    }

    SendDbgCommand(DID_REMOVE_THREAD, this);
}
Ejemplo n.º 20
0
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);
}
Ejemplo n.º 21
0
int cellSysutilUnregisterCallback(int slot)
{
	cellSysutil->Warning("cellSysutilUnregisterCallback(slot=%d)", slot);

	Emu.GetCallbackManager().m_exit_callback.Unregister(slot);

	SendDbgCommand(DID_UNREGISTRED_CALLBACK);

	return CELL_OK;
}
Ejemplo n.º 22
0
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;
}
Ejemplo n.º 23
0
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);
}
Ejemplo n.º 24
0
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);
}
Ejemplo n.º 25
0
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();
    }
}
Ejemplo n.º 26
0
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;
}
Ejemplo n.º 27
0
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;
}
Ejemplo n.º 28
0
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);
        }
    });
}
Ejemplo n.º 29
0
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();
	}
}
Ejemplo n.º 30
0
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();
	}
}