mirror of
https://github.com/RPCSX/rpcsx.git
synced 2026-01-02 14:50:05 +01:00
cellDiscGameGetBootDiscInfo is called by non-disc games for some reason. That wasn't accounted for and therefore it would try to read PARAM.SFO from an unmounted path and throw an access violation. Tested with NBA Live 08 Demo NPUB90029, probably fixes similar games as well
853 lines
25 KiB
C++
853 lines
25 KiB
C++
#include "stdafx.h"
|
|
#include "Emu/System.h"
|
|
#include "Emu/IdManager.h"
|
|
#include "Emu/Cell/PPUModule.h"
|
|
|
|
#include "cellSysutil.h"
|
|
#include "cellMsgDialog.h"
|
|
#include "cellGame.h"
|
|
|
|
#include "Loader/PSF.h"
|
|
#include "Utilities/StrUtil.h"
|
|
|
|
#include <thread>
|
|
|
|
logs::channel cellGame("cellGame", logs::level::notice);
|
|
|
|
template<>
|
|
void fmt_class_string<CellGameError>::format(std::string& out, u64 arg)
|
|
{
|
|
format_enum(out, arg, [](auto error)
|
|
{
|
|
switch (error)
|
|
{
|
|
STR_CASE(CELL_GAME_ERROR_NOTFOUND);
|
|
STR_CASE(CELL_GAME_ERROR_BROKEN);
|
|
STR_CASE(CELL_GAME_ERROR_INTERNAL);
|
|
STR_CASE(CELL_GAME_ERROR_PARAM);
|
|
STR_CASE(CELL_GAME_ERROR_NOAPP);
|
|
STR_CASE(CELL_GAME_ERROR_ACCESS_ERROR);
|
|
STR_CASE(CELL_GAME_ERROR_NOSPACE);
|
|
STR_CASE(CELL_GAME_ERROR_NOTSUPPORTED);
|
|
STR_CASE(CELL_GAME_ERROR_FAILURE);
|
|
STR_CASE(CELL_GAME_ERROR_BUSY);
|
|
STR_CASE(CELL_GAME_ERROR_IN_SHUTDOWN);
|
|
STR_CASE(CELL_GAME_ERROR_INVALID_ID);
|
|
STR_CASE(CELL_GAME_ERROR_EXIST);
|
|
STR_CASE(CELL_GAME_ERROR_NOTPATCH);
|
|
STR_CASE(CELL_GAME_ERROR_INVALID_THEME_FILE);
|
|
STR_CASE(CELL_GAME_ERROR_BOOTPATH);
|
|
}
|
|
|
|
return unknown;
|
|
});
|
|
}
|
|
|
|
template<>
|
|
void fmt_class_string<CellGameDataError>::format(std::string& out, u64 arg)
|
|
{
|
|
format_enum(out, arg, [](auto error)
|
|
{
|
|
switch (error)
|
|
{
|
|
STR_CASE(CELL_GAMEDATA_ERROR_CBRESULT);
|
|
STR_CASE(CELL_GAMEDATA_ERROR_ACCESS_ERROR);
|
|
STR_CASE(CELL_GAMEDATA_ERROR_INTERNAL);
|
|
STR_CASE(CELL_GAMEDATA_ERROR_PARAM);
|
|
STR_CASE(CELL_GAMEDATA_ERROR_NOSPACE);
|
|
STR_CASE(CELL_GAMEDATA_ERROR_BROKEN);
|
|
STR_CASE(CELL_GAMEDATA_ERROR_FAILURE);
|
|
}
|
|
|
|
return unknown;
|
|
});
|
|
}
|
|
|
|
// If dir is empty:
|
|
// contentInfo = "/dev_bdvd/PS3_GAME"
|
|
// usrdir = "/dev_bdvd/PS3_GAME/USRDIR"
|
|
// Temporary content directory (dir is not empty):
|
|
// contentInfo = "/dev_hdd1/game/" + dir
|
|
// usrdir = "/dev_hdd1/game/" + dir + "/USRDIR"
|
|
// Normal content directory (dir is not empty):
|
|
// contentInfo = "/dev_hdd0/game/" + dir
|
|
// usrdir = "/dev_hdd0/game/" + dir + "/USRDIR"
|
|
struct content_permission final
|
|
{
|
|
// Content directory name or path
|
|
const std::string dir;
|
|
|
|
// SFO file
|
|
psf::registry sfo;
|
|
|
|
// True if temporary directory is created and must be moved or deleted
|
|
bool is_temporary = false;
|
|
|
|
template <typename Dir, typename Sfo>
|
|
content_permission(Dir&& dir, Sfo&& sfo, bool is_temp = false)
|
|
: dir(std::forward<Dir>(dir))
|
|
, sfo(std::forward<Sfo>(sfo))
|
|
, is_temporary(is_temp)
|
|
{
|
|
}
|
|
|
|
~content_permission()
|
|
{
|
|
try
|
|
{
|
|
if (is_temporary)
|
|
{
|
|
fs::remove_all(vfs::get("/dev_hdd1/game/" + dir));
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
cellGame.fatal("Failed to clean directory '/dev_hdd1/game/%s'", dir);
|
|
catch_all_exceptions();
|
|
}
|
|
}
|
|
};
|
|
|
|
s32 cellHddGameCheck(ppu_thread& ppu, u32 version, vm::cptr<char> dirName, u32 errDialog, vm::ptr<CellHddGameStatCallback> funcStat, u32 container)
|
|
{
|
|
cellGame.error("cellHddGameCheck(version=%d, dirName=%s, errDialog=%d, funcStat=*0x%x, container=%d)", version, dirName, errDialog, funcStat, container);
|
|
|
|
std::string dir = dirName.get_ptr();
|
|
|
|
if (dir.size() != 9)
|
|
{
|
|
return CELL_HDDGAME_ERROR_PARAM;
|
|
}
|
|
|
|
vm::var<CellHddGameCBResult> result;
|
|
vm::var<CellHddGameStatGet> get;
|
|
vm::var<CellHddGameStatSet> set;
|
|
|
|
get->hddFreeSizeKB = 40 * 1024 * 1024; // 40 GB, TODO: Use the free space of the computer's HDD where RPCS3 is being run.
|
|
get->isNewData = CELL_HDDGAME_ISNEWDATA_EXIST;
|
|
get->sysSizeKB = 0; // TODO
|
|
get->atime = 0; // TODO
|
|
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");
|
|
|
|
const std::string& local_dir = vfs::get("/dev_hdd0/game/" + dir);
|
|
|
|
if (!fs::is_dir(local_dir))
|
|
{
|
|
get->isNewData = CELL_HDDGAME_ISNEWDATA_NODIR;
|
|
get->getParam = {};
|
|
}
|
|
else
|
|
{
|
|
// TODO: Is cellHddGameCheck really responsible for writing the information in get->getParam ? (If not, delete this else)
|
|
const auto& psf = psf::load_object(fs::file(local_dir +"/PARAM.SFO"));
|
|
|
|
// Some following fields may be zero in old FW 1.00 version PARAM.SFO
|
|
if (psf.count("PARENTAL_LEVEL") != 0) get->getParam.parentalLevel = psf.at("PARENTAL_LEVEL").as_integer();
|
|
if (psf.count("ATTRIBUTE") != 0) get->getParam.attribute = psf.at("ATTRIBUTE").as_integer();
|
|
if (psf.count("RESOLUTION") != 0) get->getParam.resolution = psf.at("RESOLUTION").as_integer();
|
|
if (psf.count("SOUND_FORMAT") != 0) get->getParam.soundFormat = psf.at("SOUND_FORMAT").as_integer();
|
|
if (psf.count("TITLE") != 0) strcpy_trunc(get->getParam.title, psf.at("TITLE").as_string());
|
|
if (psf.count("APP_VER") != 0) strcpy_trunc(get->getParam.dataVersion, psf.at("APP_VER").as_string());
|
|
if (psf.count("TITLE_ID") != 0) strcpy_trunc(get->getParam.titleId, psf.at("TITLE_ID").as_string());
|
|
|
|
for (u32 i = 0; i < CELL_HDDGAME_SYSP_LANGUAGE_NUM; i++)
|
|
{
|
|
strcpy_trunc(get->getParam.titleLang[i], psf::get_string(psf, fmt::format("TITLE_%02d", i)));
|
|
}
|
|
}
|
|
|
|
// TODO ?
|
|
|
|
funcStat(ppu, result, get, set);
|
|
|
|
if (result->result != CELL_HDDGAME_CBRESULT_OK && result->result != CELL_HDDGAME_CBRESULT_OK_CANCEL)
|
|
{
|
|
return CELL_HDDGAME_ERROR_CBRESULT;
|
|
}
|
|
|
|
// TODO ?
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
s32 cellHddGameCheck2()
|
|
{
|
|
fmt::throw_exception("Unimplemented" HERE);
|
|
}
|
|
|
|
s32 cellHddGameGetSizeKB(vm::ptr<u32> size)
|
|
{
|
|
cellGame.todo("cellHddGameGetSizeKB(size=*0x%x)", size);
|
|
|
|
*size = 0;
|
|
return CELL_OK;
|
|
}
|
|
|
|
s32 cellHddGameSetSystemVer()
|
|
{
|
|
fmt::throw_exception("Unimplemented" HERE);
|
|
}
|
|
|
|
s32 cellHddGameExitBroken()
|
|
{
|
|
fmt::throw_exception("Unimplemented" HERE);
|
|
}
|
|
|
|
|
|
s32 cellGameDataGetSizeKB(vm::ptr<u32> size)
|
|
{
|
|
cellGame.todo("cellGameDataGetSizeKB(size=*0x%x)", size);
|
|
|
|
*size = 0;
|
|
return CELL_OK;
|
|
}
|
|
|
|
s32 cellGameDataSetSystemVer()
|
|
{
|
|
fmt::throw_exception("Unimplemented" HERE);
|
|
}
|
|
|
|
s32 cellGameDataExitBroken()
|
|
{
|
|
fmt::throw_exception("Unimplemented" HERE);
|
|
}
|
|
|
|
|
|
error_code cellGameBootCheck(vm::ptr<u32> type, vm::ptr<u32> attributes, vm::ptr<CellGameContentSize> size, vm::ptr<char[CELL_GAME_DIRNAME_SIZE]> dirName)
|
|
{
|
|
cellGame.warning("cellGameBootCheck(type=*0x%x, attributes=*0x%x, size=*0x%x, dirName=*0x%x)", type, attributes, size, dirName);
|
|
|
|
if (size)
|
|
{
|
|
// TODO: Use the free space of the computer's HDD where RPCS3 is being run.
|
|
size->hddFreeSizeKB = 40000000; // 40 GB
|
|
|
|
// TODO: Calculate data size for HG and DG games, if necessary.
|
|
size->sizeKB = CELL_GAME_SIZEKB_NOTCALC;
|
|
size->sysSizeKB = 0;
|
|
}
|
|
|
|
// According to testing (in debug mode) cellGameBootCheck doesn't return an error code, when PARAM.SFO doesn't exist.
|
|
psf::registry sfo = psf::load_object(fs::file(vfs::get("/app_home/../PARAM.SFO")));
|
|
|
|
const std::string& category = psf::get_string(sfo, "CATEGORY");
|
|
|
|
if (category == "DG")
|
|
{
|
|
*type = CELL_GAME_GAMETYPE_DISC;
|
|
*attributes = 0; // TODO
|
|
if (dirName) strcpy_trunc(*dirName, ""); // ???
|
|
|
|
if (!fxm::make<content_permission>("", std::move(sfo)))
|
|
{
|
|
return CELL_GAME_ERROR_BUSY;
|
|
}
|
|
}
|
|
else if (category == "AP" || category == "AV" || category == "HG")
|
|
{
|
|
*type = CELL_GAME_GAMETYPE_HDD;
|
|
*attributes = 0; // TODO
|
|
if (dirName) strcpy_trunc(*dirName, Emu.GetTitleID());
|
|
|
|
if (!fxm::make<content_permission>(Emu.GetTitleID(), std::move(sfo)))
|
|
{
|
|
return CELL_GAME_ERROR_BUSY;
|
|
}
|
|
}
|
|
else if (category == "GD")
|
|
{
|
|
*type = CELL_GAME_GAMETYPE_DISC;
|
|
*attributes = CELL_GAME_ATTRIBUTE_PATCH; // TODO
|
|
if (dirName) strcpy_trunc(*dirName, Emu.GetTitleID()); // ???
|
|
|
|
if (!fxm::make<content_permission>("", std::move(sfo)))
|
|
{
|
|
return CELL_GAME_ERROR_BUSY;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Hack: When there is no (or unknown) CATEGORY returned, instead of throwing an exception
|
|
// we assume it's a disk game.
|
|
*type = CELL_GAME_GAMETYPE_DISC;
|
|
*attributes = 0;
|
|
cellGame.error("cellGameBootCheck(): Unknown CATEGORY: %s", category);
|
|
}
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code cellGamePatchCheck(vm::ptr<CellGameContentSize> size, vm::ptr<void> reserved)
|
|
{
|
|
cellGame.warning("cellGamePatchCheck(size=*0x%x, reserved=*0x%x)", size, reserved);
|
|
|
|
if (size)
|
|
{
|
|
// TODO: Use the free space of the computer's HDD where RPCS3 is being run.
|
|
size->hddFreeSizeKB = 40000000; // 40 GB
|
|
|
|
// TODO: Calculate data size for patch data, if necessary.
|
|
size->sizeKB = CELL_GAME_SIZEKB_NOTCALC;
|
|
size->sysSizeKB = 0;
|
|
}
|
|
|
|
psf::registry sfo = psf::load_object(fs::file(vfs::get("/app_home/../PARAM.SFO")));
|
|
|
|
if (psf::get_string(sfo, "CATEGORY") != "GD")
|
|
{
|
|
return CELL_GAME_ERROR_NOTPATCH;
|
|
}
|
|
|
|
if (!fxm::make<content_permission>(Emu.GetTitleID(), std::move(sfo)))
|
|
{
|
|
return CELL_GAME_ERROR_BUSY;
|
|
}
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code cellGameDataCheck(u32 type, vm::cptr<char> dirName, vm::ptr<CellGameContentSize> size)
|
|
{
|
|
cellGame.warning("cellGameDataCheck(type=%d, dirName=%s, size=*0x%x)", type, dirName, size);
|
|
|
|
if ((type - 1) >= 3)
|
|
{
|
|
return CELL_GAME_ERROR_PARAM;
|
|
}
|
|
|
|
if (size)
|
|
{
|
|
// TODO: Use the free space of the computer's HDD where RPCS3 is being run.
|
|
size->hddFreeSizeKB = 40000000; //40 GB
|
|
|
|
// TODO: Calculate data size for game data, if necessary.
|
|
size->sizeKB = CELL_GAME_SIZEKB_NOTCALC;
|
|
size->sysSizeKB = 0;
|
|
}
|
|
|
|
// TODO: not sure what should be checked there
|
|
const auto prm = fxm::make<content_permission>(type == CELL_GAME_GAMETYPE_DISC ? "" : dirName.get_ptr(), psf::registry{});
|
|
|
|
if (!prm)
|
|
{
|
|
return CELL_GAME_ERROR_BUSY;
|
|
}
|
|
|
|
const std::string dir = prm->dir.empty() ? "/dev_bdvd/PS3_GAME"s : "/dev_hdd0/game/" + prm->dir;
|
|
|
|
if (!fs::is_dir(vfs::get(dir)))
|
|
{
|
|
cellGame.warning("cellGameDataCheck(): directory '%s' not found", dir);
|
|
return not_an_error(CELL_GAME_RET_NONE);
|
|
}
|
|
|
|
prm->sfo = psf::load_object(fs::file(vfs::get(dir + "/PARAM.SFO")));
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code cellGameContentPermit(vm::ptr<char[CELL_GAME_PATH_MAX]> contentInfoPath, vm::ptr<char[CELL_GAME_PATH_MAX]> usrdirPath)
|
|
{
|
|
cellGame.warning("cellGameContentPermit(contentInfoPath=*0x%x, usrdirPath=*0x%x)", contentInfoPath, usrdirPath);
|
|
|
|
if (!contentInfoPath && !usrdirPath)
|
|
{
|
|
return CELL_GAME_ERROR_PARAM;
|
|
}
|
|
|
|
const auto prm = fxm::get<content_permission>();
|
|
|
|
if (!prm)
|
|
{
|
|
return CELL_GAME_ERROR_FAILURE;
|
|
}
|
|
|
|
const std::string dir = prm->dir.empty() ? "/dev_bdvd/PS3_GAME"s : "/dev_hdd0/game/" + prm->dir;
|
|
|
|
if (prm->is_temporary)
|
|
{
|
|
// Make temporary directory persistent
|
|
const auto vdir = vfs::get(dir);
|
|
|
|
if (fs::exists(vdir))
|
|
{
|
|
fmt::throw_exception("cellGameContentPermit(): epic fail: directory '%s' already exists", dir);
|
|
}
|
|
|
|
if (fs::rename(vfs::get("/dev_hdd1/game/" + prm->dir), vdir))
|
|
{
|
|
cellGame.success("cellGameContentPermit(): directory '%s' has been created", dir);
|
|
}
|
|
else
|
|
{
|
|
fmt::throw_exception("cellGameContentPermit(): failed to initialize directory '%s'", dir);
|
|
}
|
|
|
|
// Create PARAM.SFO
|
|
psf::save_object(fs::file(vdir + "/PARAM.SFO", fs::rewrite), prm->sfo);
|
|
|
|
// Disable deletion
|
|
prm->is_temporary = false;
|
|
}
|
|
|
|
strcpy_trunc(*contentInfoPath, dir);
|
|
strcpy_trunc(*usrdirPath, dir + "/USRDIR");
|
|
verify(HERE), fxm::remove<content_permission>();
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code cellGameDataCheckCreate2(ppu_thread& ppu, u32 version, vm::cptr<char> dirName, u32 errDialog, vm::ptr<CellGameDataStatCallback> funcStat, u32 container)
|
|
{
|
|
cellGame.error("cellGameDataCheckCreate2(version=0x%x, dirName=%s, errDialog=0x%x, funcStat=*0x%x, container=%d)", version, dirName, errDialog, funcStat, container);
|
|
|
|
if (version != CELL_GAMEDATA_VERSION_CURRENT || errDialog > 1)
|
|
{
|
|
return CELL_GAMEDATA_ERROR_PARAM;
|
|
}
|
|
|
|
// TODO: output errors (errDialog)
|
|
|
|
const std::string& dir = "/dev_hdd0/game/"s + dirName.get_ptr();
|
|
|
|
if (!fs::is_dir(vfs::get(dir)))
|
|
{
|
|
cellGame.todo("cellGameDataCheckCreate2(): should create directory '%s'", dir);
|
|
// TODO: create data
|
|
return CELL_OK;
|
|
}
|
|
|
|
vm::var<CellGameDataCBResult> cbResult;
|
|
vm::var<CellGameDataStatGet> cbGet;
|
|
vm::var<CellGameDataStatSet> cbSet;
|
|
|
|
// TODO: Use the free space of the computer's HDD where RPCS3 is being run.
|
|
cbGet->hddFreeSizeKB = 40000000; //40 GB
|
|
|
|
cbGet->isNewData = CELL_GAMEDATA_ISNEWDATA_NO;
|
|
strcpy_trunc(cbGet->contentInfoPath, dir);
|
|
strcpy_trunc(cbGet->gameDataPath, dir + "/USRDIR");
|
|
|
|
// TODO: set correct time
|
|
cbGet->st_atime_ = 0;
|
|
cbGet->st_ctime_ = 0;
|
|
cbGet->st_mtime_ = 0;
|
|
|
|
// TODO: calculate data size, if necessary
|
|
cbGet->sizeKB = CELL_GAMEDATA_SIZEKB_NOTCALC;
|
|
cbGet->sysSizeKB = 0;
|
|
|
|
psf::registry sfo = psf::load_object(fs::file(vfs::get("/app_home/../PARAM.SFO")));
|
|
|
|
cbGet->getParam.attribute = CELL_GAMEDATA_ATTR_NORMAL;
|
|
cbGet->getParam.parentalLevel = psf::get_integer(sfo, "PARENTAL_LEVEL", 0);
|
|
strcpy_trunc(cbGet->getParam.dataVersion, psf::get_string(sfo, "APP_VER", ""));
|
|
strcpy_trunc(cbGet->getParam.titleId, psf::get_string(sfo, "TITLE_ID", ""));
|
|
strcpy_trunc(cbGet->getParam.title, psf::get_string(sfo, "TITLE", ""));
|
|
// TODO: write lang titles
|
|
|
|
funcStat(ppu, cbResult, cbGet, cbSet);
|
|
|
|
if (cbSet->setParam)
|
|
{
|
|
// TODO: write PARAM.SFO from cbSet
|
|
cellGame.todo("cellGameDataCheckCreate2(): writing PARAM.SFO parameters (addr=0x%x)", cbSet->setParam);
|
|
}
|
|
|
|
switch ((s32)cbResult->result)
|
|
{
|
|
case CELL_GAMEDATA_CBRESULT_OK_CANCEL:
|
|
// TODO: do not process game data
|
|
cellGame.warning("cellGameDataCheckCreate2(): callback returned CELL_GAMEDATA_CBRESULT_OK_CANCEL");
|
|
|
|
case CELL_GAMEDATA_CBRESULT_OK:
|
|
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_BROKEN:
|
|
cellGame.error("cellGameDataCheckCreate2(): callback returned CELL_GAMEDATA_CBRESULT_ERR_BROKEN");
|
|
return CELL_GAMEDATA_ERROR_CBRESULT;
|
|
|
|
case CELL_GAMEDATA_CBRESULT_ERR_NODATA:
|
|
cellGame.error("cellGameDataCheckCreate2(): callback returned CELL_GAMEDATA_CBRESULT_ERR_NODATA");
|
|
return CELL_GAMEDATA_ERROR_CBRESULT;
|
|
|
|
case CELL_GAMEDATA_CBRESULT_ERR_INVALID:
|
|
cellGame.error("cellGameDataCheckCreate2(): callback returned CELL_GAMEDATA_CBRESULT_ERR_INVALID");
|
|
return CELL_GAMEDATA_ERROR_CBRESULT;
|
|
|
|
default:
|
|
cellGame.error("cellGameDataCheckCreate2(): callback returned unknown error (code=0x%x)");
|
|
return CELL_GAMEDATA_ERROR_CBRESULT;
|
|
}
|
|
}
|
|
|
|
s32 cellGameDataCheckCreate(ppu_thread& ppu, u32 version, vm::cptr<char> dirName, u32 errDialog, vm::ptr<CellGameDataStatCallback> funcStat, u32 container)
|
|
{
|
|
cellGame.warning("cellGameDataCheckCreate(version=0x%x, dirName=%s, errDialog=0x%x, funcStat=*0x%x, container=%d)", version, dirName, errDialog, funcStat, container);
|
|
|
|
// TODO: almost identical, the only difference is that this function will always calculate the size of game data
|
|
return cellGameDataCheckCreate2(ppu, version, dirName, errDialog, funcStat, container);
|
|
}
|
|
|
|
error_code cellGameCreateGameData(vm::ptr<CellGameSetInitParams> init, vm::ptr<char[CELL_GAME_PATH_MAX]> tmp_contentInfoPath, vm::ptr<char[CELL_GAME_PATH_MAX]> tmp_usrdirPath)
|
|
{
|
|
cellGame.error("cellGameCreateGameData(init=*0x%x, tmp_contentInfoPath=*0x%x, tmp_usrdirPath=*0x%x)", init, tmp_contentInfoPath, tmp_usrdirPath);
|
|
|
|
const auto prm = fxm::get<content_permission>();
|
|
|
|
if (!prm || prm->dir.empty())
|
|
{
|
|
return CELL_GAME_ERROR_FAILURE;
|
|
}
|
|
|
|
std::string tmp_contentInfo = "/dev_hdd1/game/" + prm->dir;
|
|
std::string tmp_usrdir = "/dev_hdd1/game/" + prm->dir + "/USRDIR";
|
|
|
|
if (!fs::create_dir(vfs::get(tmp_contentInfo)))
|
|
{
|
|
cellGame.error("cellGameCreateGameData(): failed to create directory '%s'", tmp_contentInfo);
|
|
return CELL_GAME_ERROR_ACCESS_ERROR; // ???
|
|
}
|
|
|
|
if (!fs::create_dir(vfs::get(tmp_usrdir)))
|
|
{
|
|
cellGame.error("cellGameCreateGameData(): failed to create directory '%s'", tmp_usrdir);
|
|
return CELL_GAME_ERROR_ACCESS_ERROR; // ???
|
|
}
|
|
|
|
// cellGameContentPermit should then move files in non-temporary location and return their non-temporary displacement
|
|
strcpy_trunc(*tmp_contentInfoPath, tmp_contentInfo);
|
|
strcpy_trunc(*tmp_usrdirPath, tmp_usrdir);
|
|
|
|
cellGame.success("cellGameCreateGameData(): temporary directory '%s' has been created", tmp_contentInfo);
|
|
prm->is_temporary = true;
|
|
|
|
// Initial PARAM.SFO parameters (overwrite)
|
|
prm->sfo =
|
|
{
|
|
{ "CATEGORY", psf::string(3, "GD") },
|
|
{ "TITLE_ID", psf::string(CELL_GAME_SYSP_TITLEID_SIZE, init->titleId) },
|
|
{ "TITLE", psf::string(CELL_GAME_SYSP_TITLE_SIZE, init->title) },
|
|
{ "VERSION", psf::string(CELL_GAME_SYSP_VERSION_SIZE, init->version) },
|
|
};
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
s32 cellGameDeleteGameData()
|
|
{
|
|
UNIMPLEMENTED_FUNC(cellGame);
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code cellGameGetParamInt(s32 id, vm::ptr<s32> value)
|
|
{
|
|
cellGame.warning("cellGameGetParamInt(id=%d, value=*0x%x)", id, value);
|
|
|
|
const auto prm = fxm::get<content_permission>();
|
|
|
|
if (!prm)
|
|
{
|
|
return CELL_GAME_ERROR_FAILURE;
|
|
}
|
|
|
|
std::string key;
|
|
|
|
switch(id)
|
|
{
|
|
case CELL_GAME_PARAMID_PARENTAL_LEVEL: key = "PARENTAL_LEVEL"; break;
|
|
case CELL_GAME_PARAMID_RESOLUTION: key = "RESOLUTION"; break;
|
|
case CELL_GAME_PARAMID_SOUND_FORMAT: key = "SOUND_FORMAT"; break;
|
|
default:
|
|
{
|
|
return CELL_GAME_ERROR_INVALID_ID;
|
|
}
|
|
}
|
|
|
|
*value = psf::get_integer(prm->sfo, key, 0);
|
|
return CELL_OK;
|
|
}
|
|
|
|
static const char* get_param_string_key(s32 id)
|
|
{
|
|
switch (id)
|
|
{
|
|
case CELL_GAME_PARAMID_TITLE: return "TITLE"; // TODO: Is this value correct?
|
|
case CELL_GAME_PARAMID_TITLE_DEFAULT: return "TITLE";
|
|
case CELL_GAME_PARAMID_TITLE_JAPANESE: return "TITLE_00";
|
|
case CELL_GAME_PARAMID_TITLE_ENGLISH: return "TITLE_01";
|
|
case CELL_GAME_PARAMID_TITLE_FRENCH: return "TITLE_02";
|
|
case CELL_GAME_PARAMID_TITLE_SPANISH: return "TITLE_03";
|
|
case CELL_GAME_PARAMID_TITLE_GERMAN: return "TITLE_04";
|
|
case CELL_GAME_PARAMID_TITLE_ITALIAN: return "TITLE_05";
|
|
case CELL_GAME_PARAMID_TITLE_DUTCH: return "TITLE_06";
|
|
case CELL_GAME_PARAMID_TITLE_PORTUGUESE: return "TITLE_07";
|
|
case CELL_GAME_PARAMID_TITLE_RUSSIAN: return "TITLE_08";
|
|
case CELL_GAME_PARAMID_TITLE_KOREAN: return "TITLE_09";
|
|
case CELL_GAME_PARAMID_TITLE_CHINESE_T: return "TITLE_10";
|
|
case CELL_GAME_PARAMID_TITLE_CHINESE_S: return "TITLE_11";
|
|
case CELL_GAME_PARAMID_TITLE_FINNISH: return "TITLE_12";
|
|
case CELL_GAME_PARAMID_TITLE_SWEDISH: return "TITLE_13";
|
|
case CELL_GAME_PARAMID_TITLE_DANISH: return "TITLE_14";
|
|
case CELL_GAME_PARAMID_TITLE_NORWEGIAN: return "TITLE_15";
|
|
case CELL_GAME_PARAMID_TITLE_POLISH: return "TITLE_16";
|
|
case CELL_GAME_PARAMID_TITLE_PORTUGUESE_BRAZIL: return "TITLE_17";
|
|
case CELL_GAME_PARAMID_TITLE_ENGLISH_UK: return "TITLE_18";
|
|
|
|
case CELL_GAME_PARAMID_TITLE_ID: return "TITLE_ID";
|
|
case CELL_GAME_PARAMID_VERSION: return "VERSION";
|
|
case CELL_GAME_PARAMID_PS3_SYSTEM_VER: return "PS3_SYSTEM_VER";
|
|
case CELL_GAME_PARAMID_APP_VER: return "APP_VER";
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
error_code cellGameGetParamString(s32 id, vm::ptr<char> buf, u32 bufsize)
|
|
{
|
|
cellGame.warning("cellGameGetParamString(id=%d, buf=*0x%x, bufsize=%d)", id, buf, bufsize);
|
|
|
|
const auto prm = fxm::get<content_permission>();
|
|
|
|
if (!prm)
|
|
{
|
|
return CELL_GAME_ERROR_FAILURE;
|
|
}
|
|
|
|
const auto key = get_param_string_key(id);
|
|
|
|
if (!key)
|
|
{
|
|
return CELL_GAME_ERROR_INVALID_ID;
|
|
}
|
|
|
|
std::string value = psf::get_string(prm->sfo, key);
|
|
value.resize(bufsize - 1);
|
|
|
|
std::memcpy(buf.get_ptr(), value.c_str(), bufsize);
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code cellGameSetParamString(s32 id, vm::cptr<char> buf)
|
|
{
|
|
cellGame.warning("cellGameSetParamString(id=%d, buf=*0x%x)", id, buf);
|
|
|
|
const auto prm = fxm::get<content_permission>();
|
|
|
|
if (!prm)
|
|
{
|
|
return CELL_GAME_ERROR_FAILURE;
|
|
}
|
|
|
|
const auto key = get_param_string_key(id);
|
|
|
|
if (!key)
|
|
{
|
|
return CELL_GAME_ERROR_INVALID_ID;
|
|
}
|
|
|
|
u32 max_size = CELL_GAME_SYSP_TITLE_SIZE;
|
|
|
|
switch (id)
|
|
{
|
|
case CELL_GAME_PARAMID_TITLE_ID: max_size = CELL_GAME_SYSP_TITLEID_SIZE; break;
|
|
case CELL_GAME_PARAMID_VERSION: max_size = CELL_GAME_SYSP_VERSION_SIZE; break;
|
|
case CELL_GAME_PARAMID_PS3_SYSTEM_VER: max_size = CELL_GAME_SYSP_PS3_SYSTEM_VER_SIZE; break;
|
|
case CELL_GAME_PARAMID_APP_VER: max_size = CELL_GAME_SYSP_APP_VER_SIZE; break;
|
|
}
|
|
|
|
prm->sfo.emplace(key, psf::string(max_size, buf.get_ptr()));
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
s32 cellGameGetSizeKB(vm::ptr<s32> size)
|
|
{
|
|
cellGame.todo("cellGameGetSizeKB(size=*0x%x)", size);
|
|
|
|
*size = 0;
|
|
return CELL_OK;
|
|
}
|
|
|
|
s32 cellGameGetDiscContentInfoUpdatePath()
|
|
{
|
|
UNIMPLEMENTED_FUNC(cellGame);
|
|
return CELL_OK;
|
|
}
|
|
|
|
s32 cellGameGetLocalWebContentPath()
|
|
{
|
|
UNIMPLEMENTED_FUNC(cellGame);
|
|
return CELL_OK;
|
|
}
|
|
|
|
error_code cellGameContentErrorDialog(s32 type, s32 errNeedSizeKB, vm::cptr<char> dirName)
|
|
{
|
|
cellGame.warning("cellGameContentErrorDialog(type=%d, errNeedSizeKB=%d, dirName=%s)", type, errNeedSizeKB, dirName);
|
|
|
|
std::string errorName;
|
|
switch (type)
|
|
{
|
|
case CELL_GAME_ERRDIALOG_BROKEN_GAMEDATA: errorName = "Game data is corrupted (can be continued)."; break;
|
|
case CELL_GAME_ERRDIALOG_BROKEN_HDDGAME: errorName = "HDD boot game is corrupted (can be continued)."; break;
|
|
case CELL_GAME_ERRDIALOG_NOSPACE: errorName = "Not enough available space (can be continued)."; break;
|
|
case CELL_GAME_ERRDIALOG_BROKEN_EXIT_GAMEDATA: errorName = "Game data is corrupted (terminate application)."; break;
|
|
case CELL_GAME_ERRDIALOG_BROKEN_EXIT_HDDGAME: errorName = "HDD boot game is corrupted (terminate application)."; break;
|
|
case CELL_GAME_ERRDIALOG_NOSPACE_EXIT: errorName = "Not enough available space (terminate application)."; break;
|
|
default: return CELL_GAME_ERROR_PARAM;
|
|
}
|
|
|
|
std::string errorMsg;
|
|
if (type == CELL_GAME_ERRDIALOG_NOSPACE || type == CELL_GAME_ERRDIALOG_NOSPACE_EXIT)
|
|
{
|
|
errorMsg = fmt::format("ERROR: %s\nSpace needed: %d KB", errorName, errNeedSizeKB);
|
|
}
|
|
else
|
|
{
|
|
errorMsg = fmt::format("ERROR: %s", errorName);
|
|
}
|
|
|
|
if (dirName)
|
|
{
|
|
errorMsg += fmt::format("\nDirectory name: %s", dirName);
|
|
}
|
|
|
|
const auto dlg = Emu.GetCallbacks().get_msg_dialog();
|
|
|
|
dlg->type.bg_invisible = true;
|
|
dlg->type.button_type = 2; // OK
|
|
dlg->type.disable_cancel = true;
|
|
|
|
atomic_t<bool> result(false);
|
|
|
|
dlg->on_close = [&](s32 status)
|
|
{
|
|
result = true;
|
|
};
|
|
|
|
dlg->Create(errorMsg);
|
|
|
|
while (!result)
|
|
{
|
|
thread_ctrl::wait_for(1000);
|
|
}
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
s32 cellGameThemeInstall()
|
|
{
|
|
UNIMPLEMENTED_FUNC(cellGame);
|
|
return CELL_OK;
|
|
}
|
|
|
|
s32 cellGameThemeInstallFromBuffer()
|
|
{
|
|
UNIMPLEMENTED_FUNC(cellGame);
|
|
return CELL_OK;
|
|
}
|
|
|
|
|
|
s32 cellDiscGameGetBootDiscInfo(vm::ptr<CellDiscGameSystemFileParam> getParam)
|
|
{
|
|
cellGame.warning("cellDiscGameGetBootDiscInfo(getParam=*0x%x)", getParam);
|
|
|
|
// This is also called by non-disc games, see NPUB90029
|
|
const std::string dir = "/dev_bdvd/PS3_GAME"s;
|
|
|
|
if (!fs::is_dir(vfs::get(dir)))
|
|
{
|
|
// Not a disc game. TODO: Fetch PARAM.SFO from proper game dir
|
|
cellGame.warning("cellDiscGameGetBootDiscInfo(): directory '%s' not found", dir);
|
|
getParam->parentalLevel = 0;
|
|
strcpy_trunc(getParam->titleId, "0");
|
|
return CELL_OK;
|
|
}
|
|
|
|
const auto& psf = psf::load_object(fs::file(vfs::get(dir + "/PARAM.SFO")));
|
|
|
|
if (psf.count("PARENTAL_LEVEL") != 0) getParam->parentalLevel = psf.at("PARENTAL_LEVEL").as_integer();
|
|
if (psf.count("TITLE_ID") != 0) strcpy_trunc(getParam->titleId, psf.at("TITLE_ID").as_string());
|
|
|
|
return CELL_OK;
|
|
}
|
|
|
|
s32 cellDiscGameRegisterDiscChangeCallback()
|
|
{
|
|
UNIMPLEMENTED_FUNC(cellGame);
|
|
return CELL_OK;
|
|
}
|
|
|
|
s32 cellDiscGameUnregisterDiscChangeCallback()
|
|
{
|
|
UNIMPLEMENTED_FUNC(cellGame);
|
|
return CELL_OK;
|
|
}
|
|
|
|
s32 cellGameRegisterDiscChangeCallback()
|
|
{
|
|
UNIMPLEMENTED_FUNC(cellGame);
|
|
return CELL_OK;
|
|
}
|
|
|
|
s32 cellGameUnregisterDiscChangeCallback()
|
|
{
|
|
UNIMPLEMENTED_FUNC(cellGame);
|
|
return CELL_OK;
|
|
}
|
|
|
|
|
|
void cellSysutil_GameData_init()
|
|
{
|
|
REG_FUNC(cellSysutil, cellHddGameCheck);
|
|
REG_FUNC(cellSysutil, cellHddGameCheck2);
|
|
REG_FUNC(cellSysutil, cellHddGameGetSizeKB);
|
|
REG_FUNC(cellSysutil, cellHddGameSetSystemVer);
|
|
REG_FUNC(cellSysutil, cellHddGameExitBroken);
|
|
|
|
REG_FUNC(cellSysutil, cellGameDataGetSizeKB);
|
|
REG_FUNC(cellSysutil, cellGameDataSetSystemVer);
|
|
REG_FUNC(cellSysutil, cellGameDataExitBroken);
|
|
|
|
REG_FUNC(cellSysutil, cellGameDataCheckCreate);
|
|
REG_FUNC(cellSysutil, cellGameDataCheckCreate2);
|
|
|
|
REG_FUNC(cellSysutil, cellDiscGameGetBootDiscInfo);
|
|
REG_FUNC(cellSysutil, cellDiscGameRegisterDiscChangeCallback);
|
|
REG_FUNC(cellSysutil, cellDiscGameUnregisterDiscChangeCallback);
|
|
REG_FUNC(cellSysutil, cellGameRegisterDiscChangeCallback);
|
|
REG_FUNC(cellSysutil, cellGameUnregisterDiscChangeCallback);
|
|
}
|
|
|
|
DECLARE(ppu_module_manager::cellGame)("cellGame", []()
|
|
{
|
|
REG_FUNC(cellGame, cellGameBootCheck);
|
|
REG_FUNC(cellGame, cellGamePatchCheck);
|
|
REG_FUNC(cellGame, cellGameDataCheck);
|
|
REG_FUNC(cellGame, cellGameContentPermit);
|
|
|
|
REG_FUNC(cellGame, cellGameCreateGameData);
|
|
REG_FUNC(cellGame, cellGameDeleteGameData);
|
|
|
|
REG_FUNC(cellGame, cellGameGetParamInt);
|
|
REG_FUNC(cellGame, cellGameGetParamString);
|
|
REG_FUNC(cellGame, cellGameSetParamString);
|
|
REG_FUNC(cellGame, cellGameGetSizeKB);
|
|
REG_FUNC(cellGame, cellGameGetDiscContentInfoUpdatePath);
|
|
REG_FUNC(cellGame, cellGameGetLocalWebContentPath);
|
|
|
|
REG_FUNC(cellGame, cellGameContentErrorDialog);
|
|
|
|
REG_FUNC(cellGame, cellGameThemeInstall);
|
|
REG_FUNC(cellGame, cellGameThemeInstallFromBuffer);
|
|
});
|