CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/PSPLoaders.cpp
Views: 1401
// Copyright (C) 2012 PPSSPP Project12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include <thread>1819#include "Common/Data/Encoding/Utf8.h"20#include "Common/Thread/ThreadUtil.h"21#include "Common/System/Request.h"2223#include "Common/File/AndroidContentURI.h"24#include "Common/File/FileUtil.h"25#include "Common/StringUtils.h"26#ifdef _WIN3227#include "Common/CommonWindows.h"28#endif2930#include "Core/ELF/ElfReader.h"31#include "Core/ELF/ParamSFO.h"3233#include "Core/FileSystems/BlockDevices.h"34#include "Core/FileSystems/BlobFileSystem.h"35#include "Core/FileSystems/DirectoryFileSystem.h"36#include "Core/FileSystems/ISOFileSystem.h"37#include "Core/FileSystems/MetaFileSystem.h"38#include "Core/FileSystems/VirtualDiscFileSystem.h"3940#include "Core/Loaders.h"41#include "Core/MemMap.h"42#include "Core/HDRemaster.h"4344#include "Core/MIPS/MIPS.h"45#include "Core/MIPS/MIPSAnalyst.h"46#include "Core/MIPS/MIPSCodeUtils.h"4748#include "Core/Config.h"49#include "Core/ConfigValues.h"50#include "Core/System.h"51#include "Core/PSPLoaders.h"52#include "Core/HLE/HLE.h"53#include "Core/HLE/sceKernel.h"54#include "Core/HLE/sceKernelThread.h"55#include "Core/HLE/sceKernelModule.h"56#include "Core/HLE/sceKernelMemory.h"5758static std::thread g_loadingThread;5960static void UseLargeMem(int memsize) {61if (memsize != 1) {62// Nothing requested.63return;64}6566if (Memory::g_PSPModel != PSP_MODEL_FAT) {67INFO_LOG(Log::Loader, "Game requested full PSP-2000 memory access");68Memory::g_MemorySize = Memory::RAM_DOUBLE_SIZE;69} else {70WARN_LOG(Log::Loader, "Game requested full PSP-2000 memory access, ignoring in PSP-1000 mode");71}72}7374// We gather the game info before actually loading/booting the ISO75// to determine if the emulator should enable extra memory and76// double-sized texture coordinates.77void InitMemoryForGameISO(FileLoader *fileLoader) {78if (!fileLoader->Exists()) {79return;80}8182std::shared_ptr<IFileSystem> fileSystem;83std::shared_ptr<IFileSystem> blockSystem;8485if (fileLoader->IsDirectory()) {86fileSystem = std::make_shared<VirtualDiscFileSystem>(&pspFileSystem, fileLoader->GetPath());87blockSystem = fileSystem;88} else {89auto bd = constructBlockDevice(fileLoader);90// Can't init anything without a block device...91if (!bd)92return;9394auto iso = std::make_shared<ISOFileSystem>(&pspFileSystem, bd);95fileSystem = iso;96blockSystem = std::make_shared<ISOBlockSystem>(iso);97}9899pspFileSystem.Mount("umd0:", blockSystem);100pspFileSystem.Mount("umd1:", blockSystem);101pspFileSystem.Mount("disc0:", fileSystem);102pspFileSystem.Mount("umd:", blockSystem);103// TODO: Should we do this?104//pspFileSystem.Mount("host0:", fileSystem);105106std::string gameID;107std::string umdData;108109std::string sfoPath("disc0:/PSP_GAME/PARAM.SFO");110PSPFileInfo fileInfo = pspFileSystem.GetFileInfo(sfoPath.c_str());111112if (fileInfo.exists) {113std::vector<u8> paramsfo;114pspFileSystem.ReadEntireFile(sfoPath, paramsfo);115if (g_paramSFO.ReadSFO(paramsfo)) {116UseLargeMem(g_paramSFO.GetValueInt("MEMSIZE"));117gameID = g_paramSFO.GetValueString("DISC_ID");118}119120std::vector<u8> umdDataBin;121if (pspFileSystem.ReadEntireFile("disc0:/UMD_DATA.BIN", umdDataBin) >= 0) {122umdData = std::string((const char *)&umdDataBin[0], umdDataBin.size());123}124}125126for (size_t i = 0; i < g_HDRemastersCount; i++) {127const auto &entry = g_HDRemasters[i];128if (entry.gameID != gameID) {129continue;130}131if (entry.umdDataValue && umdData.find(entry.umdDataValue) == umdData.npos) {132continue;133}134135g_RemasterMode = true;136Memory::g_MemorySize = entry.memorySize;137g_DoubleTextureCoordinates = entry.doubleTextureCoordinates;138break;139}140if (g_RemasterMode) {141INFO_LOG(Log::Loader, "HDRemaster found, using increased memory");142}143}144145bool ReInitMemoryForGameISO(FileLoader *fileLoader) {146if (!fileLoader->Exists()) {147return false;148}149150std::shared_ptr<IFileSystem> fileSystem;151std::shared_ptr<IFileSystem> blockSystem;152153if (fileLoader->IsDirectory()) {154fileSystem = std::make_shared<VirtualDiscFileSystem>(&pspFileSystem, fileLoader->GetPath());155blockSystem = fileSystem;156} else {157auto bd = constructBlockDevice(fileLoader);158if (!bd)159return false;160161auto iso = std::make_shared<ISOFileSystem>(&pspFileSystem, bd);162fileSystem = iso;163blockSystem = std::make_shared<ISOBlockSystem>(iso);164}165166pspFileSystem.Remount("umd0:", blockSystem);167pspFileSystem.Remount("umd1:", blockSystem);168pspFileSystem.Remount("umd:", blockSystem);169pspFileSystem.Remount("disc0:", fileSystem);170171return true;172}173174void InitMemoryForGamePBP(FileLoader *fileLoader) {175if (!fileLoader->Exists()) {176return;177}178179PBPReader pbp(fileLoader);180if (pbp.IsValid() && !pbp.IsELF()) {181std::vector<u8> sfoData;182if (pbp.GetSubFile(PBP_PARAM_SFO, &sfoData)) {183ParamSFOData paramSFO;184if (paramSFO.ReadSFO(sfoData)) {185// This is the parameter CFW uses to determine homebrew wants the full 64MB.186UseLargeMem(paramSFO.GetValueInt("MEMSIZE"));187188// Take this moment to bring over the title, if set.189std::string title = paramSFO.GetValueString("TITLE");190if (g_paramSFO.GetValueString("TITLE").empty() && !title.empty()) {191g_paramSFO.SetValue("TITLE", title, (int)title.size());192}193194std::string discID = paramSFO.GetValueString("DISC_ID");195std::string systemVer = paramSFO.GetValueString("PSP_SYSTEM_VER");196// Homebrew typically always leave this zero.197bool discTotalCheck = paramSFO.GetValueInt("DISC_TOTAL") != 0;198// A lot of homebrew reuse real game disc IDs - avoid.199bool formatCheck = discID.substr(0, 2) != "NP" && discID.substr(0, 2) != "UL" && discID.substr(0, 2) != "UC";200char region = discID.size() > 3 ? discID[2] : '\0';201bool regionCheck = region != 'A' && region != 'E' && region != 'H' && region != 'I' && region != 'J' && region != 'K' && region != 'U' && region != 'X';202bool systemVerCheck = !systemVer.empty() && systemVer[0] >= '5';203if ((formatCheck || regionCheck || discTotalCheck || systemVerCheck) && !discID.empty()) {204g_paramSFO.SetValue("DISC_ID", discID, (int)discID.size());205std::string ver = paramSFO.GetValueString("DISC_VERSION");206if (ver.empty())207ver = "1.00";208g_paramSFO.SetValue("DISC_VERSION", ver, (int)ver.size());209}210}211}212}213}214215216// Chinese translators like to rename EBOOT.BIN and replace it with some kind of stub217// that probably loads a plugin and then launches the actual game. These stubs don't work in PPSSPP.218// No idea why they are doing this, but it works to just bypass it. They could stop219// inventing new filenames though...220static const char * const altBootNames[] = {221"disc0:/PSP_GAME/SYSDIR/EBOOT.OLD",222"disc0:/PSP_GAME/SYSDIR/EBOOT.DAT",223"disc0:/PSP_GAME/SYSDIR/EBOOT.BI",224"disc0:/PSP_GAME/SYSDIR/EBOOT.LLD",225//"disc0:/PSP_GAME/SYSDIR/OLD_EBOOT.BIN", //Utawareru Mono Chinese version226"disc0:/PSP_GAME/SYSDIR/EBOOT.123",227//"disc0:/PSP_GAME/SYSDIR/EBOOT_LRC_CH.BIN", // Hatsune Miku Project Diva Extend chinese version228"disc0:/PSP_GAME/SYSDIR/BOOT0.OLD",229"disc0:/PSP_GAME/SYSDIR/BOOT1.OLD",230"disc0:/PSP_GAME/SYSDIR/BINOT.BIN",231"disc0:/PSP_GAME/SYSDIR/EBOOT.FRY",232"disc0:/PSP_GAME/SYSDIR/EBOOT.Z.Y",233"disc0:/PSP_GAME/SYSDIR/EBOOT.LEI",234"disc0:/PSP_GAME/SYSDIR/EBOOT.DNR",235"disc0:/PSP_GAME/SYSDIR/DBZ2.BIN",236//"disc0:/PSP_GAME/SYSDIR/ss.RAW",//Code Geass: Lost Colors chinese version237};238239bool Load_PSP_ISO(FileLoader *fileLoader, std::string *error_string) {240// Mounting stuff relocated to InitMemoryForGameISO due to HD Remaster restructuring of code.241242std::string sfoPath("disc0:/PSP_GAME/PARAM.SFO");243PSPFileInfo fileInfo = pspFileSystem.GetFileInfo(sfoPath.c_str());244if (fileInfo.exists) {245std::vector<u8> paramsfo;246pspFileSystem.ReadEntireFile(sfoPath, paramsfo);247if (g_paramSFO.ReadSFO(paramsfo)) {248std::string title = StringFromFormat("%s : %s", g_paramSFO.GetValueString("DISC_ID").c_str(), g_paramSFO.GetValueString("TITLE").c_str());249INFO_LOG(Log::Loader, "%s", title.c_str());250System_SetWindowTitle(title);251}252}253254std::string bootpath("disc0:/PSP_GAME/SYSDIR/EBOOT.BIN");255256// Bypass Chinese translation patches, see comment above.257for (size_t i = 0; i < ARRAY_SIZE(altBootNames); i++) {258if (pspFileSystem.GetFileInfo(altBootNames[i]).exists) {259bootpath = altBootNames[i];260}261}262263// Bypass another more dangerous one where the file is in USRDIR - this could collide with files in some game.264std::string id = g_paramSFO.GetValueString("DISC_ID");265if (id == "NPJH50624" && pspFileSystem.GetFileInfo("disc0:/PSP_GAME/USRDIR/PAKFILE2.BIN").exists) {266bootpath = "disc0:/PSP_GAME/USRDIR/PAKFILE2.BIN";267}268if (id == "NPJH00100" && pspFileSystem.GetFileInfo("disc0:/PSP_GAME/USRDIR/DATA/GIM/GBL").exists) {269bootpath = "disc0:/PSP_GAME/USRDIR/DATA/GIM/GBL";270}271272bool hasEncrypted = false;273int fd;274if ((fd = pspFileSystem.OpenFile(bootpath, FILEACCESS_READ)) >= 0)275{276u8 head[4];277pspFileSystem.ReadFile(fd, head, 4);278if (memcmp(head, "~PSP", 4) == 0 || memcmp(head, "\x7F""ELF", 4) == 0) {279hasEncrypted = true;280}281pspFileSystem.CloseFile(fd);282}283if (!hasEncrypted) {284// try unencrypted Boot.BIN285bootpath = "disc0:/PSP_GAME/SYSDIR/BOOT.BIN";286}287288// Fail early with a clearer message for some types of ISOs.289if (!pspFileSystem.GetFileInfo(bootpath).exists) {290// Can't tell for sure if it's PS1 or PS2, but doesn't much matter.291if (pspFileSystem.GetFileInfo("disc0:/SYSTEM.CNF;1").exists || pspFileSystem.GetFileInfo("disc0:/PSX.EXE;1").exists) {292*error_string = "PPSSPP plays PSP games, not PlayStation 1 or 2 games.";293} else if (pspFileSystem.GetFileInfo("disc0:/UMD_VIDEO/PLAYLIST.UMD").exists) {294*error_string = "PPSSPP doesn't support UMD Video.";295} else if (pspFileSystem.GetFileInfo("disc0:/UMD_AUDIO/PLAYLIST.UMD").exists) {296*error_string = "PPSSPP doesn't support UMD Music.";297} else if (pspFileSystem.GetDirListing("disc0:/").empty()) {298*error_string = "Not a valid disc image.";299} else {300*error_string = "A PSP game couldn't be found on the disc.";301}302coreState = CORE_BOOT_ERROR;303return false;304}305306//in case we didn't go through EmuScreen::boot307g_Config.loadGameConfig(id, g_paramSFO.GetValueString("TITLE"));308System_PostUIMessage(UIMessage::CONFIG_LOADED);309INFO_LOG(Log::Loader, "Loading %s...", bootpath.c_str());310311PSPLoaders_Shutdown();312// Note: this thread reads the game binary, loads caches, and links HLE while UI spins.313// To do something deterministically when the game starts, disabling this thread won't be enough.314// Instead: Use Core_ListenLifecycle() or watch coreState.315g_loadingThread = std::thread([bootpath] {316SetCurrentThreadName("ExecLoader");317PSP_LoadingLock guard;318if (coreState != CORE_POWERUP)319return;320321AndroidJNIThreadContext jniContext;322323PSP_SetLoading("Loading executable...");324// TODO: We can't use the initial error_string pointer.325bool success = __KernelLoadExec(bootpath.c_str(), 0, &PSP_CoreParameter().errorString);326if (success && coreState == CORE_POWERUP) {327coreState = PSP_CoreParameter().startBreak ? CORE_STEPPING : CORE_RUNNING;328} else {329coreState = CORE_BOOT_ERROR;330// TODO: This is a crummy way to communicate the error...331PSP_CoreParameter().fileToStart.clear();332}333});334return true;335}336337static Path NormalizePath(const Path &path) {338if (path.Type() != PathType::NATIVE) {339// Nothing to do - these can't be non-normalized.340return path;341}342343#ifdef _WIN32344std::wstring wpath = path.ToWString();345std::wstring buf;346buf.resize(512);347size_t sz = GetFullPathName(wpath.c_str(), (DWORD)buf.size(), &buf[0], nullptr);348if (sz != 0 && sz < buf.size()) {349buf.resize(sz);350} else if (sz > buf.size()) {351buf.resize(sz);352sz = GetFullPathName(wpath.c_str(), (DWORD)buf.size(), &buf[0], nullptr);353// This should truncate off the null terminator.354buf.resize(sz);355}356return Path(buf);357#else358char buf[PATH_MAX + 1];359if (!realpath(path.c_str(), buf))360return Path();361return Path(buf);362#endif363}364365bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string) {366// This is really just for headless, might need tweaking later.367if (PSP_CoreParameter().mountIsoLoader != nullptr) {368auto bd = constructBlockDevice(PSP_CoreParameter().mountIsoLoader);369if (bd != NULL) {370auto umd2 = std::make_shared<ISOFileSystem>(&pspFileSystem, bd);371auto blockSystem = std::make_shared<ISOBlockSystem>(umd2);372373pspFileSystem.Mount("umd1:", blockSystem);374pspFileSystem.Mount("disc0:", umd2);375pspFileSystem.Mount("umd:", blockSystem);376}377}378379Path full_path = fileLoader->GetPath();380std::string path = full_path.GetDirectory();381std::string file = full_path.GetFilename();382383if (full_path.Type() == PathType::CONTENT_URI) {384path = AndroidContentURI(full_path.GetDirectory()).FilePath();385}386387size_t pos = path.find("PSP/GAME/");388std::string ms_path;389if (pos != std::string::npos) {390ms_path = "ms0:/" + path.substr(pos) + "/";391} else {392// This is wrong, but it's better than not having a working directory at all.393// Note that umd0:/ is actually the writable containing directory, in this case.394ms_path = "umd0:/";395}396397Path dir;398if (!PSP_CoreParameter().mountRoot.empty()) {399// We don't want to worry about .. and cwd and such.400const Path rootNorm = NormalizePath(PSP_CoreParameter().mountRoot);401Path pathNorm = NormalizePath(Path(path));402403if (full_path.Type() == PathType::CONTENT_URI) {404pathNorm = full_path.NavigateUp();405}406407// If root is not a subpath of path, we can't boot the game.408if (!pathNorm.StartsWith(rootNorm)) {409*error_string = "Cannot boot ELF located outside mountRoot.";410coreState = CORE_BOOT_ERROR;411return false;412}413414std::string filepath;415if (full_path.Type() == PathType::CONTENT_URI) {416std::string rootFilePath = AndroidContentURI(rootNorm.c_str()).FilePath();417std::string pathFilePath = AndroidContentURI(pathNorm.c_str()).FilePath();418filepath = pathFilePath.substr(rootFilePath.size());419} else {420filepath = ReplaceAll(pathNorm.ToString().substr(rootNorm.ToString().size()), "\\", "/");421}422423file = filepath + "/" + file;424path = rootNorm.ToString();425pspFileSystem.SetStartingDirectory(filepath);426dir = Path(path);427} else {428pspFileSystem.SetStartingDirectory(ms_path);429dir = full_path.NavigateUp();430}431432auto fs = std::make_shared<DirectoryFileSystem>(&pspFileSystem, dir, FileSystemFlags::SIMULATE_FAT32 | FileSystemFlags::CARD);433pspFileSystem.Mount("umd0:", fs);434435std::string finalName = ms_path + file;436437std::string homebrewName = PSP_CoreParameter().fileToStart.ToVisualString();438std::size_t lslash = homebrewName.find_last_of('/');439#if PPSSPP_PLATFORM(UWP)440if (lslash == homebrewName.npos) {441lslash = homebrewName.find_last_of("\\");442}443#endif444if (lslash != homebrewName.npos)445homebrewName = homebrewName.substr(lslash + 1);446std::string homebrewTitle = g_paramSFO.GetValueString("TITLE");447if (homebrewTitle.empty())448homebrewTitle = homebrewName;449std::string discID = g_paramSFO.GetDiscID();450std::string discVersion = g_paramSFO.GetValueString("DISC_VERSION");451std::string madeUpID = g_paramSFO.GenerateFakeID(Path());452453std::string title = StringFromFormat("%s : %s", discID.c_str(), homebrewTitle.c_str());454INFO_LOG(Log::Loader, "%s", title.c_str());455System_SetWindowTitle(title);456457// Migrate old save states from old versions of fake game IDs.458const Path savestateDir = GetSysDirectory(DIRECTORY_SAVESTATE);459for (int i = 0; i < 5; ++i) {460Path newPrefix = savestateDir / StringFromFormat("%s_%s_%d", discID.c_str(), discVersion.c_str(), i);461Path oldNamePrefix = savestateDir / StringFromFormat("%s_%d", homebrewName.c_str(), i);462Path oldIDPrefix = savestateDir / StringFromFormat("%s_1.00_%d", madeUpID.c_str(), i);463464if (oldIDPrefix != newPrefix && File::Exists(oldIDPrefix.WithExtraExtension(".ppst")))465File::Rename(oldIDPrefix.WithExtraExtension(".ppst"), newPrefix.WithExtraExtension(".ppst"));466else if (File::Exists(oldNamePrefix.WithExtraExtension(".ppst")))467File::Rename(oldNamePrefix.WithExtraExtension(".ppst"), newPrefix.WithExtraExtension(".ppst"));468if (oldIDPrefix != newPrefix && File::Exists(oldIDPrefix.WithExtraExtension(".jpg")))469File::Rename(oldIDPrefix.WithExtraExtension(".jpg"), newPrefix.WithExtraExtension(".jpg"));470else if (File::Exists(oldNamePrefix.WithExtraExtension(".jpg")))471File::Rename(oldNamePrefix.WithExtraExtension(".jpg"), newPrefix.WithExtraExtension(".jpg"));472}473474PSPLoaders_Shutdown();475// Note: See Load_PSP_ISO for notes about this thread.476g_loadingThread = std::thread([finalName] {477SetCurrentThreadName("ExecLoader");478PSP_LoadingLock guard;479if (coreState != CORE_POWERUP)480return;481482AndroidJNIThreadContext jniContext;483484bool success = __KernelLoadExec(finalName.c_str(), 0, &PSP_CoreParameter().errorString);485if (success && coreState == CORE_POWERUP) {486coreState = PSP_CoreParameter().startBreak ? CORE_STEPPING : CORE_RUNNING;487} else {488coreState = CORE_BOOT_ERROR;489// TODO: This is a crummy way to communicate the error...490PSP_CoreParameter().fileToStart.clear();491}492});493return true;494}495496bool Load_PSP_GE_Dump(FileLoader *fileLoader, std::string *error_string) {497auto umd = std::make_shared<BlobFileSystem>(&pspFileSystem, fileLoader, "data.ppdmp");498pspFileSystem.Mount("disc0:", umd);499500PSPLoaders_Shutdown();501// Note: See Load_PSP_ISO for notes about this thread.502g_loadingThread = std::thread([] {503SetCurrentThreadName("ExecLoader");504PSP_LoadingLock guard;505if (coreState != CORE_POWERUP)506return;507508AndroidJNIThreadContext jniContext;509510bool success = __KernelLoadGEDump("disc0:/data.ppdmp", &PSP_CoreParameter().errorString);511if (success && coreState == CORE_POWERUP) {512coreState = PSP_CoreParameter().startBreak ? CORE_STEPPING : CORE_RUNNING;513} else {514coreState = CORE_BOOT_ERROR;515// TODO: This is a crummy way to communicate the error...516PSP_CoreParameter().fileToStart.clear();517}518});519return true;520}521522void PSPLoaders_Shutdown() {523if (g_loadingThread.joinable())524g_loadingThread.join();525}526527528