rpcsx/rpcs3/Emu/Cell/Modules/cellSaveData.cpp
Nekotekina f841b47b6b cellSaveData: add auto maintenance routine in Emu.Init()
This routine:
1) Removes junk backup directories
2) Fixes interrupted save data process in edge case
This case can happen if emu terminates between two atomic renames.

Also use directory renaming technique for delete op.
Also rewrite recreate operation to be part of atomic process.
2019-09-25 18:47:38 +03:00

1799 lines
59 KiB
C++

#include "stdafx.h"
#include "Emu/System.h"
#include "Emu/Cell/lv2/sys_sync.h"
#include "Emu/Cell/lv2/sys_process.h"
#include "Emu/Cell/PPUModule.h"
#include "Emu/Cell/Modules/cellSysutil.h"
#include "cellSaveData.h"
#include "cellMsgDialog.h"
#include "Loader/PSF.h"
#include "Utilities/StrUtil.h"
#include <thread>
#include <mutex>
#include <algorithm>
LOG_CHANNEL(cellSaveData);
template<>
void fmt_class_string<CellSaveDataError>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](auto error)
{
switch (error)
{
STR_CASE(CELL_SAVEDATA_ERROR_CBRESULT);
STR_CASE(CELL_SAVEDATA_ERROR_ACCESS_ERROR);
STR_CASE(CELL_SAVEDATA_ERROR_INTERNAL);
STR_CASE(CELL_SAVEDATA_ERROR_PARAM);
STR_CASE(CELL_SAVEDATA_ERROR_NOSPACE);
STR_CASE(CELL_SAVEDATA_ERROR_BROKEN);
STR_CASE(CELL_SAVEDATA_ERROR_FAILURE);
STR_CASE(CELL_SAVEDATA_ERROR_BUSY);
STR_CASE(CELL_SAVEDATA_ERROR_NOUSER);
STR_CASE(CELL_SAVEDATA_ERROR_SIZEOVER);
STR_CASE(CELL_SAVEDATA_ERROR_NODATA);
STR_CASE(CELL_SAVEDATA_ERROR_NOTSUPPORTED);
}
return unknown;
});
}
SaveDialogBase::~SaveDialogBase()
{
}
// cellSaveData aliases (only for cellSaveData.cpp)
using PSetList = vm::ptr<CellSaveDataSetList>;
using PSetBuf = vm::ptr<CellSaveDataSetBuf>;
using PFuncFixed = vm::ptr<CellSaveDataFixedCallback>;
using PFuncList = vm::ptr<CellSaveDataListCallback>;
using PFuncStat = vm::ptr<CellSaveDataStatCallback>;
using PFuncFile = vm::ptr<CellSaveDataFileCallback>;
using PFuncDone = vm::ptr<CellSaveDataDoneCallback>;
enum : u32
{
SAVEDATA_OP_AUTO_SAVE = 0,
SAVEDATA_OP_AUTO_LOAD = 1,
SAVEDATA_OP_LIST_AUTO_SAVE = 2,
SAVEDATA_OP_LIST_AUTO_LOAD = 3,
SAVEDATA_OP_LIST_SAVE = 4,
SAVEDATA_OP_LIST_LOAD = 5,
SAVEDATA_OP_FIXED_SAVE = 6,
SAVEDATA_OP_FIXED_LOAD = 7,
SAVEDATA_OP_LIST_DELETE = 13,
SAVEDATA_OP_FIXED_DELETE = 14,
};
namespace
{
struct savedata_context
{
CellSaveDataCBResult result;
CellSaveDataListGet listGet;
CellSaveDataListSet listSet;
CellSaveDataFixedSet fixedSet;
CellSaveDataStatGet statGet;
CellSaveDataStatSet statSet;
CellSaveDataFileGet fileGet;
CellSaveDataFileSet fileSet;
CellSaveDataDoneGet doneGet;
};
}
vm::gvar<savedata_context> g_savedata_context;
std::mutex g_savedata_mutex;
static std::vector<SaveDataEntry> get_save_entries(const std::string& base_dir, const std::string& prefix)
{
std::vector<SaveDataEntry> save_entries;
if (base_dir.empty() || prefix.empty())
{
return save_entries;
}
// get the saves matching the supplied prefix
for (auto&& entry : fs::dir(base_dir))
{
if (!entry.is_directory || entry.name == "." || entry.name == "..")
{
continue;
}
entry.name = vfs::unescape(entry.name);
if (entry.name.substr(0, prefix.size()) != prefix)
{
continue;
}
// PSF parameters
const psf::registry psf = psf::load_object(fs::file(base_dir + entry.name + "/PARAM.SFO"));
if (psf.empty())
{
continue;
}
SaveDataEntry save_entry;
save_entry.dirName = psf.at("SAVEDATA_DIRECTORY").as_string();
save_entry.listParam = psf.at("SAVEDATA_LIST_PARAM").as_string();
save_entry.title = psf.at("TITLE").as_string();
save_entry.subtitle = psf.at("SUB_TITLE").as_string();
save_entry.details = psf.at("DETAIL").as_string();
for (const auto entry2 : fs::dir(base_dir + entry.name))
{
save_entry.size += entry2.size;
}
save_entry.atime = entry.atime;
save_entry.mtime = entry.mtime;
save_entry.ctime = entry.ctime;
if (fs::file icon{base_dir + entry.name + "/ICON0.PNG"})
save_entry.iconBuf = icon.to_vector<uchar>();
save_entry.isNew = false;
save_entries.emplace_back(save_entry);
}
return save_entries;
}
static error_code select_and_delete(ppu_thread& ppu)
{
std::unique_lock lock(g_savedata_mutex, std::try_to_lock);
if (!lock)
{
return CELL_SAVEDATA_ERROR_BUSY;
}
const std::string base_dir = vfs::get(fmt::format("/dev_hdd0/home/%08u/savedata/", Emu.GetUsrId()));
auto save_entries = get_save_entries(base_dir, Emu.GetTitleID());
s32 selected = -1;
s32 focused = -1;
while (true)
{
// Yield
lv2_obj::sleep(ppu);
// Display Save Data List asynchronously in the GUI thread.
selected = Emu.GetCallbacks().get_save_dialog()->ShowSaveDataList(save_entries, focused, SAVEDATA_OP_LIST_DELETE, vm::null);
// Reschedule
if (ppu.check_state())
{
return 0;
}
// Abort if dialog was canceled
if (selected == -2)
{
return CELL_CANCEL;
}
// Set focused entry for the next iteration
focused = save_entries.empty() ? -1 : selected;
// Get information from the selected entry
SaveDataEntry entry = save_entries[selected];
const std::string info = entry.title + "\n" + entry.subtitle + "\n" + entry.details;
// Get user confirmation
std::string msg = "Do you really want to delete this entry?\n\n" + info;
error_code res = open_msg_dialog(true, CELL_MSGDIALOG_TYPE_SE_TYPE_NORMAL | CELL_MSGDIALOG_TYPE_BUTTON_TYPE_YESNO, vm::make_str(msg));
if (res != CELL_OK)
{
return CELL_SAVEDATA_ERROR_INTERNAL;
}
if (g_last_user_response.load() == CELL_MSGDIALOG_BUTTON_YES)
{
// Remove directory
const std::string path = base_dir + save_entries[selected].dirName;
fs::remove_all(path);
// Remove entry from the list and reset the selection
save_entries.erase(save_entries.cbegin() + selected);
selected = -1;
// Reset the focused index if the new list is empty
if (save_entries.empty())
{
focused = -1;
}
// Display success message (return value should be irrelevant here)
msg = "Successfully removed entry!\n\n" + info;
cellSaveData.success("%s", msg);
res = open_msg_dialog(true, CELL_MSGDIALOG_TYPE_SE_TYPE_NORMAL | CELL_MSGDIALOG_TYPE_BUTTON_TYPE_OK, vm::make_str(msg));
}
}
return CELL_CANCEL;
}
static s32 savedata_check_args(u32 operation, u32 version, vm::cptr<char> dirName,
u32 errDialog, PSetList setList, PSetBuf setBuf, PFuncList funcList, PFuncFixed funcFixed, PFuncStat funcStat,
PFuncFile funcFile, u32 container, u32 unk_op_flags, vm::ptr<void> userdata, u32 userId, PFuncDone funcDone)
{
if (version > CELL_SAVEDATA_VERSION_420)
{
// ****** sysutil savedata parameter error : 1 ******
return 1;
}
if (errDialog > CELL_SAVEDATA_ERRDIALOG_NOREPEAT)
{
// ****** sysutil savedata parameter error : 5 ******
return 5;
}
if (operation <= SAVEDATA_OP_AUTO_LOAD && !dirName)
{
// ****** sysutil savedata parameter error : 2 ******
return 2;
}
if ((operation >= SAVEDATA_OP_LIST_AUTO_SAVE && operation <= SAVEDATA_OP_FIXED_LOAD) || operation == SAVEDATA_OP_FIXED_DELETE)
{
if (!setList)
{
// ****** sysutil savedata parameter error : 11 ******
return 11;
}
if (setList->sortType > CELL_SAVEDATA_SORTTYPE_SUBTITLE)
{
// ****** sysutil savedata parameter error : 12 ******
return 12;
}
if (setList->sortOrder > CELL_SAVEDATA_SORTORDER_ASCENT)
{
// ****** sysutil savedata parameter error : 13 ******
return 13;
}
if (!setList->dirNamePrefix)
{
// ****** sysutil savedata parameter error : 15 ******
return 15;
}
if (!memchr(setList->dirNamePrefix.get_ptr(), '\0', CELL_SAVEDATA_PREFIX_SIZE)
|| (g_ps3_process_info.sdk_ver > 0x3FFFFF && !setList->dirNamePrefix[0]))
{
// ****** sysutil savedata parameter error : 17 ******
return 17;
}
// TODO: Theres some check here I've missed about dirNamePrefix
if (setList->reserved)
{
// ****** sysutil savedata parameter error : 14 ******
return 14;
}
}
if (!setBuf)
{
// ****** sysutil savedata parameter error : 74 ******
return 74;
}
if ((operation >= SAVEDATA_OP_LIST_AUTO_SAVE && operation <= SAVEDATA_OP_FIXED_LOAD) || operation == SAVEDATA_OP_FIXED_DELETE)
{
if (setBuf->dirListMax > CELL_SAVEDATA_DIRLIST_MAX)
{
// ****** sysutil savedata parameter error : 8 ******
return 8;
}
CHECK_SIZE(CellSaveDataDirList, 48);
if (setBuf->dirListMax * sizeof(CellSaveDataDirList) > setBuf->bufSize)
{
// ****** sysutil savedata parameter error : 7 ******
return 7;
}
}
CHECK_SIZE(CellSaveDataFileStat, 56);
if (operation == SAVEDATA_OP_FIXED_DELETE)
{
if (setBuf->fileListMax != 0)
{
// ****** sysutil savedata parameter error : 9 ******
return 9;
}
}
else if (setBuf->fileListMax * sizeof(CellSaveDataFileStat) > setBuf->bufSize)
{
// ****** sysutil savedata parameter error : 7 ******
return 7;
}
if (setBuf->bufSize && !setBuf->buf)
{
// ****** sysutil savedata parameter error : 6 ******
return 6;
}
for (auto resv : setBuf->reserved)
{
if (resv.raw() != 0)
{
// ****** sysutil savedata parameter error : 10 ******
return 10;
}
}
if ((operation == SAVEDATA_OP_LIST_SAVE || operation == SAVEDATA_OP_LIST_LOAD) && !funcList)
{
// ****** sysutil savedata parameter error : 18 ******
return 18;
}
else if ((operation == SAVEDATA_OP_FIXED_SAVE || operation == SAVEDATA_OP_FIXED_LOAD ||
operation == SAVEDATA_OP_LIST_AUTO_LOAD || operation == SAVEDATA_OP_LIST_AUTO_SAVE || operation == SAVEDATA_OP_FIXED_DELETE) && !funcFixed)
{
// ****** sysutil savedata parameter error : 19 ******
return 19;
}
if (!(unk_op_flags & 0x2) || operation == SAVEDATA_OP_AUTO_SAVE || operation == SAVEDATA_OP_AUTO_LOAD)
{
if (!funcStat)
{
// ****** sysutil savedata parameter error : 20 ******
return 20;
}
if (!(unk_op_flags & 0x2) && !funcFile)
{
// ****** sysutil savedata parameter error : 18 ******
return 18;
}
}
return CELL_OK;
}
static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 version, vm::cptr<char> dirName,
u32 errDialog, PSetList setList, PSetBuf setBuf, PFuncList funcList, PFuncFixed funcFixed, PFuncStat funcStat,
PFuncFile funcFile, u32 container, u32 unk_op_flags /*TODO*/, vm::ptr<void> userdata, u32 userId, PFuncDone funcDone)
{
if (const auto ecode = savedata_check_args(operation, version, dirName, errDialog, setList, setBuf, funcList, funcFixed, funcStat,
funcFile, container, unk_op_flags, userdata, userId, funcDone))
{
return {CELL_SAVEDATA_ERROR_PARAM, std::to_string(ecode)};
}
std::unique_lock lock(g_savedata_mutex, std::try_to_lock);
if (!lock)
{
return CELL_SAVEDATA_ERROR_BUSY;
}
// Simulate idle time while data is being sent to VSH
const auto lv2_sleep = [](ppu_thread& ppu, size_t sleep_time)
{
lv2_obj::sleep(ppu);
std::this_thread::sleep_for(std::chrono::microseconds(sleep_time));
ppu.check_state();
};
lv2_sleep(ppu, 500);
*g_savedata_context = {};
vm::ptr<CellSaveDataCBResult> result = g_savedata_context.ptr(&savedata_context::result);
vm::ptr<CellSaveDataListGet> listGet = g_savedata_context.ptr(&savedata_context::listGet);
vm::ptr<CellSaveDataListSet> listSet = g_savedata_context.ptr(&savedata_context::listSet);
vm::ptr<CellSaveDataFixedSet> fixedSet = g_savedata_context.ptr(&savedata_context::fixedSet);
vm::ptr<CellSaveDataStatGet> statGet = g_savedata_context.ptr(&savedata_context::statGet);
vm::ptr<CellSaveDataStatSet> statSet = g_savedata_context.ptr(&savedata_context::statSet);
vm::ptr<CellSaveDataFileGet> fileGet = g_savedata_context.ptr(&savedata_context::fileGet);
vm::ptr<CellSaveDataFileSet> fileSet = g_savedata_context.ptr(&savedata_context::fileSet);
vm::ptr<CellSaveDataDoneGet> doneGet = g_savedata_context.ptr(&savedata_context::doneGet);
// userId(0) = CELL_SYSUTIL_USERID_CURRENT;
// path of the specified user (00000001 by default)
const std::string base_dir = vfs::get(fmt::format("/dev_hdd0/home/%08u/savedata/", userId ? userId : Emu.GetUsrId()));
result->userdata = userdata; // probably should be assigned only once (allows the callback to change it)
SaveDataEntry save_entry;
if (setList)
{
std::vector<SaveDataEntry> save_entries;
listGet->dirNum = 0;
listGet->dirListNum = 0;
listGet->dirList.set(setBuf->buf.addr());
std::memset(listGet->reserved, 0, sizeof(listGet->reserved));
const auto prefix_list = fmt::split(setList->dirNamePrefix.get_ptr(), {"|"});
// get the saves matching the supplied prefix
for (auto&& entry : fs::dir(base_dir))
{
if (!entry.is_directory || entry.name == "." || entry.name == "..")
{
continue;
}
entry.name = vfs::unescape(entry.name);
for (const auto& prefix : prefix_list)
{
if (entry.name.substr(0, prefix.size()) == prefix)
{
// Count the amount of matches and the amount of listed directories
listGet->dirNum++; // total number of directories
if (listGet->dirListNum < setBuf->dirListMax)
{
listGet->dirListNum++; // number of directories in list
// PSF parameters
const psf::registry psf = psf::load_object(fs::file(base_dir + entry.name + "/PARAM.SFO"));
if (psf.empty())
{
break;
}
SaveDataEntry save_entry2;
save_entry2.dirName = psf.at("SAVEDATA_DIRECTORY").as_string();
save_entry2.listParam = psf.at("SAVEDATA_LIST_PARAM").as_string();
save_entry2.title = psf.at("TITLE").as_string();
save_entry2.subtitle = psf.at("SUB_TITLE").as_string();
save_entry2.details = psf.at("DETAIL").as_string();
for (const auto entry2 : fs::dir(base_dir + entry.name))
{
save_entry2.size += entry2.size;
}
save_entry2.atime = entry.atime;
save_entry2.mtime = entry.mtime;
save_entry2.ctime = entry.ctime;
if (fs::file icon{base_dir + entry.name + "/ICON0.PNG"})
save_entry2.iconBuf = icon.to_vector<uchar>();
save_entry2.isNew = false;
save_entries.emplace_back(save_entry2);
}
break;
}
}
}
// Sort the entries
{
const u32 order = setList->sortOrder;
const u32 type = setList->sortType;
std::sort(save_entries.begin(), save_entries.end(), [=](const SaveDataEntry& entry1, const SaveDataEntry& entry2)
{
if (order == CELL_SAVEDATA_SORTORDER_DESCENT && type == CELL_SAVEDATA_SORTTYPE_MODIFIEDTIME)
{
return entry1.mtime >= entry2.mtime;
}
if (order == CELL_SAVEDATA_SORTORDER_DESCENT && type == CELL_SAVEDATA_SORTTYPE_SUBTITLE)
{
return entry1.subtitle >= entry2.subtitle;
}
if (order == CELL_SAVEDATA_SORTORDER_ASCENT && type == CELL_SAVEDATA_SORTTYPE_MODIFIEDTIME)
{
return entry1.mtime < entry2.mtime;
}
if (order == CELL_SAVEDATA_SORTORDER_ASCENT && type == CELL_SAVEDATA_SORTTYPE_SUBTITLE)
{
return entry1.subtitle < entry2.subtitle;
}
return true;
});
}
// Fill the listGet->dirList array
auto dir_list = listGet->dirList.get_ptr();
for (const auto& entry : save_entries)
{
auto& dir = *dir_list++;
strcpy_trunc(dir.dirName, entry.dirName);
strcpy_trunc(dir.listParam, entry.listParam);
std::memset(dir.reserved, 0, sizeof(dir.reserved));
}
s32 selected = -1;
s32 focused = -1;
if (funcList)
{
// List Callback
funcList(ppu, result, listGet, listSet);
if (result->result < 0)
{
//TODO: display dialog
cellSaveData.warning("savedata_op(): funcList returned result=%d.", result->result);
return CELL_SAVEDATA_ERROR_CBRESULT;
}
// if the callback has returned ok, lets return OK.
// typically used at game launch when no list is actually required.
// CELL_SAVEDATA_CBRESULT_OK_LAST_NOCONFIRM is only valid for funcFile and funcDone
if (result->result == CELL_SAVEDATA_CBRESULT_OK_LAST || result->result == CELL_SAVEDATA_CBRESULT_OK_LAST_NOCONFIRM)
{
return CELL_OK;
}
// Clean save data list
save_entries.erase(std::remove_if(save_entries.begin(), save_entries.end(), [&listSet](const SaveDataEntry& entry) -> bool
{
for (u32 i = 0; i < listSet->fixedListNum; i++)
{
if (entry.dirName == listSet->fixedList[i].dirName)
{
return false;
}
}
return true;
}), save_entries.end());
switch (const u32 pos_type = listSet->focusPosition)
{
case CELL_SAVEDATA_FOCUSPOS_DIRNAME:
{
for (s32 i = 0; i < save_entries.size(); i++)
{
if (save_entries[i].dirName == listSet->focusDirName.get_ptr())
{
focused = i;
break;
}
}
break;
}
case CELL_SAVEDATA_FOCUSPOS_LISTHEAD:
{
focused = save_entries.empty() ? -1 : 0;
break;
}
case CELL_SAVEDATA_FOCUSPOS_LISTTAIL:
{
focused = ::size32(save_entries) - 1;
break;
}
case CELL_SAVEDATA_FOCUSPOS_LATEST:
{
s64 max = INT64_MIN;
for (s32 i = 0; i < save_entries.size(); i++)
{
if (save_entries[i].mtime > max)
{
focused = i;
max = save_entries[i].mtime;
}
}
break;
}
case CELL_SAVEDATA_FOCUSPOS_OLDEST:
{
s64 min = INT64_MAX;
for (s32 i = 0; i < save_entries.size(); i++)
{
if (save_entries[i].mtime < min)
{
focused = i;
min = save_entries[i].mtime;
}
}
break;
}
case CELL_SAVEDATA_FOCUSPOS_NEWDATA:
{
//TODO: If adding the new data to the save_entries vector
// to be displayed in the save mangaer UI, it should be focused here
break;
}
default:
{
// ****** sysutil savedata parameter error : 34 ******
cellSaveData.error("savedata_op(): unknown listSet->focusPosition (0x%x)", pos_type);
return {CELL_SAVEDATA_ERROR_PARAM, "34"};
}
}
}
auto delete_save = [&]()
{
strcpy_trunc(doneGet->dirName, save_entries[selected].dirName);
doneGet->hddFreeSizeKB = 40 * 1024 * 1024 - 1; // Read explanation in cellHddGameCheck
doneGet->sizeKB = 0;
doneGet->excResult = CELL_OK;
std::memset(doneGet->reserved, 0, sizeof(doneGet->reserved));
const std::string old_path = base_dir + ".backup_" + save_entries[selected].dirName + "/";
const std::string del_path = base_dir + save_entries[selected].dirName + "/";
const fs::dir _dir(del_path);
for (auto&& file : _dir)
{
if (!file.is_directory)
{
doneGet->sizeKB += static_cast<s32>(::align(file.size, 4096));
}
}
if (_dir)
{
// Remove old backup
fs::remove_all(old_path);
// Remove savedata by renaming
if (!vfs::host::rename(del_path, old_path, false))
{
fmt::throw_exception("Failed to move directory %s (%s)", del_path, fs::g_tls_error);
}
// Cleanup
fs::remove_all(old_path);
}
else
{
doneGet->excResult = CELL_SAVEDATA_ERROR_NODATA;
}
funcDone(ppu, result, doneGet);
};
while (funcList)
{
// Yield
lv2_obj::sleep(ppu);
// Display Save Data List asynchronously in the GUI thread.
if (auto save_dialog = Emu.GetCallbacks().get_save_dialog())
{
selected = save_dialog->ShowSaveDataList(save_entries, focused, operation, listSet);
}
else
{
selected = -2;
}
// Reschedule
if (ppu.check_state())
{
return 0;
}
// UI returns -1 for new save games
if (selected == -1)
{
save_entry.dirName = listSet->newData->dirName.get_ptr();
}
// Cancel selected in UI
if (selected == -2)
{
return CELL_CANCEL;
}
if (operation == SAVEDATA_OP_LIST_DELETE)
{
delete_save();
if (result->result < 0)
{
cellSaveData.warning("savedata_op(): funcDone returned result=%d.", result->result);
return CELL_SAVEDATA_ERROR_CBRESULT;
}
if (result->result == CELL_SAVEDATA_CBRESULT_OK_LAST || result->result == CELL_SAVEDATA_CBRESULT_OK_LAST_NOCONFIRM)
{
return CELL_OK;
}
// CELL_SAVEDATA_CBRESULT_OK_NEXT expected
save_entries.erase(save_entries.cbegin() + selected);
focused = save_entries.empty() ? -1 : selected;
selected = -1;
continue;
}
break;
}
if (funcFixed)
{
lv2_sleep(ppu, 250);
// Fixed Callback
funcFixed(ppu, result, listGet, fixedSet);
// check result for validity - CELL_SAVEDATA_CBRESULT_OK_LAST_NOCONFIRM is not a valid result for funcFixed
if (result->result < CELL_SAVEDATA_CBRESULT_ERR_INVALID || result->result >= CELL_SAVEDATA_CBRESULT_OK_LAST_NOCONFIRM)
{
cellSaveData.error("savedata_op(): funcFixed returned result=%d.", result->result);
return CELL_SAVEDATA_ERROR_PARAM;
}
// skip all following steps if OK_LAST
if (result->result == CELL_SAVEDATA_CBRESULT_OK_LAST)
{
return CELL_OK;
}
if (result->result < 0)
{
//TODO: Show msgDialog if required
// depends on fixedSet->option
// 0 = none
// 1 = skip confirmation dialog
cellSaveData.warning("savedata_op(): funcFixed returned result=%d.", result->result);
return CELL_SAVEDATA_ERROR_CBRESULT;
}
if (!fixedSet->dirName)
{
// ****** sysutil savedata parameter error : 26 ******
return {CELL_SAVEDATA_ERROR_PARAM, "26"};
}
for (s32 i = 0; i < save_entries.size(); i++)
{
if (save_entries[i].dirName == fixedSet->dirName.get_ptr())
{
selected = i;
break;
}
}
if (selected == -1)
{
save_entry.dirName = fixedSet->dirName.get_ptr();
}
if (operation == SAVEDATA_OP_FIXED_DELETE)
{
delete_save();
if (result->result < 0)
{
cellSaveData.warning("savedata_op(): funcDone_ returned result=%d.", result->result);
return CELL_SAVEDATA_ERROR_CBRESULT;
}
return CELL_OK;
}
}
if (selected >= 0)
{
if (selected < save_entries.size())
{
save_entry.dirName = std::move(save_entries[selected].dirName);
}
else
{
fmt::throw_exception("Invalid savedata selected" HERE);
}
}
}
if (dirName)
{
save_entry.dirName = dirName.get_ptr();
}
const std::string dir_path = base_dir + save_entry.dirName + "/";
const std::string old_path = base_dir + ".backup_" + save_entry.dirName + "/";
const std::string new_path = base_dir + ".working_" + save_entry.dirName + "/";
psf::registry psf = psf::load_object(fs::file(dir_path + "PARAM.SFO"));
bool has_modified = false;
bool recreated = false;
lv2_sleep(ppu, 250);
// Check if RPCS3_BLIST section exist in PARAM.SFO
// This section contains the list of files in the save ordered as they would be in BSD filesystem
std::vector<std::string> blist;
if (psf.count("RPCS3_BLIST"))
blist = fmt::split(psf.at("RPCS3_BLIST").as_string(), {"/"}, false);
// Get save stats
{
fs::stat_t dir_info{};
if (!fs::stat(dir_path, dir_info))
{
// funcStat is called even if the directory doesn't exist.
}
statGet->hddFreeSizeKB = 40 * 1024 * 1024 - 1; // Read explanation in cellHddGameCheck
statGet->isNewData = save_entry.isNew = psf.empty();
statGet->dir.atime = save_entry.atime = dir_info.atime;
statGet->dir.mtime = save_entry.mtime = dir_info.mtime;
statGet->dir.ctime = save_entry.ctime = dir_info.ctime;
strcpy_trunc(statGet->dir.dirName, save_entry.dirName);
if (!psf.empty())
{
statGet->getParam.parental_level = psf.at("PARENTAL_LEVEL").as_integer();
statGet->getParam.attribute = psf.at("ATTRIBUTE").as_integer(); // ???
strcpy_trunc(statGet->getParam.title, save_entry.title = psf.at("TITLE").as_string());
strcpy_trunc(statGet->getParam.subTitle, save_entry.subtitle = psf.at("SUB_TITLE").as_string());
strcpy_trunc(statGet->getParam.detail, save_entry.details = psf.at("DETAIL").as_string());
strcpy_trunc(statGet->getParam.listParam, save_entry.listParam = psf.at("SAVEDATA_LIST_PARAM").as_string());
}
statGet->bind = 0;
statGet->fileNum = 0;
statGet->fileList.set(setBuf->buf.addr());
statGet->fileListNum = 0;
memset(statGet->reserved, 0, sizeof(statGet->reserved));
auto file_list = statGet->fileList.get_ptr();
u32 size_kbytes = 0;
std::vector<fs::dir_entry> files_sorted;
for (auto&& entry : fs::dir(dir_path))
{
entry.name = vfs::unescape(entry.name);
if (!entry.is_directory)
{
if (entry.name == "PARAM.SFO" || entry.name == "PARAM.PFD")
{
continue; // system files are not included in the file list
}
files_sorted.push_back(entry);
}
}
// clang-format off
std::sort(files_sorted.begin(), files_sorted.end(), [&](const fs::dir_entry& a, const fs::dir_entry& b) -> bool
{
const auto a_it = std::find(blist.begin(), blist.end(), a.name);
const auto b_it = std::find(blist.begin(), blist.end(), b.name);
if (a_it == blist.end() && b_it == blist.end())
{
// Order alphabetically for old saves
return a.name.compare(b.name);
}
return a_it < b_it;
});
// clang-format on
for (auto&& entry : files_sorted)
{
{
statGet->fileNum++;
size_kbytes += (entry.size + 1023) / 1024; // firmware rounds this value up
if (statGet->fileListNum >= setBuf->fileListMax)
continue;
statGet->fileListNum++;
auto& file = *file_list++;
file.size = entry.size;
file.atime = entry.atime;
file.mtime = entry.mtime;
file.ctime = entry.ctime;
strcpy_trunc(file.fileName, entry.name);
if (entry.name == "ICON0.PNG")
{
file.fileType = CELL_SAVEDATA_FILETYPE_CONTENT_ICON0;
}
else if (entry.name == "ICON1.PAM")
{
file.fileType = CELL_SAVEDATA_FILETYPE_CONTENT_ICON1;
}
else if (entry.name == "PIC1.PNG")
{
file.fileType = CELL_SAVEDATA_FILETYPE_CONTENT_PIC1;
}
else if (entry.name == "SND0.AT3")
{
file.fileType = CELL_SAVEDATA_FILETYPE_CONTENT_SND0;
}
else if (psf::get_integer(psf, "*" + entry.name)) // let's put the list of protected files in PARAM.SFO (int param = 1 if protected)
{
file.fileType = CELL_SAVEDATA_FILETYPE_SECUREFILE;
}
else
{
file.fileType = CELL_SAVEDATA_FILETYPE_NORMALFILE;
}
}
}
statGet->sysSizeKB = 35; // always reported as 35 regardless of actual file sizes
statGet->sizeKB = !save_entry.isNew ? size_kbytes + statGet->sysSizeKB : 0;
// Stat Callback
funcStat(ppu, result, statGet, statSet);
if (result->result != CELL_SAVEDATA_CBRESULT_OK_NEXT)
{
cellSaveData.warning("savedata_op(): funcStat returned result=%d.", result->result);
// Skip and error
if (result->result >= CELL_SAVEDATA_CBRESULT_OK_LAST_NOCONFIRM || result->result < CELL_SAVEDATA_CBRESULT_ERR_INVALID)
{
// ****** sysutil savedata parameter error : 22 ******
return {CELL_SAVEDATA_ERROR_PARAM, "22"};
}
if (result->result < CELL_SAVEDATA_CBRESULT_OK_NEXT)
{
return CELL_SAVEDATA_ERROR_CBRESULT;
}
// Skip and return without error
if (result->result == CELL_SAVEDATA_CBRESULT_OK_LAST)
{
return CELL_OK;
}
}
if (statSet->setParam)
{
if (statSet->setParam->attribute > CELL_SAVEDATA_ATTR_NODUPLICATE)
{
// ****** sysutil savedata parameter error : 57 ******
return {CELL_SAVEDATA_ERROR_PARAM, "57"};
}
if (g_ps3_process_info.sdk_ver > 0x36FFFF)
{
// In firmware 3.70 or higher parental_level was changed to reserved2 and has to zeroes
if (statSet->setParam->parental_level)
{
// ****** sysutil savedata parameter error : 58 ******
return {CELL_SAVEDATA_ERROR_PARAM, "58"};
}
}
for (u8 resv : statSet->setParam->reserved)
{
if (resv)
{
// ****** sysutil savedata parameter error : 59 ******
return {CELL_SAVEDATA_ERROR_PARAM, "59"};
}
}
// Update PARAM.SFO
psf.clear();
psf.insert(
{
{ "ACCOUNT_ID", psf::array(16, "0000000000000000") }, // ???
{ "ATTRIBUTE", statSet->setParam->attribute.value() },
{ "CATEGORY", psf::string(4, "SD") }, // ???
{ "PARAMS", psf::string(1024, {}) }, // ???
{ "PARAMS2", psf::string(12, {}) }, // ???
{ "PARENTAL_LEVEL", statSet->setParam->parental_level.value() },
{ "DETAIL", psf::string(CELL_SAVEDATA_SYSP_DETAIL_SIZE, statSet->setParam->detail) },
{ "SAVEDATA_DIRECTORY", psf::string(CELL_SAVEDATA_DIRNAME_SIZE, save_entry.dirName) },
{ "SAVEDATA_LIST_PARAM", psf::string(CELL_SAVEDATA_SYSP_LPARAM_SIZE, statSet->setParam->listParam) },
{ "SUB_TITLE", psf::string(CELL_SAVEDATA_SYSP_SUBTITLE_SIZE, statSet->setParam->subTitle) },
{ "TITLE", psf::string(CELL_SAVEDATA_SYSP_TITLE_SIZE, statSet->setParam->title) }
});
has_modified = true;
}
else if (save_entry.isNew)
{
// ****** sysutil savedata parameter error : 50 ******
return {CELL_SAVEDATA_ERROR_PARAM, "50"};
}
switch (const u32 mode = statSet->reCreateMode & CELL_SAVEDATA_RECREATE_MASK)
{
case CELL_SAVEDATA_RECREATE_NO:
{
//CELL_SAVEDATA_RECREATE_NO = overwrite and let the user know, not data is corrupt.
//cellSaveData.error("Savedata %s considered broken", save_entry.dirName);
//TODO: if this is a save, and it's not auto, then show a dialog
// fallthrough
}
case CELL_SAVEDATA_RECREATE_NO_NOBROKEN:
{
break;
}
case CELL_SAVEDATA_RECREATE_YES:
case CELL_SAVEDATA_RECREATE_YES_RESET_OWNER:
{
if (!statSet->setParam)
{
// ****** sysutil savedata parameter error : 50 ******
return {CELL_SAVEDATA_ERROR_PARAM, "50"};
}
// Clear secure file info
for (auto it = psf.cbegin(), end = psf.cend(); it != end;)
{
if (it->first[0] == '*')
it = psf.erase(it);
else
it++;
}
// Clear order info
blist.clear();
// Set to not load files
has_modified = true;
recreated = true;
break;
}
default:
{
// ****** sysutil savedata parameter error : 48 ******
cellSaveData.error("savedata_op(): unknown statSet->reCreateMode (0x%x)", statSet->reCreateMode);
return {CELL_SAVEDATA_ERROR_PARAM, "48"};
}
}
}
// Create save directory if necessary
if (psf.size() && save_entry.isNew && !fs::create_dir(dir_path))
{
cellSaveData.warning("savedata_op(): failed to create %s", dir_path);
return CELL_SAVEDATA_ERROR_ACCESS_ERROR;
}
// Enter the loop where the save files are read/created/deleted
std::map<std::string, std::pair<s64, s64>> all_times;
std::map<std::string, fs::file> all_files;
// First, preload all files (TODO: beware of possible lag, although it should be insignificant)
for (auto&& entry : fs::dir(dir_path))
{
if (!recreated && !entry.is_directory)
{
// Read file into a vector and make a memory file
all_times.emplace(entry.name, std::make_pair(entry.atime, entry.mtime));
all_files.emplace(std::move(entry.name), fs::make_stream(fs::file(dir_path + entry.name).to_vector<uchar>()));
}
}
fileGet->excSize = 0;
memset(fileGet->reserved, 0, sizeof(fileGet->reserved));
while (funcFile)
{
funcFile(ppu, result, fileGet, fileSet);
if (result->result < 0)
{
cellSaveData.warning("savedata_op(): funcFile returned result=%d.", result->result);
return CELL_SAVEDATA_ERROR_CBRESULT;
}
if (result->result == CELL_SAVEDATA_CBRESULT_OK_LAST || result->result == CELL_SAVEDATA_CBRESULT_OK_LAST_NOCONFIRM)
{
// TODO: display user prompt
break;
}
// TODO: Show progress if it's not an auto load/save
std::string file_path;
switch (const u32 type = fileSet->fileType)
{
case CELL_SAVEDATA_FILETYPE_SECUREFILE:
case CELL_SAVEDATA_FILETYPE_NORMALFILE:
{
if (!fileSet->fileName)
{
// ****** sysutil savedata parameter error : 69 ******
return {CELL_SAVEDATA_ERROR_PARAM, "69"};
}
file_path = fileSet->fileName.get_ptr();
if (type == CELL_SAVEDATA_FILETYPE_SECUREFILE)
{
cellSaveData.notice("SECUREFILE: %s -> %s", file_path, fileSet->secureFileId);
}
break;
}
case CELL_SAVEDATA_FILETYPE_CONTENT_ICON0:
{
file_path = "ICON0.PNG";
break;
}
case CELL_SAVEDATA_FILETYPE_CONTENT_ICON1:
{
file_path = "ICON1.PAM";
break;
}
case CELL_SAVEDATA_FILETYPE_CONTENT_PIC1:
{
file_path = "PIC1.PNG";
break;
}
case CELL_SAVEDATA_FILETYPE_CONTENT_SND0:
{
file_path = "SND0.AT3";
break;
}
default:
{
// ****** sysutil savedata parameter error : 61 ******
cellSaveData.error("savedata_op(): unknown fileSet->fileType (0x%x)", type);
return {CELL_SAVEDATA_ERROR_PARAM, "61"};
}
}
psf.emplace("*" + file_path, fileSet->fileType == CELL_SAVEDATA_FILETYPE_SECUREFILE);
const u32 access_size = std::min<u32>(fileSet->fileSize, fileSet->fileBufSize);
// clang-format off
auto add_to_blist = [&](const std::string& to_add)
{
if (std::find(blist.begin(), blist.end(), to_add) == blist.end())
{
if(auto it = std::find(blist.begin(), blist.end(), ""); it != blist.end())
*it = to_add;
else
blist.push_back(to_add);
}
};
auto del_from_blist = [&](const std::string& to_del)
{
if (auto it = std::find(blist.begin(), blist.end(), to_del); it != blist.end())
*it = "";
};
// clang-format on
switch (const u32 op = fileSet->fileOperation)
{
case CELL_SAVEDATA_FILEOP_READ:
{
fs::file& file = all_files[file_path];
if (!file)
{
// ****** sysutil savedata parameter error : 22 ******
cellSaveData.error("Failed to open file %s%s", dir_path, file_path);
return {CELL_SAVEDATA_ERROR_PARAM, "22"};
}
if (fileSet->fileBufSize < fileSet->fileSize)
{
// ****** sysutil savedata parameter error : 72 ******
return {CELL_SAVEDATA_ERROR_PARAM, "72"};
}
if (!fileSet->fileBuf)
{
// ****** sysutil savedata parameter error : 73 ******
return {CELL_SAVEDATA_ERROR_PARAM, "73"};
}
// Read from memory file to vm
const u64 sr = file.seek(fileSet->fileOffset);
const u64 rr = file.read(fileSet->fileBuf.get_ptr(), access_size);
fileGet->excSize = ::narrow<u32>(rr);
break;
}
case CELL_SAVEDATA_FILEOP_WRITE:
{
fs::file& file = all_files[file_path];
if (!file)
{
file = fs::make_stream<std::vector<uchar>>();
}
// Write to memory file and truncate
const u64 sr = file.seek(fileSet->fileOffset);
const u64 wr = file.write(fileSet->fileBuf.get_ptr(), access_size);
file.trunc(sr + wr);
fileGet->excSize = ::narrow<u32>(wr);
all_times.erase(file_path);
add_to_blist(file_path);
has_modified = true;
break;
}
case CELL_SAVEDATA_FILEOP_DELETE:
{
// Delete memory file
all_files[file_path].close();
psf.erase("*" + file_path);
fileGet->excSize = 0;
all_times.erase(file_path);
del_from_blist(file_path);
has_modified = true;
break;
}
case CELL_SAVEDATA_FILEOP_WRITE_NOTRUNC:
{
fs::file& file = all_files[file_path];
if (!file)
{
file = fs::make_stream<std::vector<uchar>>();
}
// Write to memory file normally
const u64 sr = file.seek(fileSet->fileOffset);
const u64 wr = file.write(fileSet->fileBuf.get_ptr(), access_size);
fileGet->excSize = ::narrow<u32>(wr);
all_times.erase(file_path);
add_to_blist(file_path);
has_modified = true;
break;
}
default:
{
// ****** sysutil savedata parameter error : 60 ******
cellSaveData.error("savedata_op(): unknown fileSet->fileOperation (0x%x)", op);
return {CELL_SAVEDATA_ERROR_PARAM, "60"};
}
}
}
// Write PARAM.SFO and savedata
if (!psf.empty() && has_modified)
{
// First, create temporary directory
if (fs::create_dir(new_path) || fs::g_tls_error == fs::error::exist)
{
fs::remove_all(new_path, false);
}
else
{
fmt::throw_exception("Failed to create directory %s (%s)", new_path, fs::g_tls_error);
}
// add file list per FS order to PARAM.SFO
std::string final_blist;
final_blist = fmt::merge(blist, "/");
psf::assign(psf, "RPCS3_BLIST", psf::string(::align(::size32(final_blist) + 1, 4), final_blist));
// Write all files in temporary directory
auto& fsfo = all_files["PARAM.SFO"];
fsfo = fs::make_stream<std::vector<uchar>>();
psf::save_object(fsfo, psf);
for (auto&& pair : all_files)
{
if (auto file = pair.second.release())
{
auto fvec = static_cast<fs::container_stream<std::vector<uchar>>&>(*file);
fs::file(new_path + pair.first, fs::rewrite).write(fvec.obj);
}
}
for (auto&& pair : all_times)
{
// Restore atime/mtime for files which have not been modified
fs::utime(new_path + pair.first, pair.second.first, pair.second.second);
}
// Remove old backup
fs::remove_all(old_path);
// Backup old savedata
if (!vfs::host::rename(dir_path, old_path, false))
{
fmt::throw_exception("Failed to move directory %s (%s)", dir_path, fs::g_tls_error);
}
// Commit new savedata
if (!vfs::host::rename(new_path, dir_path, false))
{
// TODO: handle the case when only commit failed at the next save load
fmt::throw_exception("Failed to move directory %s (%s)", new_path, fs::g_tls_error);
}
// Remove backup again (TODO: may be changed to persistent backup implementation)
fs::remove_all(old_path);
}
return CELL_OK;
}
static NEVER_INLINE error_code savedata_get_list_item(vm::cptr<char> dirName, vm::ptr<CellSaveDataDirStat> dir, vm::ptr<CellSaveDataSystemFileParam> sysFileParam, vm::ptr<u32> bind, vm::ptr<u32> sizeKB, u32 userId)
{
if (userId == 0)
{
userId = Emu.GetUsrId();
}
std::string save_path = vfs::get(fmt::format("/dev_hdd0/home/%08u/savedata/%s/", userId, dirName.get_ptr()));
std::string sfo = save_path + "PARAM.SFO";
if (!fs::is_dir(save_path) && !fs::is_file(sfo))
{
cellSaveData.error("cellSaveDataGetListItem(): Savedata at %s does not exist", dirName);
return CELL_SAVEDATA_ERROR_NODATA;
}
auto psf = psf::load_object(fs::file(sfo));
if (sysFileParam)
{
strcpy_trunc(sysFileParam->listParam, psf.at("SAVEDATA_LIST_PARAM").as_string());
strcpy_trunc(sysFileParam->title, psf.at("TITLE").as_string());
strcpy_trunc(sysFileParam->subTitle, psf.at("SUB_TITLE").as_string());
strcpy_trunc(sysFileParam->detail, psf.at("DETAIL").as_string());
}
if (dir)
{
fs::stat_t dir_info{};
if (!fs::stat(save_path, dir_info))
{
return CELL_SAVEDATA_ERROR_INTERNAL;
}
// get file stats, namely directory
strcpy_trunc(dir->dirName, dirName.get_ptr());
dir->atime = dir_info.atime;
dir->ctime = dir_info.ctime;
dir->mtime = dir_info.mtime;
}
if (sizeKB)
{
u32 size_kbytes = 0;
for (const auto& entry : fs::dir(save_path))
{
size_kbytes += (entry.size + 1023) / 1024; // firmware rounds this value up
}
*sizeKB = size_kbytes;
}
if (bind)
{
//TODO: Set bind in accordance to any problems
*bind = 0;
}
return CELL_OK;
}
// Functions
error_code cellSaveDataListSave2(ppu_thread& ppu, u32 version, PSetList setList, PSetBuf setBuf, PFuncList funcList,
PFuncStat funcStat, PFuncFile funcFile, u32 container, vm::ptr<void> userdata)
{
cellSaveData.warning("cellSaveDataListSave2(version=%d, setList=*0x%x, setBuf=*0x%x, funcList=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x, userdata=*0x%x)",
version, setList, setBuf, funcList, funcStat, funcFile, container, userdata);
return savedata_op(ppu, SAVEDATA_OP_LIST_SAVE, version, vm::null, 1, setList, setBuf, funcList, vm::null, funcStat, funcFile, container, 2, userdata, 0, vm::null);
}
error_code cellSaveDataListLoad2(ppu_thread& ppu, u32 version, PSetList setList, PSetBuf setBuf, PFuncList funcList,
PFuncStat funcStat, PFuncFile funcFile, u32 container, vm::ptr<void> userdata)
{
cellSaveData.warning("cellSaveDataListLoad2(version=%d, setList=*0x%x, setBuf=*0x%x, funcList=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x, userdata=*0x%x)",
version, setList, setBuf, funcList, funcStat, funcFile, container, userdata);
return savedata_op(ppu, SAVEDATA_OP_LIST_LOAD, version, vm::null, 1, setList, setBuf, funcList, vm::null, funcStat, funcFile, container, 2, userdata, 0, vm::null);
}
error_code cellSaveDataListSave(ppu_thread& ppu, u32 version, PSetList setList, PSetBuf setBuf, PFuncList funcList,
PFuncStat funcStat, PFuncFile funcFile, u32 container)
{
cellSaveData.warning("cellSaveDataListSave(version=%d, setList=*0x%x, setBuf=*0x%x, funcList=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x)",
version, setList, setBuf, funcList, funcStat, funcFile, container);
return savedata_op(ppu, SAVEDATA_OP_LIST_SAVE, version, vm::null, 1, setList, setBuf, funcList, vm::null, funcStat, funcFile, container, 2, vm::null, 0, vm::null);
}
error_code cellSaveDataListLoad(ppu_thread& ppu, u32 version, PSetList setList, PSetBuf setBuf, PFuncList funcList,
PFuncStat funcStat, PFuncFile funcFile, u32 container)
{
cellSaveData.warning("cellSaveDataListLoad(version=%d, setList=*0x%x, setBuf=*0x%x, funcList=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x)",
version, setList, setBuf, funcList, funcStat, funcFile, container);
return savedata_op(ppu, SAVEDATA_OP_LIST_LOAD, version, vm::null, 1, setList, setBuf, funcList, vm::null, funcStat, funcFile, container, 2, vm::null, 0, vm::null);
}
error_code cellSaveDataFixedSave2(ppu_thread& ppu, u32 version, PSetList setList, PSetBuf setBuf, PFuncFixed funcFixed,
PFuncStat funcStat, PFuncFile funcFile, u32 container, vm::ptr<void> userdata)
{
cellSaveData.warning("cellSaveDataFixedSave2(version=%d, setList=*0x%x, setBuf=*0x%x, funcFixed=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x, userdata=*0x%x)",
version, setList, setBuf, funcFixed, funcStat, funcFile, container, userdata);
return savedata_op(ppu, SAVEDATA_OP_FIXED_SAVE, version, vm::null, 1, setList, setBuf, vm::null, funcFixed, funcStat, funcFile, container, 2, userdata, 0, vm::null);
}
error_code cellSaveDataFixedLoad2(ppu_thread& ppu, u32 version, PSetList setList, PSetBuf setBuf, PFuncFixed funcFixed,
PFuncStat funcStat, PFuncFile funcFile, u32 container, vm::ptr<void> userdata)
{
cellSaveData.warning("cellSaveDataFixedLoad2(version=%d, setList=*0x%x, setBuf=*0x%x, funcFixed=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x, userdata=*0x%x)",
version, setList, setBuf, funcFixed, funcStat, funcFile, container, userdata);
return savedata_op(ppu, SAVEDATA_OP_FIXED_LOAD, version, vm::null, 1, setList, setBuf, vm::null, funcFixed, funcStat, funcFile, container, 2, userdata, 0, vm::null);
}
error_code cellSaveDataFixedSave(ppu_thread& ppu, u32 version, PSetList setList, PSetBuf setBuf, PFuncFixed funcFixed,
PFuncStat funcStat, PFuncFile funcFile, u32 container)
{
cellSaveData.warning("cellSaveDataFixedSave(version=%d, setList=*0x%x, setBuf=*0x%x, funcFixed=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x)",
version, setList, setBuf, funcFixed, funcStat, funcFile, container);
return savedata_op(ppu, SAVEDATA_OP_FIXED_SAVE, version, vm::null, 1, setList, setBuf, vm::null, funcFixed, funcStat, funcFile, container, 2, vm::null, 0, vm::null);
}
error_code cellSaveDataFixedLoad(ppu_thread& ppu, u32 version, PSetList setList, PSetBuf setBuf, PFuncFixed funcFixed,
PFuncStat funcStat, PFuncFile funcFile, u32 container)
{
cellSaveData.warning("cellSaveDataFixedLoad(version=%d, setList=*0x%x, setBuf=*0x%x, funcFixed=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x)",
version, setList, setBuf, funcFixed, funcStat, funcFile, container);
return savedata_op(ppu, SAVEDATA_OP_FIXED_LOAD, version, vm::null, 1, setList, setBuf, vm::null, funcFixed, funcStat, funcFile, container, 2, vm::null, 0, vm::null);
}
error_code cellSaveDataAutoSave2(ppu_thread& ppu, u32 version, vm::cptr<char> dirName, u32 errDialog, PSetBuf setBuf,
PFuncStat funcStat, PFuncFile funcFile, u32 container, vm::ptr<void> userdata)
{
cellSaveData.warning("cellSaveDataAutoSave2(version=%d, dirName=%s, errDialog=%d, setBuf=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x, userdata=*0x%x)",
version, dirName, errDialog, setBuf, funcStat, funcFile, container, userdata);
return savedata_op(ppu, SAVEDATA_OP_AUTO_SAVE, version, dirName, errDialog, vm::null, setBuf, vm::null, vm::null, funcStat, funcFile, container, 2, userdata, 0, vm::null);
}
error_code cellSaveDataAutoLoad2(ppu_thread& ppu, u32 version, vm::cptr<char> dirName, u32 errDialog, PSetBuf setBuf,
PFuncStat funcStat, PFuncFile funcFile, u32 container, vm::ptr<void> userdata)
{
cellSaveData.warning("cellSaveDataAutoLoad2(version=%d, dirName=%s, errDialog=%d, setBuf=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x, userdata=*0x%x)",
version, dirName, errDialog, setBuf, funcStat, funcFile, container, userdata);
return savedata_op(ppu, SAVEDATA_OP_AUTO_LOAD, version, dirName, errDialog, vm::null, setBuf, vm::null, vm::null, funcStat, funcFile, container, 2, userdata, 0, vm::null);
}
error_code cellSaveDataAutoSave(ppu_thread& ppu, u32 version, vm::cptr<char> dirName, u32 errDialog, PSetBuf setBuf,
PFuncStat funcStat, PFuncFile funcFile, u32 container)
{
cellSaveData.warning("cellSaveDataAutoSave(version=%d, dirName=%s, errDialog=%d, setBuf=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x)",
version, dirName, errDialog, setBuf, funcStat, funcFile, container);
return savedata_op(ppu, SAVEDATA_OP_AUTO_SAVE, version, dirName, errDialog, vm::null, setBuf, vm::null, vm::null, funcStat, funcFile, container, 2, vm::null, 0, vm::null);
}
error_code cellSaveDataAutoLoad(ppu_thread& ppu, u32 version, vm::cptr<char> dirName, u32 errDialog, PSetBuf setBuf,
PFuncStat funcStat, PFuncFile funcFile, u32 container)
{
cellSaveData.warning("cellSaveDataAutoLoad(version=%d, dirName=%s, errDialog=%d, setBuf=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x)",
version, dirName, errDialog, setBuf, funcStat, funcFile, container);
return savedata_op(ppu, SAVEDATA_OP_AUTO_LOAD, version, dirName, errDialog, vm::null, setBuf, vm::null, vm::null, funcStat, funcFile, container, 2, vm::null, 0, vm::null);
}
error_code cellSaveDataListAutoSave(ppu_thread& ppu, u32 version, u32 errDialog, PSetList setList, PSetBuf setBuf, PFuncFixed funcFixed, PFuncStat funcStat, PFuncFile funcFile, u32 container, vm::ptr<void> userdata)
{
cellSaveData.warning("cellSaveDataListAutoSave(version=%d, errDialog=%d, setList=*0x%x, setBuf=*0x%x, funcFixed=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x, userdata=*0x%x)",
version, errDialog, setList, setBuf, funcFixed, funcStat, funcFile, container, userdata);
return savedata_op(ppu, SAVEDATA_OP_LIST_AUTO_SAVE, version, vm::null, errDialog, setList, setBuf, vm::null, funcFixed, funcStat, funcFile, container, 2, userdata, 0, vm::null);
}
error_code cellSaveDataListAutoLoad(ppu_thread& ppu, u32 version, u32 errDialog, PSetList setList, PSetBuf setBuf, PFuncFixed funcFixed, PFuncStat funcStat, PFuncFile funcFile, u32 container, vm::ptr<void> userdata)
{
cellSaveData.warning("cellSaveDataListAutoLoad(version=%d, errDialog=%d, setList=*0x%x, setBuf=*0x%x, funcFixed=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x, userdata=*0x%x)",
version, errDialog, setList, setBuf, funcFixed, funcStat, funcFile, container, userdata);
return savedata_op(ppu, SAVEDATA_OP_LIST_AUTO_LOAD, version, vm::null, errDialog, setList, setBuf, vm::null, funcFixed, funcStat, funcFile, container, 2, userdata, 0, vm::null);
}
error_code cellSaveDataDelete(ppu_thread& ppu, u32 container)
{
cellSaveData.warning("cellSaveDataDelete(container=0x%x)", container);
return select_and_delete(ppu);
}
error_code cellSaveDataDelete2(ppu_thread& ppu, u32 container)
{
cellSaveData.warning("cellSaveDataDelete2(container=0x%x)", container);
return select_and_delete(ppu);
}
error_code cellSaveDataFixedDelete(ppu_thread& ppu, PSetList setList, PSetBuf setBuf, PFuncFixed funcFixed, PFuncDone funcDone, u32 container, vm::ptr<void> userdata)
{
cellSaveData.warning("cellSaveDataFixedDelete(setList=*0x%x, setBuf=*0x%x, funcFixed=*0x%x, funcDone=*0x%x, container=0x%x, userdata=*0x%x)",
setList, setBuf, funcFixed, funcDone, container, userdata);
return savedata_op(ppu, SAVEDATA_OP_FIXED_DELETE, 0, vm::null, 1, setList, setBuf, vm::null, funcFixed, vm::null, vm::null, container, 2, userdata, 0, funcDone);
}
error_code cellSaveDataUserListSave(ppu_thread& ppu, u32 version, u32 userId, PSetList setList, PSetBuf setBuf, PFuncList funcList, PFuncStat funcStat, PFuncFile funcFile, u32 container, vm::ptr<void> userdata)
{
cellSaveData.error("cellSaveDataUserListSave(version=%d, userId=%d, setList=*0x%x, setBuf=*0x%x, funcList=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x, userdata=*0x%x)",
version, userId, setList, setBuf, funcList, funcStat, funcFile, container, userdata);
return savedata_op(ppu, SAVEDATA_OP_LIST_SAVE, version, vm::null, 0, setList, setBuf, funcList, vm::null, funcStat, funcFile, container, 6, userdata, userId, vm::null);
}
error_code cellSaveDataUserListLoad(ppu_thread& ppu, u32 version, u32 userId, PSetList setList, PSetBuf setBuf, PFuncList funcList, PFuncStat funcStat, PFuncFile funcFile, u32 container, vm::ptr<void> userdata)
{
cellSaveData.error("cellSaveDataUserListLoad(version=%d, userId=%d, setList=*0x%x, setBuf=*0x%x, funcList=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x, userdata=*0x%x)",
version, userId, setList, setBuf, funcList, funcStat, funcFile, container, userdata);
return savedata_op(ppu, SAVEDATA_OP_LIST_LOAD, version, vm::null, 0, setList, setBuf, funcList, vm::null, funcStat, funcFile, container, 6, userdata, userId, vm::null);
}
error_code cellSaveDataUserFixedSave(ppu_thread& ppu, u32 version, u32 userId, PSetList setList, PSetBuf setBuf, PFuncFixed funcFixed, PFuncStat funcStat, PFuncFile funcFile, u32 container, vm::ptr<void> userdata)
{
cellSaveData.error("cellSaveDataUserFixedSave(version=%d, userId=%d, setList=*0x%x, setBuf=*0x%x, funcFixed=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x, userdata=*0x%x)",
version, userId, setList, setBuf, funcFixed, funcStat, funcFile, container, userdata);
return savedata_op(ppu, SAVEDATA_OP_FIXED_SAVE, version, vm::null, 0, setList, setBuf, vm::null, funcFixed, funcStat, funcFile, container, 6, userdata, userId, vm::null);
}
error_code cellSaveDataUserFixedLoad(ppu_thread& ppu, u32 version, u32 userId, PSetList setList, PSetBuf setBuf, PFuncFixed funcFixed, PFuncStat funcStat, PFuncFile funcFile, u32 container, vm::ptr<void> userdata)
{
cellSaveData.error("cellSaveDataUserFixedLoad(version=%d, userId=%d, setList=*0x%x, setBuf=*0x%x, funcFixed=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x, userdata=*0x%x)",
version, userId, setList, setBuf, funcFixed, funcStat, funcFile, container, userdata);
return savedata_op(ppu, SAVEDATA_OP_FIXED_LOAD, version, vm::null, 0, setList, setBuf, vm::null, funcFixed, funcStat, funcFile, container, 6, userdata, userId, vm::null);
}
error_code cellSaveDataUserAutoSave(ppu_thread& ppu, u32 version, u32 userId, vm::cptr<char> dirName, u32 errDialog, PSetBuf setBuf, PFuncStat funcStat, PFuncFile funcFile, u32 container, vm::ptr<void> userdata)
{
cellSaveData.error("cellSaveDataUserAutoSave(version=%d, userId=%d, dirName=%s, errDialog=%d, setBuf=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x, userdata=*0x%x)",
version, userId, dirName, errDialog, setBuf, funcStat, funcFile, container, userdata);
return savedata_op(ppu, SAVEDATA_OP_AUTO_SAVE, version, dirName, errDialog, vm::null, setBuf, vm::null, vm::null, funcStat, funcFile, container, 6, userdata, userId, vm::null);
}
error_code cellSaveDataUserAutoLoad(ppu_thread& ppu, u32 version, u32 userId, vm::cptr<char> dirName, u32 errDialog, PSetBuf setBuf, PFuncStat funcStat, PFuncFile funcFile, u32 container, vm::ptr<void> userdata)
{
cellSaveData.error("cellSaveDataUserAutoLoad(version=%d, userId=%d, dirName=%s, errDialog=%d, setBuf=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x, userdata=*0x%x)",
version, userId, dirName, errDialog, setBuf, funcStat, funcFile, container, userdata);
return savedata_op(ppu, SAVEDATA_OP_AUTO_LOAD, version, dirName, errDialog, vm::null, setBuf, vm::null, vm::null, funcStat, funcFile, container, 6, userdata, userId, vm::null);
}
error_code cellSaveDataUserListAutoSave(ppu_thread& ppu, u32 version, u32 userId, u32 errDialog, PSetList setList, PSetBuf setBuf, PFuncFixed funcFixed, PFuncStat funcStat, PFuncFile funcFile, u32 container, vm::ptr<void> userdata)
{
cellSaveData.error("cellSaveDataUserListAutoSave(version=%d, userId=%d, errDialog=%d, setList=*0x%x, setBuf=*0x%x, funcFixed=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x, userdata=*0x%x)",
version, userId, errDialog, setList, setBuf, funcFixed, funcStat, funcFile, container, userdata);
return savedata_op(ppu, SAVEDATA_OP_LIST_AUTO_SAVE, version, vm::null, errDialog, setList, setBuf, vm::null, funcFixed, funcStat, funcFile, container, 6, userdata, userId, vm::null);
}
error_code cellSaveDataUserListAutoLoad(ppu_thread& ppu, u32 version, u32 userId, u32 errDialog, PSetList setList, PSetBuf setBuf, PFuncFixed funcFixed, PFuncStat funcStat, PFuncFile funcFile, u32 container, vm::ptr<void> userdata)
{
cellSaveData.error("cellSaveDataUserListAutoLoad(version=%d, userId=%d, errDialog=%d, setList=*0x%x, setBuf=*0x%x, funcFixed=*0x%x, funcStat=*0x%x, funcFile=*0x%x, container=0x%x, userdata=*0x%x)",
version, userId, errDialog, setList, setBuf, funcFixed, funcStat, funcFile, container, userdata);
return savedata_op(ppu, SAVEDATA_OP_LIST_AUTO_LOAD, version, vm::null, errDialog, setList, setBuf, vm::null, funcFixed, funcStat, funcFile, container, 6, userdata, userId, vm::null);
}
error_code cellSaveDataUserFixedDelete(ppu_thread& ppu, u32 userId, PSetList setList, PSetBuf setBuf, PFuncFixed funcFixed, PFuncDone funcDone, u32 container, vm::ptr<void> userdata)
{
cellSaveData.error("cellSaveDataUserFixedDelete(userId=%d, setList=*0x%x, setBuf=*0x%x, funcFixed=*0x%x, funcDone=*0x%x, container=0x%x, userdata=*0x%x)",
userId, setList, setBuf, funcFixed, funcDone, container, userdata);
return savedata_op(ppu, SAVEDATA_OP_FIXED_DELETE, 0, vm::null, 1, setList, setBuf, vm::null, funcFixed, vm::null, vm::null, container, 6, userdata, userId, funcDone);
}
void cellSaveDataEnableOverlay(s32 enable)
{
cellSaveData.error("cellSaveDataEnableOverlay(enable=%d)", enable);
}
// Functions (Extensions)
error_code cellSaveDataListDelete(ppu_thread& ppu, PSetList setList, PSetBuf setBuf, PFuncList funcList, PFuncDone funcDone, u32 container, vm::ptr<void> userdata)
{
cellSaveData.warning("cellSaveDataListDelete(setList=*0x%x, setBuf=*0x%x, funcList=*0x%x, funcDone=*0x%x, container=0x%x, userdata=*0x%x)", setList, setBuf, funcList, funcDone, container, userdata);
return savedata_op(ppu, SAVEDATA_OP_LIST_DELETE, 0, vm::null, 0, setList, setBuf, funcList, vm::null, vm::null, vm::null, container, 0x40, userdata, 0, funcDone);
}
error_code cellSaveDataListImport(ppu_thread& ppu, PSetList setList, u32 maxSizeKB, PFuncDone funcDone, u32 container, vm::ptr<void> userdata)
{
UNIMPLEMENTED_FUNC(cellSaveData);
return CELL_OK;
}
error_code cellSaveDataListExport(ppu_thread& ppu, PSetList setList, u32 maxSizeKB, PFuncDone funcDone, u32 container, vm::ptr<void> userdata)
{
UNIMPLEMENTED_FUNC(cellSaveData);
return CELL_OK;
}
error_code cellSaveDataFixedImport(ppu_thread& ppu, vm::cptr<char> dirName, u32 maxSizeKB, PFuncDone funcDone, u32 container, vm::ptr<void> userdata)
{
UNIMPLEMENTED_FUNC(cellSaveData);
return CELL_OK;
}
error_code cellSaveDataFixedExport(ppu_thread& ppu, vm::cptr<char> dirName, u32 maxSizeKB, PFuncDone funcDone, u32 container, vm::ptr<void> userdata)
{
UNIMPLEMENTED_FUNC(cellSaveData);
return CELL_OK;
}
error_code cellSaveDataGetListItem(vm::cptr<char> dirName, vm::ptr<CellSaveDataDirStat> dir, vm::ptr<CellSaveDataSystemFileParam> sysFileParam, vm::ptr<u32> bind, vm::ptr<u32> sizeKB)
{
cellSaveData.warning("cellSaveDataGetListItem(dirName=%s, dir=*0x%x, sysFileParam=*0x%x, bind=*0x%x, sizeKB=*0x%x)", dirName, dir, sysFileParam, bind, sizeKB);
return savedata_get_list_item(dirName, dir, sysFileParam, bind, sizeKB, 0);
}
error_code cellSaveDataUserListDelete(ppu_thread& ppu, u32 userId, PSetList setList, PSetBuf setBuf, PFuncList funcList, PFuncDone funcDone, u32 container, vm::ptr<void> userdata)
{
cellSaveData.error("cellSaveDataUserListDelete(userId=%d, setList=*0x%x, setBuf=*0x%x, funcList=*0x%x, funcDone=*0x%x, container=0x%x, userdata=*0x%x)", userId, setList, setBuf, funcList, funcDone, container, userdata);
return savedata_op(ppu, SAVEDATA_OP_LIST_DELETE, 0, vm::null, 0, setList, setBuf, funcList, vm::null, vm::null, vm::null, container, 0x40, userdata, userId, funcDone);
}
error_code cellSaveDataUserListImport(ppu_thread& ppu, u32 userId, PSetList setList, u32 maxSizeKB, PFuncDone funcDone, u32 container, vm::ptr<void> userdata)
{
UNIMPLEMENTED_FUNC(cellSaveData);
return CELL_OK;
}
error_code cellSaveDataUserListExport(ppu_thread& ppu, u32 userId, PSetList setList, u32 maxSizeKB, PFuncDone funcDone, u32 container, vm::ptr<void> userdata)
{
UNIMPLEMENTED_FUNC(cellSaveData);
return CELL_OK;
}
error_code cellSaveDataUserFixedImport(ppu_thread& ppu, u32 userId, vm::cptr<char> dirName, u32 maxSizeKB, PFuncDone funcDone, u32 container, vm::ptr<void> userdata)
{
UNIMPLEMENTED_FUNC(cellSaveData);
return CELL_OK;
}
error_code cellSaveDataUserFixedExport(ppu_thread& ppu, u32 userId, vm::cptr<char> dirName, u32 maxSizeKB, PFuncDone funcDone, u32 container, vm::ptr<void> userdata)
{
UNIMPLEMENTED_FUNC(cellSaveData);
return CELL_OK;
}
error_code cellSaveDataUserGetListItem(u32 userId, vm::cptr<char> dirName, vm::ptr<CellSaveDataDirStat> dir, vm::ptr<CellSaveDataSystemFileParam> sysFileParam, vm::ptr<u32> bind, vm::ptr<u32> sizeKB)
{
cellSaveData.warning("cellSaveDataUserGetListItem(dirName=%s, dir=*0x%x, sysFileParam=*0x%x, bind=*0x%x, sizeKB=*0x%x, userID=*0x%x)", dirName, dir, sysFileParam, bind, sizeKB, userId);
return savedata_get_list_item(dirName, dir, sysFileParam, bind, sizeKB, userId);
}
void cellSysutil_SaveData_init()
{
REG_VAR(cellSysutil, g_savedata_context).flag(MFF_HIDDEN);
// libsysutil functions:
REG_FUNC(cellSysutil, cellSaveDataEnableOverlay);
REG_FUNC(cellSysutil, cellSaveDataDelete2);
REG_FUNC(cellSysutil, cellSaveDataDelete);
REG_FUNC(cellSysutil, cellSaveDataUserFixedDelete);
REG_FUNC(cellSysutil, cellSaveDataFixedDelete);
REG_FUNC(cellSysutil, cellSaveDataUserFixedLoad);
REG_FUNC(cellSysutil, cellSaveDataUserFixedSave);
REG_FUNC(cellSysutil, cellSaveDataFixedLoad2);
REG_FUNC(cellSysutil, cellSaveDataFixedSave2);
REG_FUNC(cellSysutil, cellSaveDataFixedLoad);
REG_FUNC(cellSysutil, cellSaveDataFixedSave);
REG_FUNC(cellSysutil, cellSaveDataUserListLoad);
REG_FUNC(cellSysutil, cellSaveDataUserListSave);
REG_FUNC(cellSysutil, cellSaveDataListLoad2);
REG_FUNC(cellSysutil, cellSaveDataListSave2);
REG_FUNC(cellSysutil, cellSaveDataListLoad);
REG_FUNC(cellSysutil, cellSaveDataListSave);
REG_FUNC(cellSysutil, cellSaveDataUserListAutoLoad);
REG_FUNC(cellSysutil, cellSaveDataUserListAutoSave);
REG_FUNC(cellSysutil, cellSaveDataListAutoLoad);
REG_FUNC(cellSysutil, cellSaveDataListAutoSave);
REG_FUNC(cellSysutil, cellSaveDataUserAutoLoad);
REG_FUNC(cellSysutil, cellSaveDataUserAutoSave);
REG_FUNC(cellSysutil, cellSaveDataAutoLoad2);
REG_FUNC(cellSysutil, cellSaveDataAutoSave2);
REG_FUNC(cellSysutil, cellSaveDataAutoLoad);
REG_FUNC(cellSysutil, cellSaveDataAutoSave);
}
DECLARE(ppu_module_manager::cellSaveData)("cellSaveData", []()
{
// libsysutil_savedata functions:
REG_FUNC(cellSaveData, cellSaveDataUserGetListItem);
REG_FUNC(cellSaveData, cellSaveDataGetListItem);
REG_FUNC(cellSaveData, cellSaveDataUserListDelete);
REG_FUNC(cellSaveData, cellSaveDataListDelete);
REG_FUNC(cellSaveData, cellSaveDataUserFixedExport);
REG_FUNC(cellSaveData, cellSaveDataUserFixedImport);
REG_FUNC(cellSaveData, cellSaveDataUserListExport);
REG_FUNC(cellSaveData, cellSaveDataUserListImport);
REG_FUNC(cellSaveData, cellSaveDataFixedExport);
REG_FUNC(cellSaveData, cellSaveDataFixedImport);
REG_FUNC(cellSaveData, cellSaveDataListExport);
REG_FUNC(cellSaveData, cellSaveDataListImport);
});
DECLARE(ppu_module_manager::cellMinisSaveData)("cellMinisSaveData", []()
{
// libsysutil_savedata_psp functions:
//REG_FUNC(cellMinisSaveData, cellMinisSaveDataDelete); // 0x6eb168b3
//REG_FUNC(cellMinisSaveData, cellMinisSaveDataListDelete); // 0xe63eb964
//REG_FUNC(cellMinisSaveData, cellMinisSaveDataFixedLoad); // 0x66515c18
//REG_FUNC(cellMinisSaveData, cellMinisSaveDataFixedSave); // 0xf3f974b8
//REG_FUNC(cellMinisSaveData, cellMinisSaveDataListLoad); // 0xba161d45
//REG_FUNC(cellMinisSaveData, cellMinisSaveDataListSave); // 0xa342a73f
//REG_FUNC(cellMinisSaveData, cellMinisSaveDataListAutoLoad); // 0x22f2a553
//REG_FUNC(cellMinisSaveData, cellMinisSaveDataListAutoSave); // 0xa931356e
//REG_FUNC(cellMinisSaveData, cellMinisSaveDataAutoLoad); // 0xfc3045d9
});