diff --git a/rpcs3/Emu/Cell/Modules/cellGame.cpp b/rpcs3/Emu/Cell/Modules/cellGame.cpp index 117a065bfbd..69d2f77b299 100644 --- a/rpcs3/Emu/Cell/Modules/cellGame.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGame.cpp @@ -3,6 +3,7 @@ #include "Emu/VFS.h" #include "Emu/IdManager.h" #include "Emu/Cell/PPUModule.h" +#include "Emu/Cell/lv2/sys_sync.h" #include "cellSysutil.h" #include "cellMsgDialog.h" @@ -159,10 +160,27 @@ error_code cellHddGameCheck(ppu_thread& ppu, u32 version, vm::cptr dirName return CELL_HDDGAME_ERROR_PARAM; } - std::string dir = dirName.get_ptr(); + std::string game_dir = dirName.get_ptr(); // TODO: Find error code - verify(HERE), dir.size() == 9; + verify(HERE), game_dir.size() == 9; + + const std::string dir = "/dev_hdd0/game/" + game_dir; + + psf::registry sfo = psf::load_object(fs::file(vfs::get(dir + "/PARAM.SFO"))); + + const u32 new_data = sfo.empty() && !fs::is_file(vfs::get(dir + "/PARAM.SFO")) ? CELL_GAMEDATA_ISNEWDATA_YES : CELL_GAMEDATA_ISNEWDATA_NO; + + if (!new_data) + { + const auto cat = psf::get_string(sfo, "CATEGORY", ""); + if (cat != "HG") + { + return CELL_GAMEDATA_ERROR_BROKEN; + } + } + + const std::string usrdir = dir + "/USRDIR"; vm::var result; vm::var get; @@ -178,13 +196,13 @@ error_code cellHddGameCheck(ppu_thread& ppu, u32 version, vm::cptr dirName get->ctime = 0; // TODO get->mtime = 0; // TODO get->sizeKB = CELL_HDDGAME_SIZEKB_NOTCALC; - strcpy_trunc(get->contentInfoPath, "/dev_hdd0/game/" + dir); - strcpy_trunc(get->hddGamePath, "/dev_hdd0/game/" + dir + "/USRDIR"); + strcpy_trunc(get->contentInfoPath, dir); + strcpy_trunc(get->hddGamePath, usrdir); vm::var setParam; set->setParam = setParam; - const std::string& local_dir = vfs::get("/dev_hdd0/game/" + dir); + const std::string& local_dir = vfs::get(dir); if (!fs::is_dir(local_dir)) { @@ -215,14 +233,107 @@ error_code cellHddGameCheck(ppu_thread& ppu, u32 version, vm::cptr dirName funcStat(ppu, result, get, set); - if (result->result != u32{CELL_HDDGAME_CBRESULT_OK} && result->result != u32{CELL_HDDGAME_CBRESULT_OK_CANCEL}) + std::string error_msg; + + switch (result->result) + { + case CELL_HDDGAME_CBRESULT_OK: { - return CELL_HDDGAME_ERROR_CBRESULT; + // Game confirmed that it wants to create directory + const auto setParam = set->setParam; + + if (new_data) + { + if (!setParam) + { + return CELL_GAMEDATA_ERROR_PARAM; + } + + if (!fs::create_path(vfs::get(usrdir))) + { + return {CELL_GAME_ERROR_ACCESS_ERROR, usrdir}; + } + } + + if (setParam) + { + if (new_data) + { + psf::assign(sfo, "CATEGORY", psf::string(3, "GD")); + } + + psf::assign(sfo, "TITLE_ID", psf::string(CELL_GAME_SYSP_TITLEID_SIZE, setParam->titleId)); + psf::assign(sfo, "TITLE", psf::string(CELL_GAME_SYSP_TITLE_SIZE, setParam->title)); + psf::assign(sfo, "VERSION", psf::string(CELL_GAME_SYSP_VERSION_SIZE, setParam->dataVersion)); + psf::assign(sfo, "PARENTAL_LEVEL", +setParam->parentalLevel); + psf::assign(sfo, "RESOLUTION", +setParam->resolution); + psf::assign(sfo, "SOUND_FORMAT", +setParam->soundFormat); + + for (u32 i = 0; i < CELL_HDDGAME_SYSP_LANGUAGE_NUM; i++) + { + if (!setParam->titleLang[i][0]) + { + continue; + } + + psf::assign(sfo, fmt::format("TITLE_%02d", i), psf::string(CELL_GAME_SYSP_TITLE_SIZE, setParam->titleLang[i])); + } + + psf::save_object(fs::file(vfs::get(dir + "/PARAM.SFO"), fs::rewrite), sfo); + } + return CELL_OK; } + case CELL_HDDGAME_CBRESULT_OK_CANCEL: + cellGame.warning("cellHddGameCheck(): callback returned CELL_HDDGAME_CBRESULT_OK_CANCEL"); + return CELL_OK; - // TODO ? + case CELL_HDDGAME_CBRESULT_ERR_NOSPACE: + cellGame.error("cellHddGameCheck(): callback returned CELL_HDDGAME_CBRESULT_ERR_NOSPACE. Space Needed: %d KB", result->errNeedSizeKB); + error_msg = fmt::format("Not enough space to create HDD boot game.\nSpace Needed: %d KB", result->errNeedSizeKB); + break; - return CELL_OK; + case CELL_HDDGAME_CBRESULT_ERR_BROKEN: + cellGame.error("cellHddGameCheck(): callback returned CELL_HDDGAME_CBRESULT_ERR_BROKEN"); + error_msg = fmt::format("HDD boot game %s is corrupt!", game_dir); + break; + + case CELL_HDDGAME_CBRESULT_ERR_NODATA: + cellGame.error("cellHddGameCheck(): callback returned CELL_HDDGAME_CBRESULT_ERR_NODATA"); + error_msg = fmt::format("HDD boot game %s could not be found!", game_dir); + break; + + case CELL_HDDGAME_CBRESULT_ERR_INVALID: + cellGame.error("cellHddGameCheck(): callback returned CELL_HDDGAME_CBRESULT_ERR_INVALID. Error message: %s", result->invalidMsg); + error_msg = fmt::format("Error: %s", result->invalidMsg); + break; + + default: + cellGame.error("cellHddGameCheck(): callback returned unknown error (code=0x%x). Error message: %s", result->invalidMsg); + error_msg = fmt::format("Error: %s", result->invalidMsg); + break; + } + + if (errDialog == CELL_GAMEDATA_ERRDIALOG_ALWAYS) // Maybe != CELL_GAMEDATA_ERRDIALOG_NONE + { + // Yield before a blocking dialog is being spawned + lv2_obj::sleep(ppu); + + // Get user confirmation by opening a blocking dialog + error_code res = open_msg_dialog(true, CELL_MSGDIALOG_TYPE_SE_TYPE_ERROR | CELL_MSGDIALOG_TYPE_BUTTON_TYPE_OK | CELL_MSGDIALOG_TYPE_DISABLE_CANCEL_ON, vm::make_str(error_msg)); + + // Reschedule after a blocking dialog returns + if (ppu.check_state()) + { + return 0; + } + + if (res != CELL_OK) + { + return CELL_GAMEDATA_ERROR_INTERNAL; + } + } + + return CELL_HDDGAME_ERROR_CBRESULT; } error_code cellHddGameCheck2() @@ -552,14 +663,8 @@ error_code cellGameDataCheckCreate2(ppu_thread& ppu, u32 version, vm::cptr return CELL_GAMEDATA_ERROR_PARAM; } - // TODO: output errors (errDialog) - - const std::string dir = "/dev_hdd0/game/"s + dirName.get_ptr(); - const std::string usrdir = dir + "/USRDIR"; - - vm::var cbResult; - vm::var cbGet; - vm::var cbSet; + const std::string game_dir = dirName.get_ptr(); + const std::string dir = "/dev_hdd0/game/"s + game_dir; psf::registry sfo = psf::load_object(fs::file(vfs::get(dir + "/PARAM.SFO"))); @@ -574,6 +679,12 @@ error_code cellGameDataCheckCreate2(ppu_thread& ppu, u32 version, vm::cptr } } + const std::string usrdir = dir + "/USRDIR"; + + vm::var cbResult; + vm::var cbGet; + vm::var cbSet; + cbGet->isNewData = new_data; // TODO: Use the free space of the computer's HDD where RPCS3 is being run. @@ -603,15 +714,15 @@ error_code cellGameDataCheckCreate2(ppu_thread& ppu, u32 version, vm::cptr funcStat(ppu, cbResult, cbGet, cbSet); + std::string error_msg; + switch (cbResult->result) { case CELL_GAMEDATA_CBRESULT_OK_CANCEL: { - // TODO: do not process game data(directory) cellGame.warning("cellGameDataCheckCreate2(): callback returned CELL_GAMEDATA_CBRESULT_OK_CANCEL"); return CELL_OK; } - case CELL_GAMEDATA_CBRESULT_OK: { // Game confirmed that it wants to create directory @@ -644,7 +755,7 @@ error_code cellGameDataCheckCreate2(ppu_thread& ppu, u32 version, vm::cptr for (u32 i = 0; i < CELL_HDDGAME_SYSP_LANGUAGE_NUM; i++) { - if (!cbSet->setParam->titleLang[i][0]) + if (!setParam->titleLang[i][0]) { continue; } @@ -657,26 +768,53 @@ error_code cellGameDataCheckCreate2(ppu_thread& ppu, u32 version, vm::cptr return CELL_OK; } - case CELL_GAMEDATA_CBRESULT_ERR_NOSPACE: // TODO: process errors, error message and needSizeKB result - cellGame.error("cellGameDataCheckCreate2(): callback returned CELL_GAMEDATA_CBRESULT_ERR_NOSPACE"); - return CELL_GAMEDATA_ERROR_CBRESULT; + case CELL_GAMEDATA_CBRESULT_ERR_NOSPACE: + cellGame.error("cellGameDataCheckCreate2(): callback returned CELL_GAMEDATA_CBRESULT_ERR_NOSPACE. Space Needed: %d KB", cbResult->errNeedSizeKB); + error_msg = fmt::format("Not enough space to create game data.\nSpace Needed: %d KB", cbResult->errNeedSizeKB); + break; case CELL_GAMEDATA_CBRESULT_ERR_BROKEN: cellGame.error("cellGameDataCheckCreate2(): callback returned CELL_GAMEDATA_CBRESULT_ERR_BROKEN"); - return CELL_GAMEDATA_ERROR_CBRESULT; + error_msg = fmt::format("The game data in %s is corrupt!", game_dir); + break; case CELL_GAMEDATA_CBRESULT_ERR_NODATA: cellGame.error("cellGameDataCheckCreate2(): callback returned CELL_GAMEDATA_CBRESULT_ERR_NODATA"); - return CELL_GAMEDATA_ERROR_CBRESULT; + error_msg = fmt::format("The game data in %s could not be found!", game_dir); + break; case CELL_GAMEDATA_CBRESULT_ERR_INVALID: - cellGame.error("cellGameDataCheckCreate2(): callback returned CELL_GAMEDATA_CBRESULT_ERR_INVALID"); - return CELL_GAMEDATA_ERROR_CBRESULT; + cellGame.error("cellGameDataCheckCreate2(): callback returned CELL_GAMEDATA_CBRESULT_ERR_INVALID. Error message: %s", cbResult->invalidMsg_addr); + error_msg = fmt::format("Error: %s", cbResult->invalidMsg_addr); + break; default: - cellGame.error("cellGameDataCheckCreate2(): callback returned unknown error (code=0x%x)"); - return CELL_GAMEDATA_ERROR_CBRESULT; + cellGame.error("cellGameDataCheckCreate2(): callback returned unknown error (code=0x%x). Error message: %s", cbResult->invalidMsg_addr); + error_msg = fmt::format("Error: %s", cbResult->invalidMsg_addr); + break; + } + + if (errDialog == CELL_GAMEDATA_ERRDIALOG_ALWAYS) // Maybe != CELL_GAMEDATA_ERRDIALOG_NONE + { + // Yield before a blocking dialog is being spawned + lv2_obj::sleep(ppu); + + // Get user confirmation by opening a blocking dialog + error_code res = open_msg_dialog(true, CELL_MSGDIALOG_TYPE_SE_TYPE_ERROR | CELL_MSGDIALOG_TYPE_BUTTON_TYPE_OK | CELL_MSGDIALOG_TYPE_DISABLE_CANCEL_ON, vm::make_str(error_msg)); + + // Reschedule after a blocking dialog returns + if (ppu.check_state()) + { + return 0; + } + + if (res != CELL_OK) + { + return CELL_GAMEDATA_ERROR_INTERNAL; + } } + + return CELL_GAMEDATA_ERROR_CBRESULT; } error_code cellGameDataCheckCreate(ppu_thread& ppu, u32 version, vm::cptr dirName, u32 errDialog, vm::ptr funcStat, u32 container) diff --git a/rpcs3/Emu/Cell/Modules/cellGame.h b/rpcs3/Emu/Cell/Modules/cellGame.h index 493e76af98e..85de44cb1d6 100644 --- a/rpcs3/Emu/Cell/Modules/cellGame.h +++ b/rpcs3/Emu/Cell/Modules/cellGame.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "Emu/Cell/ErrorCodes.h" @@ -304,7 +304,7 @@ struct CellHddGameSystemFileParam struct CellHddGameCBResult { - be_t result; + be_t result; be_t errNeedSizeKB; vm::bptr invalidMsg; vm::bptr reserved;