diff --git a/rpcs3/Crypto/unself.cpp b/rpcs3/Crypto/unself.cpp index 7f670136cf..1266dee0fe 100644 --- a/rpcs3/Crypto/unself.cpp +++ b/rpcs3/Crypto/unself.cpp @@ -4,6 +4,7 @@ #include "utils.h" #include "unself.h" #include "Emu/VFS.h" +#include "Emu/System.h" #include #include @@ -1294,7 +1295,7 @@ bool SELFDecrypter::GetKeyFromRap(u8* content_id, u8* npdrm_key) // Try to find a matching RAP file under exdata folder. const std::string ci_str = reinterpret_cast(content_id); - const std::string rap_path = "/dev_hdd0/home/00000001/exdata/" + ci_str + ".rap"; + const std::string rap_path = fmt::format("/dev_hdd0/home/%s/exdata/%s.rap", Emu.GetUsr(), ci_str); // Open the RAP file and read the key. const fs::file rap_file(vfs::get(rap_path)); diff --git a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp index 8f85e2193a..c3621b28b0 100644 --- a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp @@ -85,8 +85,8 @@ static NEVER_INLINE s32 savedata_op(ppu_thread& ppu, u32 operation, u32 version, vm::ptr fileSet = g_savedata_context.ptr(&savedata_context::fileSet); vm::ptr doneGet = g_savedata_context.ptr(&savedata_context::doneGet); - //TODO: get current user ID - // userId(0) = CELL_SYSUTIL_USERID_CURRENT + // TODO: now that we have Emu.GetUsrId(), do we need "userId ? userId : 1u"? Replace with error check? + // 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 : 1u)); @@ -843,10 +843,9 @@ static NEVER_INLINE s32 savedata_op(ppu_thread& ppu, u32 operation, u32 version, static NEVER_INLINE s32 savedata_get_list_item(vm::cptr dirName, vm::ptr dir, vm::ptr sysFileParam, vm::ptr bind, vm::ptr sizeKB, u32 userId) { - //TODO: accurately get the current user if (userId == 0) { - userId = 1u; + 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"; @@ -910,7 +909,7 @@ s32 cellSaveDataListSave2(ppu_thread& ppu, u32 version, PSetList setList, PSetBu 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); + return savedata_op(ppu, SAVEDATA_OP_LIST_SAVE, version, vm::null, 1, setList, setBuf, funcList, vm::null, funcStat, funcFile, container, 2, userdata, Emu.GetUsrId(), vm::null); } s32 cellSaveDataListLoad2(ppu_thread& ppu, u32 version, PSetList setList, PSetBuf setBuf, PFuncList funcList, @@ -919,7 +918,7 @@ s32 cellSaveDataListLoad2(ppu_thread& ppu, u32 version, PSetList setList, PSetBu 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); + return savedata_op(ppu, SAVEDATA_OP_LIST_LOAD, version, vm::null, 1, setList, setBuf, funcList, vm::null, funcStat, funcFile, container, 2, userdata, Emu.GetUsrId(), vm::null); } s32 cellSaveDataListSave(ppu_thread& ppu, u32 version, PSetList setList, PSetBuf setBuf, PFuncList funcList, @@ -928,7 +927,7 @@ s32 cellSaveDataListSave(ppu_thread& ppu, u32 version, PSetList setList, PSetBuf 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); + return savedata_op(ppu, SAVEDATA_OP_LIST_SAVE, version, vm::null, 1, setList, setBuf, funcList, vm::null, funcStat, funcFile, container, 2, vm::null, Emu.GetUsrId(), vm::null); } s32 cellSaveDataListLoad(ppu_thread& ppu, u32 version, PSetList setList, PSetBuf setBuf, PFuncList funcList, @@ -937,7 +936,7 @@ s32 cellSaveDataListLoad(ppu_thread& ppu, u32 version, PSetList setList, PSetBuf 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); + return savedata_op(ppu, SAVEDATA_OP_LIST_LOAD, version, vm::null, 1, setList, setBuf, funcList, vm::null, funcStat, funcFile, container, 2, vm::null, Emu.GetUsrId(), vm::null); } s32 cellSaveDataFixedSave2(ppu_thread& ppu, u32 version, PSetList setList, PSetBuf setBuf, PFuncFixed funcFixed, @@ -946,7 +945,7 @@ s32 cellSaveDataFixedSave2(ppu_thread& ppu, u32 version, PSetList setList, PSetB 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); + return savedata_op(ppu, SAVEDATA_OP_FIXED_SAVE, version, vm::null, 1, setList, setBuf, vm::null, funcFixed, funcStat, funcFile, container, 2, userdata, Emu.GetUsrId(), vm::null); } s32 cellSaveDataFixedLoad2(ppu_thread& ppu, u32 version, PSetList setList, PSetBuf setBuf, PFuncFixed funcFixed, @@ -955,7 +954,7 @@ s32 cellSaveDataFixedLoad2(ppu_thread& ppu, u32 version, PSetList setList, PSetB 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); + return savedata_op(ppu, SAVEDATA_OP_FIXED_LOAD, version, vm::null, 1, setList, setBuf, vm::null, funcFixed, funcStat, funcFile, container, 2, userdata, Emu.GetUsrId(), vm::null); } s32 cellSaveDataFixedSave(ppu_thread& ppu, u32 version, PSetList setList, PSetBuf setBuf, PFuncFixed funcFixed, @@ -964,7 +963,7 @@ s32 cellSaveDataFixedSave(ppu_thread& ppu, u32 version, PSetList setList, PSetBu 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); + return savedata_op(ppu, SAVEDATA_OP_FIXED_SAVE, version, vm::null, 1, setList, setBuf, vm::null, funcFixed, funcStat, funcFile, container, 2, vm::null, Emu.GetUsrId(), vm::null); } s32 cellSaveDataFixedLoad(ppu_thread& ppu, u32 version, PSetList setList, PSetBuf setBuf, PFuncFixed funcFixed, @@ -973,7 +972,7 @@ s32 cellSaveDataFixedLoad(ppu_thread& ppu, u32 version, PSetList setList, PSetBu 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); + return savedata_op(ppu, SAVEDATA_OP_FIXED_LOAD, version, vm::null, 1, setList, setBuf, vm::null, funcFixed, funcStat, funcFile, container, 2, vm::null, Emu.GetUsrId(), vm::null); } s32 cellSaveDataAutoSave2(ppu_thread& ppu, u32 version, vm::cptr dirName, u32 errDialog, PSetBuf setBuf, @@ -982,7 +981,7 @@ s32 cellSaveDataAutoSave2(ppu_thread& ppu, u32 version, vm::cptr dirName, 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); + return savedata_op(ppu, SAVEDATA_OP_AUTO_SAVE, version, dirName, errDialog, vm::null, setBuf, vm::null, vm::null, funcStat, funcFile, container, 2, userdata, Emu.GetUsrId(), vm::null); } s32 cellSaveDataAutoLoad2(ppu_thread& ppu, u32 version, vm::cptr dirName, u32 errDialog, PSetBuf setBuf, @@ -991,7 +990,7 @@ s32 cellSaveDataAutoLoad2(ppu_thread& ppu, u32 version, vm::cptr dirName, 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); + return savedata_op(ppu, SAVEDATA_OP_AUTO_LOAD, version, dirName, errDialog, vm::null, setBuf, vm::null, vm::null, funcStat, funcFile, container, 2, userdata, Emu.GetUsrId(), vm::null); } s32 cellSaveDataAutoSave(ppu_thread& ppu, u32 version, vm::cptr dirName, u32 errDialog, PSetBuf setBuf, @@ -1000,7 +999,7 @@ s32 cellSaveDataAutoSave(ppu_thread& ppu, u32 version, vm::cptr dirName, u 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); + return savedata_op(ppu, SAVEDATA_OP_AUTO_SAVE, version, dirName, errDialog, vm::null, setBuf, vm::null, vm::null, funcStat, funcFile, container, 2, vm::null, Emu.GetUsrId(), vm::null); } s32 cellSaveDataAutoLoad(ppu_thread& ppu, u32 version, vm::cptr dirName, u32 errDialog, PSetBuf setBuf, @@ -1009,7 +1008,7 @@ s32 cellSaveDataAutoLoad(ppu_thread& ppu, u32 version, vm::cptr dirName, u 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); + return savedata_op(ppu, SAVEDATA_OP_AUTO_LOAD, version, dirName, errDialog, vm::null, setBuf, vm::null, vm::null, funcStat, funcFile, container, 2, vm::null, Emu.GetUsrId(), vm::null); } s32 cellSaveDataListAutoSave(ppu_thread& ppu, u32 version, u32 errDialog, PSetList setList, PSetBuf setBuf, PFuncFixed funcFixed, PFuncStat funcStat, PFuncFile funcFile, u32 container, vm::ptr userdata) @@ -1017,7 +1016,7 @@ s32 cellSaveDataListAutoSave(ppu_thread& ppu, u32 version, u32 errDialog, PSetLi 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, 0, userdata, 0, vm::null); + return savedata_op(ppu, SAVEDATA_OP_LIST_AUTO_SAVE, version, vm::null, errDialog, setList, setBuf, vm::null, funcFixed, funcStat, funcFile, container, 0, userdata, Emu.GetUsrId(), vm::null); } s32 cellSaveDataListAutoLoad(ppu_thread& ppu, u32 version, u32 errDialog, PSetList setList, PSetBuf setBuf, PFuncFixed funcFixed, PFuncStat funcStat, PFuncFile funcFile, u32 container, vm::ptr userdata) @@ -1025,7 +1024,7 @@ s32 cellSaveDataListAutoLoad(ppu_thread& ppu, u32 version, u32 errDialog, PSetLi 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, 0, userdata, 0, vm::null); + return savedata_op(ppu, SAVEDATA_OP_LIST_AUTO_LOAD, version, vm::null, errDialog, setList, setBuf, vm::null, funcFixed, funcStat, funcFile, container, 0, userdata, Emu.GetUsrId(), vm::null); } s32 cellSaveDataDelete2(u32 container) @@ -1047,7 +1046,7 @@ s32 cellSaveDataFixedDelete(ppu_thread& ppu, PSetList setList, PSetBuf setBuf, P 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); + return savedata_op(ppu, SAVEDATA_OP_FIXED_DELETE, 0, vm::null, 1, setList, setBuf, vm::null, funcFixed, vm::null, vm::null, container, 2, userdata, Emu.GetUsrId(), funcDone); } s32 cellSaveDataUserListSave(ppu_thread& ppu, u32 version, u32 userId, PSetList setList, PSetBuf setBuf, PFuncList funcList, PFuncStat funcStat, PFuncFile funcFile, u32 container, vm::ptr userdata) @@ -1133,7 +1132,7 @@ s32 cellSaveDataListDelete(ppu_thread& ppu, PSetList setList, PSetBuf setBuf, PF { 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); + return savedata_op(ppu, SAVEDATA_OP_LIST_DELETE, 0, vm::null, 0, setList, setBuf, funcList, vm::null, vm::null, vm::null, container, 0x40, userdata, Emu.GetUsrId(), funcDone); } s32 cellSaveDataListImport(ppu_thread& ppu, PSetList setList, u32 maxSizeKB, PFuncDone funcDone, u32 container, vm::ptr userdata) @@ -1166,7 +1165,7 @@ s32 cellSaveDataFixedExport(ppu_thread& ppu, vm::cptr dirName, u32 maxSize s32 cellSaveDataGetListItem(vm::cptr dirName, vm::ptr dir, vm::ptr sysFileParam, vm::ptr bind, vm::ptr sizeKB) { - cellSaveData.warning("cellSavaDataGetListItem(dirName=%s, dir=*0x%x, sysFileParam=*0x%x, bind=*0x%x, sizeKB=*0x%x)", dirName, dir, sysFileParam, bind, 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); } @@ -1208,7 +1207,7 @@ s32 cellSaveDataUserFixedExport(ppu_thread& ppu, u32 userId, vm::cptr dirN s32 cellSaveDataUserGetListItem(u32 userId, vm::cptr dirName, vm::ptr dir, vm::ptr sysFileParam, vm::ptr bind, vm::ptr sizeKB) { - cellSaveData.warning("cellSavaDataGetListItem(dirName=%s, dir=*0x%x, sysFileParam=*0x%x, bind=*0x%x, sizeKB=*0x%x, userID=*0x%x)", dirName, dir, sysFileParam, bind, sizeKB, userId); + cellSaveData.warning("cellSaveDataGetListItem(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); } diff --git a/rpcs3/Emu/Cell/Modules/cellUserInfo.cpp b/rpcs3/Emu/Cell/Modules/cellUserInfo.cpp index b39825986d..c1aba93045 100644 --- a/rpcs3/Emu/Cell/Modules/cellUserInfo.cpp +++ b/rpcs3/Emu/Cell/Modules/cellUserInfo.cpp @@ -37,8 +37,8 @@ error_code cellUserInfoGetStat(u32 id, vm::ptr stat) if (id == CELL_SYSUTIL_USERID_CURRENT) { - // TODO: Return current user/profile when that is implemented - id = 1; + // We want the int value, not the string. + id = Emu.GetUsrId(); } if (!stat) @@ -121,8 +121,8 @@ error_code cellUserInfoGetList(vm::ptr listNum, vm::ptr k_licensee_addr, vm::cptr drm_path) npdrmkeys->devKlic.fill(0); npdrmkeys->rifKey.fill(0); - // todo: profile for rap_dir_path - std::string rap_dir_path = "/dev_hdd0/home/00000001/exdata/"; + std::string rap_dir_path = fmt::format("/dev_hdd0/home/%s/exdata/", Emu.GetUsr()); const std::string& enc_drm_path_local = vfs::get(enc_drm_path); const fs::file enc_file(enc_drm_path_local); @@ -145,7 +144,7 @@ s32 sceNpDrmVerifyUpgradeLicense(vm::cptr content_id) return SCE_NP_DRM_ERROR_INVALID_PARAM; } - if (!fs::is_file(vfs::get("/dev_hdd0/home/00000001/exdata/") + content_id.get_ptr() + ".rap")) + if (!fs::is_file(vfs::get(fmt::format("/dev_hdd0/home/%s/exdata/%s.rap", Emu.GetUsr(), content_id.get_ptr())))) { // Game hasn't been purchased therefore no RAP file present return SCE_NP_DRM_ERROR_LICENSE_NOT_FOUND; @@ -164,7 +163,7 @@ s32 sceNpDrmVerifyUpgradeLicense2(vm::cptr content_id) return SCE_NP_DRM_ERROR_INVALID_PARAM; } - if (!fs::is_file(vfs::get("/dev_hdd0/home/00000001/exdata/") + content_id.get_ptr() + ".rap")) + if (!fs::is_file(vfs::get(fmt::format("/dev_hdd0/home/%s/exdata/%s.rap", Emu.GetUsr(), content_id.get_ptr())))) { // Game hasn't been purchased therefore no RAP file present return SCE_NP_DRM_ERROR_LICENSE_NOT_FOUND; diff --git a/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp b/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp index 52be44ca75..2c17eda431 100644 --- a/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp @@ -296,8 +296,7 @@ error_code sceNpTrophyRegisterContext(ppu_thread& ppu, u32 context, u32 handle, } } - // TODO: Get the path of the current user - std::string trophyPath = "/dev_hdd0/home/00000001/trophy/" + ctxt->trp_name; + std::string trophyPath = fmt::format("/dev_hdd0/home/%s/trophy/%s", Emu.GetUsr(), ctxt->trp_name); if (!trp.Install(trophyPath)) { return SCE_NP_TROPHY_ERROR_ILLEGAL_UPDATE; @@ -346,7 +345,7 @@ error_code sceNpTrophyGetRequiredDiskSpace(u32 context, u32 handle, vm::ptr return SCE_NP_TROPHY_ERROR_UNKNOWN_HANDLE; } - if (!fs::is_dir(vfs::get("/dev_hdd0/home/00000001/trophy/" + ctxt->trp_name))) + if (!fs::is_dir(vfs::get(fmt::format("/dev_hdd0/home/%s/trophy/%s", Emu.GetUsr(), ctxt->trp_name)))) { TRPLoader trp(ctxt->trp_stream); @@ -405,8 +404,7 @@ error_code sceNpTrophyGetGameInfo(u32 context, u32 handle, vm::ptrtrp_name + "/TROPCONF.SFM")); + fs::file config(vfs::get(fmt::format("/dev_hdd0/home/%s/trophy/%s/TROPCONF.SFM", Emu.GetUsr(), ctxt->trp_name))); if (!config) { @@ -505,7 +503,7 @@ error_code sceNpTrophyUnlockTrophy(u32 context, u32 handle, s32 trophyId, vm::pt return SCE_NP_TROPHY_ERROR_ALREADY_UNLOCKED; ctxt->tropusr->UnlockTrophy(trophyId, 0, 0); // TODO - std::string trophyPath = "/dev_hdd0/home/00000001/trophy/" + ctxt->trp_name + "/TROPUSR.DAT"; + std::string trophyPath = fmt::format("/dev_hdd0/home/%s/trophy/%s/TROPUSR.DAT", Emu.GetUsr(), ctxt->trp_name); ctxt->tropusr->Save(trophyPath); if (platinumId) @@ -527,7 +525,8 @@ error_code sceNpTrophyUnlockTrophy(u32 context, u32 handle, s32 trophyId, vm::pt } // Get icon for the notification. - std::string trophyIconPath = "/dev_hdd0/home/00000001/trophy/" + ctxt->trp_name + "/TROP" + padding + std::to_string(trophyId) + ".PNG"; + std::string paddedTrophyId = padding + std::to_string(trophyId); + std::string trophyIconPath = fmt::format("/dev_hdd0/home/%s/trophy/%s/TROP%s.PNG", Emu.GetUsr(), ctxt->trp_name, paddedTrophyId); fs::file trophyIconFile = fs::file(vfs::get(trophyIconPath)); size_t iconSize = trophyIconFile.size(); std::vector trophyIconData; @@ -612,8 +611,7 @@ error_code sceNpTrophyGetTrophyInfo(u32 context, u32 handle, s32 trophyId, vm::p return SCE_NP_TROPHY_ERROR_UNKNOWN_HANDLE; } - // TODO: Get the path of the current user - fs::file config(vfs::get("/dev_hdd0/home/00000001/trophy/" + ctxt->trp_name + "/TROPCONF.SFM")); + fs::file config(vfs::get(fmt::format("/dev_hdd0/home/%s/trophy/%s/TROPCONF.SFM", Emu.GetUsr(), ctxt->trp_name))); if (!config) { @@ -756,7 +754,7 @@ error_code sceNpTrophyGetGameIcon(u32 context, u32 handle, vm::ptr buffer, return SCE_NP_TROPHY_ERROR_UNKNOWN_HANDLE; } - fs::file icon_file(vfs::get("/dev_hdd0/home/00000001/trophy/" + ctxt->trp_name + "/ICON0.PNG")); + fs::file icon_file(vfs::get(fmt::format("/dev_hdd0/home/%s/trophy/%s/ICON0.PNG", Emu.GetUsr(), ctxt->trp_name))); if (!icon_file) { @@ -809,7 +807,7 @@ error_code sceNpTrophyGetTrophyIcon(u32 context, u32 handle, s32 trophyId, vm::p return hidden ? SCE_NP_TROPHY_ERROR_HIDDEN : SCE_NP_TROPHY_ERROR_LOCKED; } - fs::file icon_file(vfs::get("/dev_hdd0/home/00000001/trophy/" + ctxt->trp_name + fmt::format("/TROP%03d.PNG", trophyId))); + fs::file icon_file(vfs::get(fmt::format("/dev_hdd0/home/%s/trophy/%s/TROP%03d.PNG", Emu.GetUsr(), ctxt->trp_name, trophyId))); if (!icon_file) { diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index e6874acf64..839215146a 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -278,10 +278,14 @@ void Emulator::Init() // Create directories const std::string emu_dir = GetEmuDir(); - const std::string dev_hdd0 = fmt::replace_all(g_cfg.vfs.dev_hdd0, "$(EmulatorDir)", emu_dir); + const std::string dev_hdd0 = GetHddDir(); const std::string dev_hdd1 = fmt::replace_all(g_cfg.vfs.dev_hdd1, "$(EmulatorDir)", emu_dir); const std::string dev_usb = fmt::replace_all(g_cfg.vfs.dev_usb000, "$(EmulatorDir)", emu_dir); + // Set selected user. + m_usr = g_cfg.usr.selected_usr; + m_usrid = static_cast(std::stoul(m_usr)); + fs::create_path(dev_hdd0); fs::create_path(dev_hdd1); fs::create_path(dev_usb); @@ -290,11 +294,11 @@ void Emulator::Init() fs::create_dir(dev_hdd0 + "game/TEST12345/USRDIR/"); fs::create_dir(dev_hdd0 + "game/.locks/"); fs::create_dir(dev_hdd0 + "home/"); - fs::create_dir(dev_hdd0 + "home/00000001/"); - fs::create_dir(dev_hdd0 + "home/00000001/exdata/"); - fs::create_dir(dev_hdd0 + "home/00000001/savedata/"); - fs::create_dir(dev_hdd0 + "home/00000001/trophy/"); - fs::write_file(dev_hdd0 + "home/00000001/localusername", fs::create + fs::excl + fs::write, "User"s); + fs::create_dir(dev_hdd0 + "home/" + m_usr + "/"); + fs::create_dir(dev_hdd0 + "home/" + m_usr + "/exdata/"); + fs::create_dir(dev_hdd0 + "home/" + m_usr + "/savedata/"); + fs::create_dir(dev_hdd0 + "home/" + m_usr + "/trophy/"); + fs::write_file(dev_hdd0 + "home/" + m_usr + "/localusername", fs::create + fs::excl + fs::write, "User"s); fs::create_dir(dev_hdd0 + "disc/"); fs::create_dir(dev_hdd0 + "savedata/"); fs::create_dir(dev_hdd0 + "savedata/vmc/"); diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index ddadaa6c1c..0fde477358 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -209,6 +209,8 @@ class Emulator final std::string m_title; std::string m_cat; std::string m_dir; + std::string m_usr; + u32 m_usrid; bool m_force_boot = false; @@ -277,6 +279,18 @@ public: return m_dir; } + // String for GUI dialogs. + const std::string& GetUsr() const + { + return m_usr; + } + + // u32 for cell. + const u32 GetUsrId() const + { + return m_usrid; + } + u64 GetPauseTime() { return m_pause_amend_time; @@ -491,6 +505,15 @@ struct cfg_root : cfg::node } net{this}; + struct node_usr : cfg::node + { + node_usr(cfg::node* _this) : cfg::node(_this, "User") {} + + // This is only a default, will get read from existing config.yml. + cfg::string selected_usr{ this, "Selected User", "00000001" }; + + } usr{this}; + struct node_misc : cfg::node { node_misc(cfg::node* _this) : cfg::node(_this, "Miscellaneous") {} diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index 5dfe160bbf..c0cba7391c 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -481,6 +481,11 @@ true true + + true + true + true + true true @@ -636,6 +641,11 @@ true true + + true + true + true + true true @@ -811,6 +821,11 @@ true true + + true + true + true + true true @@ -966,6 +981,11 @@ true true + + true + true + true + true true @@ -998,6 +1018,8 @@ + + @@ -1605,6 +1627,24 @@ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DLLVM_AVAILABLE -D_SCL_SECURE_NO_WARNINGS -D_UNICODE "-I$(VULKAN_SDK)\Include" "-I.\.." "-I.\..\3rdparty\minidx12\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtQml" "-I$(QTDIR)\include\QtNetwork" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)\." "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" + + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_NO_DEBUG -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_QUICK_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DBRANCH= -DLLVM_AVAILABLE -D_UNICODE "-I.\..\Vulkan\Vulkan-LoaderAndValidationLayers\include" "-I.\..\3rdparty\minidx12\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtQuick" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtQml" "-I$(QTDIR)\include\QtNetwork" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)\." "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_QUICK_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -D_SCL_SECURE_NO_WARNINGS -D_UNICODE "-I.\..\Vulkan\Vulkan-LoaderAndValidationLayers\include" "-I.\.." "-I.\..\3rdparty\minidx12\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtQuick" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtQml" "-I$(QTDIR)\include\QtNetwork" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)\." "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_NO_DEBUG -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_QUICK_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -D_UNICODE "-I.\..\Vulkan\Vulkan-LoaderAndValidationLayers\include" "-I.\..\3rdparty\minidx12\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtQuick" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtQml" "-I$(QTDIR)\include\QtNetwork" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)\." "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_QUICK_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DLLVM_AVAILABLE -D_SCL_SECURE_NO_WARNINGS -D_UNICODE "-I.\..\Vulkan\Vulkan-LoaderAndValidationLayers\include" "-I.\.." "-I.\..\3rdparty\minidx12\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtQuick" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtQml" "-I$(QTDIR)\include\QtNetwork" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)\." "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" + $(QTDIR)\bin\moc.exe;%(FullPath) + $(QTDIR)\bin\moc.exe;%(FullPath) + $(QTDIR)\bin\moc.exe;%(FullPath) + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing welcome_dialog.h... @@ -1663,6 +1703,7 @@ + diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index 6a44ee0c1f..71cf3f785f 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -102,6 +102,9 @@ {77ca7382-c296-43af-adb4-fb32f5ab4571} + + {bebec4fb-789c-4456-a838-5ba7e158af5c} + @@ -599,6 +602,24 @@ Generated Files\Debug - LLVM + + Gui\user accounts + + + Gui\user accounts + + + Generated Files\Release - LLVM + + + Generated Files\Debug + + + Generated Files\Release + + + Generated Files\Debug - LLVM + Gui\debugger @@ -718,6 +739,9 @@ Gui + + Gui\user accounts + Gui\utils @@ -840,6 +864,9 @@ Gui\debugger + + Gui\user accounts + Gui\debugger diff --git a/rpcs3/rpcs3qt/emu_settings.h b/rpcs3/rpcs3qt/emu_settings.h index 39c490c273..b97a2bff80 100644 --- a/rpcs3/rpcs3qt/emu_settings.h +++ b/rpcs3/rpcs3qt/emu_settings.h @@ -110,6 +110,9 @@ public: // Network ConnectionStatus, + // User + SelectedUser, + // Language Language, EnableHostRoot, @@ -287,6 +290,9 @@ private: // Networking { ConnectionStatus, { "Net", "Connection status"}}, + // User + {SelectedUser, {"User", "Selected User"}}, + // System { Language, { "System", "Language"}}, { EnableHostRoot, { "VFS", "Enable /host_root/"}}, diff --git a/rpcs3/rpcs3qt/gui_settings.h b/rpcs3/rpcs3qt/gui_settings.h index 212e26e1bd..db1fcd7330 100644 --- a/rpcs3/rpcs3qt/gui_settings.h +++ b/rpcs3/rpcs3qt/gui_settings.h @@ -126,6 +126,7 @@ namespace gui const QString gs_frame = "GSFrame"; const QString trophy = "Trophy"; const QString savedata = "SaveData"; + const QString users = "Users"; const QString notes = "Notes"; const QColor gl_icon_color = QColor(36, 36, 36, 255); @@ -216,6 +217,8 @@ namespace gui const gui_save tr_trophy_state = gui_save(trophy, "trophy_state", QByteArray()); const gui_save sd_geometry = gui_save(savedata, "geometry", QByteArray()); + + const gui_save um_geometry = gui_save(users, "geometry", QByteArray()); } /** Class for GUI settings.. diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 59fdfb0f3b..6ee29aecbd 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -16,6 +16,7 @@ #include "vfs_dialog.h" #include "save_manager_dialog.h" #include "trophy_manager_dialog.h" +#include "user_manager_dialog.h" #include "kernel_explorer.h" #include "game_list_frame.h" #include "debugger_frame.h" @@ -891,6 +892,8 @@ void main_window::OnEmuStop() ui->toolbar_start->setText(tr("Play")); ui->toolbar_start->setToolTip(Emu.IsReady() ? tr("Start emulation") : tr("Resume emulation")); } + ui->actionManage_Users->setEnabled(true); + #ifdef WITH_DISCORD_RPC // Discord Rich Presence Integration if (guiSettings->GetValue(gui::m_richPresence).toBool()) @@ -913,6 +916,8 @@ void main_window::OnEmuReady() ui->toolbar_start->setText(tr("Play")); ui->toolbar_start->setToolTip(Emu.IsReady() ? tr("Start emulation") : tr("Resume emulation")); EnableMenus(true); + + ui->actionManage_Users->setEnabled(false); } void main_window::EnableMenus(bool enabled) @@ -1262,6 +1267,12 @@ void main_window::CreateConnects() trop_manager->show(); }); + connect(ui->actionManage_Users, &QAction::triggered, [=] + { + user_manager_dialog* user_manager = new user_manager_dialog(guiSettings, emuSettings, "", this); + user_manager->show(); + }); + connect(ui->toolsCgDisasmAct, &QAction::triggered, [=] { cg_disasm_window* cgdw = new cg_disasm_window(guiSettings); @@ -1778,8 +1789,7 @@ void main_window::dropEvent(QDropEvent* event) { const std::string rapname = sstr(QFileInfo(rap).fileName()); - // TODO: use correct user ID once User Manager is implemented - if (!fs::copy_file(sstr(rap), fmt::format("%s/home/%s/exdata/%s", Emu.GetHddDir(), "00000001", rapname), false)) + if (!fs::copy_file(sstr(rap), fmt::format("%s/home/%s/exdata/%s", Emu.GetHddDir(), Emu.GetUsr(), rapname), false)) { LOG_WARNING(GENERAL, "Could not copy rap file by drop: %s", rapname); } diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index b617347bf3..5485b2b191 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -502,7 +502,7 @@ - false + true User Accounts diff --git a/rpcs3/rpcs3qt/save_data_info_dialog.cpp b/rpcs3/rpcs3qt/save_data_info_dialog.cpp index a31ab8a44f..61dab2fe4f 100644 --- a/rpcs3/rpcs3qt/save_data_info_dialog.cpp +++ b/rpcs3/rpcs3qt/save_data_info_dialog.cpp @@ -3,6 +3,7 @@ #include #include #include +#include "Emu/System.h" constexpr auto qstr = QString::fromStdString; @@ -60,7 +61,7 @@ void save_data_info_dialog::UpdateData() //Maybe there should be more details of save data. m_list->setItem(0, 0, new QTableWidgetItem(tr("User ID"))); - m_list->setItem(0, 1, new QTableWidgetItem("00000001 (Default)")); + m_list->setItem(0, 1, new QTableWidgetItem(qstr(Emu.GetUsr()))); m_list->setItem(1, 0, new QTableWidgetItem(tr("Title"))); m_list->setItem(1, 1, new QTableWidgetItem(qstr(m_entry.title))); diff --git a/rpcs3/rpcs3qt/save_manager_dialog.cpp b/rpcs3/rpcs3qt/save_manager_dialog.cpp index b483615f0f..c81814dc15 100644 --- a/rpcs3/rpcs3qt/save_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/save_manager_dialog.cpp @@ -146,7 +146,8 @@ void save_manager_dialog::UpdateList() { if (m_dir == "") { - m_dir = Emu.GetHddDir() + "home/00000001/savedata/"; + // fmt::format(%shome ... is harder to read than straight concatenation. + m_dir = Emu.GetHddDir() + "home/" + Emu.GetUsr() + "/savedata/"; } m_save_entries = GetSaveEntries(m_dir); diff --git a/rpcs3/rpcs3qt/trophy_manager_dialog.cpp b/rpcs3/rpcs3qt/trophy_manager_dialog.cpp index 05e2b7a6f2..426aef28e1 100644 --- a/rpcs3/rpcs3qt/trophy_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/trophy_manager_dialog.cpp @@ -29,8 +29,6 @@ #include #include -static const char* m_TROPHY_DIR = "/dev_hdd0/home/00000001/trophy/"; - namespace { constexpr auto qstr = QString::fromStdString; @@ -58,6 +56,10 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr gui_s // HACK: dev_hdd0 must be mounted for vfs to work for loading trophies. vfs::mount("dev_hdd0", Emu.GetHddDir()); + + // Get the currently selected user's trophy path. + m_trophy_dir = fmt::format("/dev_hdd0/home/%s/trophy/", Emu.GetUsr()); + // Game chooser combo box m_game_combo = new QComboBox(); m_game_combo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); @@ -320,7 +322,7 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr gui_s bool trophy_manager_dialog::LoadTrophyFolderToDB(const std::string& trop_name) { - std::string trophyPath = m_TROPHY_DIR + trop_name; + std::string trophyPath = m_trophy_dir + trop_name; // Populate GameTrophiesData std::unique_ptr game_trophy_data = std::make_unique(); diff --git a/rpcs3/rpcs3qt/trophy_manager_dialog.h b/rpcs3/rpcs3qt/trophy_manager_dialog.h index bb1a6ce49b..51ab360843 100644 --- a/rpcs3/rpcs3qt/trophy_manager_dialog.h +++ b/rpcs3/rpcs3qt/trophy_manager_dialog.h @@ -106,6 +106,7 @@ private: bool m_show_silver_trophies = true; bool m_show_gold_trophies = true; bool m_show_platinum_trophies = true; + std::string m_trophy_dir; int m_icon_height = 75; bool m_save_icon_height = false; diff --git a/rpcs3/rpcs3qt/user_account.cpp b/rpcs3/rpcs3qt/user_account.cpp new file mode 100644 index 0000000000..dd918026a3 --- /dev/null +++ b/rpcs3/rpcs3qt/user_account.cpp @@ -0,0 +1,27 @@ +#include "user_account.h" + +UserAccount::UserAccount(const std::string& userId) +{ + // Setting userId. + m_userId = userId; + + // Setting userDir. + m_userDir = Emu.GetHddDir() + "home/" + m_userId + "/"; + + // Setting userName. + fs::file file; + if (file.open(m_userDir + "localusername", fs::read)) + { + file.read(m_userName, 16*sizeof(char)); //max of 16 chars on real PS3 + file.close(); + } + else + { + LOG_WARNING(GENERAL, "UserAccount: localusername file read error (userId=%s, userDir=%s).", m_userId, m_userDir); + } +} + +UserAccount::~UserAccount() +{ +} + diff --git a/rpcs3/rpcs3qt/user_account.h b/rpcs3/rpcs3qt/user_account.h new file mode 100644 index 0000000000..d3c9e8fc41 --- /dev/null +++ b/rpcs3/rpcs3qt/user_account.h @@ -0,0 +1,26 @@ +#pragma once + +#include "Emu/System.h" +#include "Utilities/File.h" +#include "Utilities/StrFmt.h" + +// Do not confuse this with the "user" in Emu/System.h. +// That user is read from config.yml, and it only represents the currently "logged in" user. +// The UserAccount class will represent all users in the home directory for the User Manager dialog. +// Selecting a user account in this dialog and saving writes it to config.yml. +class UserAccount +{ +public: + explicit UserAccount(const std::string& userId); + + std::string GetUserId() { return m_userId; }; + std::string GetUserDir() { return m_userDir; }; + std::string GetUserName() { return m_userName; }; + ~UserAccount(); + +private: + std::string m_userId; + std::string m_userDir; + std::string m_userName; + +}; diff --git a/rpcs3/rpcs3qt/user_manager_dialog.cpp b/rpcs3/rpcs3qt/user_manager_dialog.cpp new file mode 100644 index 0000000000..5aedee4457 --- /dev/null +++ b/rpcs3/rpcs3qt/user_manager_dialog.cpp @@ -0,0 +1,371 @@ +#include "user_manager_dialog.h" + +#include "Utilities/StrUtil.h" + +#include +#include + +namespace +{ + std::vector GetUserAccounts(const std::string& base_dir) + { + std::vector user_list; + + // I believe this gets the folder list sorted alphabetically by default, + // but I can't find proof of this always being true. + for (const auto& userFolder : fs::dir(base_dir)) + { + if (!userFolder.is_directory) + { + continue; + } + + // Is the folder name exactly 8 all-numerical characters long? + // We use strtol to find any non-numeric characters in folder name. + char* nonNumericChar; + std::strtol(userFolder.name.c_str(), &nonNumericChar, 10); + if (userFolder.name.length() != 8 || *nonNumericChar != '\0') + { + continue; + } + + // Does the localusername file exist? + if (!fs::is_file(fmt::format("%s/%s/localusername", base_dir, userFolder.name))) + { + continue; + } + + UserAccount* user_entry = new UserAccount(userFolder.name); + user_list.emplace_back(user_entry); + } + return user_list; + } +} + +user_manager_dialog::user_manager_dialog(std::shared_ptr gui_settings, std::shared_ptr emu_settings, const std::string& dir, QWidget* parent) + : QDialog(parent), m_user_list(), m_dir(dir), m_sort_column(1), m_sort_ascending(true), m_gui_settings(gui_settings), m_emu_settings(emu_settings) +{ + setWindowTitle(tr("User Manager")); + setMinimumSize(QSize(400, 400)); + setModal(true); + + m_emu_settings->LoadSettings(); + Init(dir); +} + +void user_manager_dialog::Init(const std::string& dir) +{ + // Table + m_table = new QTableWidget(this); + + //m_table->setItemDelegate(new table_item_delegate(this)); // to get rid of cell selection rectangles include "table_item_delegate.h" + m_table->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); + m_table->setSelectionBehavior(QAbstractItemView::SelectRows); + m_table->setContextMenuPolicy(Qt::CustomContextMenu); + m_table->setColumnCount(2); + m_table->setHorizontalHeaderLabels(QStringList() << tr("User ID") << tr("User Name")); + + QPushButton* push_remove_user = new QPushButton(tr("Delete User"), this); + QPushButton* push_create_user = new QPushButton(tr("Create User"), this); + QPushButton* push_login_user = new QPushButton(tr("Log In User"), this); + QPushButton* push_rename_user = new QPushButton(tr("Rename User"), this); + + QPushButton* push_close = new QPushButton(tr("&Close"), this); + push_close->setAutoDefault(true); + + // Button Layout + QHBoxLayout* hbox_buttons = new QHBoxLayout(); + hbox_buttons->addWidget(push_create_user); + hbox_buttons->addWidget(push_login_user); + hbox_buttons->addWidget(push_rename_user); + hbox_buttons->addWidget(push_remove_user); + hbox_buttons->addStretch(); + hbox_buttons->addWidget(push_close); + + // main layout + QVBoxLayout* vbox_main = new QVBoxLayout(); + vbox_main->setAlignment(Qt::AlignCenter); + vbox_main->addWidget(m_table); + vbox_main->addLayout(hbox_buttons); + setLayout(vbox_main); + + m_selected_user = m_emu_settings->GetSetting(emu_settings::SelectedUser); + UpdateTable(); + + restoreGeometry(m_gui_settings->GetValue(gui::um_geometry).toByteArray()); + + // Use this in multiple connects to protect the current user from deletion/rename. + auto enableButtons = [=]() + { + int idx = m_table->currentRow(); + if (idx < 0) + { + return; + } + int idx_real = m_table->item(idx, 0)->data(Qt::UserRole).toInt(); + std::string idx_user = m_user_list[idx_real]->GetUserId(); + bool enable = idx_user != m_selected_user; + + push_rename_user->setEnabled(enable); + push_remove_user->setEnabled(enable); + }; + + // Connects and events + connect(push_close, &QAbstractButton::clicked, this, &user_manager_dialog::close); + connect(push_remove_user, &QAbstractButton::clicked, this, &user_manager_dialog::OnUserRemove); + connect(push_rename_user, &QAbstractButton::clicked, this, &user_manager_dialog::OnUserRename); + connect(push_create_user, &QAbstractButton::clicked, this, &user_manager_dialog::OnUserCreate); + connect(push_login_user, &QAbstractButton::clicked, this, &user_manager_dialog::OnUserLogin); + connect(this, &user_manager_dialog::OnUserLoginSuccess, this, enableButtons); + connect(m_table, &QTableWidget::itemDoubleClicked, this, &user_manager_dialog::OnUserLogin); + connect(m_table->horizontalHeader(), &QHeaderView::sectionClicked, this, &user_manager_dialog::OnSort); + connect(m_table, &QTableWidget::customContextMenuRequested, this, &user_manager_dialog::ShowContextMenu); + connect(m_table, &QTableWidget::itemClicked, this, enableButtons); + connect(m_table, &QTableWidget::itemSelectionChanged, this, enableButtons); +} + +void user_manager_dialog::UpdateTable() +{ + if (m_dir == "") + { + // fmt::format(%shome ... is harder to read than straight concatenation. + m_dir = Emu.GetHddDir() + "home"; + } + + // Get the user folders in the home directory and the currently logged in user. + m_user_list.clear(); + m_user_list = GetUserAccounts(m_dir); + + // Clear and then repopulate the table with the list gathered above. + m_table->setRowCount(static_cast(m_user_list.size())); + + // For indicating logged-in user. + QFont boldFont; + boldFont.setBold(true); + + int row = 0; + for (UserAccount* user : m_user_list) + { + QString userId = qstr(user->GetUserId()); + QString userName = qstr(user->GetUserName()); + + QTableWidgetItem* userIdItem = new QTableWidgetItem(userId); + userIdItem->setData(Qt::UserRole, row); // For sorting to work properly + userIdItem->setFlags(userIdItem->flags() & ~Qt::ItemIsEditable); + m_table->setItem(row, 0, userIdItem); + + QTableWidgetItem* userNameItem = new QTableWidgetItem(userName); + userNameItem->setData(Qt::UserRole, row); // For sorting to work properly + userNameItem->setFlags(userNameItem->flags() & ~Qt::ItemIsEditable); + m_table->setItem(row, 1, userNameItem); + + // Compare current config value with the one in this user (only 8 digits in userId) + if (m_selected_user.compare(0, 8, user->GetUserId()) == 0) + { + userIdItem->setFont(boldFont); + userNameItem->setFont(boldFont); + } + ++row; + } + // GUI resizing + m_table->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); + m_table->verticalHeader()->resizeSections(QHeaderView::ResizeToContents); + + QSize tableSize = QSize( + m_table->verticalHeader()->width() + m_table->horizontalHeader()->length() + m_table->frameWidth() * 2, + m_table->horizontalHeader()->height() + m_table->verticalHeader()->length() + m_table->frameWidth() * 2); + + QSize preferredSize = minimumSize().expandedTo(sizeHint() - m_table->sizeHint() + tableSize).expandedTo(size()); + QSize maxSize = QSize(preferredSize.width(), static_cast(QApplication::desktop()->screenGeometry().height()*.6)); + + resize(preferredSize.boundedTo(maxSize)); +} + +//Remove a user folder, needs to be confirmed. +void user_manager_dialog::OnUserRemove() +{ + int idx = m_table->currentRow(); + int idx_real = m_table->item(idx, 0)->data(Qt::UserRole).toInt(); + if (QMessageBox::question(this, tr("Delete Confirmation"), tr("Are you sure you want to delete:\n%1?").arg(qstr(m_user_list[idx_real]->GetUserName())), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) + { + fs::remove_all(m_user_list[idx_real]->GetUserDir()); + UpdateTable(); + } +} + +void user_manager_dialog::GenerateUser(const std::string& username) +{ + // If the user list is sorted by defult from fs::dir, then we just need the last one in the list. + std::string largestUserId = m_user_list[m_user_list.size() - 1]->GetUserId(); + + // Add one to the largest user id, then reformat the result into an 8-digit string. + u8 nextLargest = static_cast(std::stoul(largestUserId) + 1u); + char* buf; + sprintf(buf, "%08d", nextLargest); + std::string nextUserId(buf); + + // Create user folders and such. + const std::string homeDir = Emu.GetHddDir() + "home/"; + const std::string userDir = homeDir + nextUserId; + fs::create_dir(homeDir); + fs::create_dir(fmt::format("%s/", userDir)); + fs::create_dir(fmt::format("%s/exdata/", userDir)); + fs::create_dir(fmt::format("%s/savedata/", userDir)); + fs::create_dir(fmt::format("%s/trophy/", userDir)); + fs::write_file(fmt::format("%s/localusername", userDir), fs::create + fs::excl + fs::write, username); +} + +bool user_manager_dialog::ValidateUsername(const QString& textToValidate) +{ + // "Entire string (^...$) must be between 3 and 16 characters + // and only consist of letters, numbers, underscores, and hyphens." + QRegExpValidator validator(QRegExp("^[A-Za-z0-9_-]{3,16}$")); + + int pos = 0; + QString text = textToValidate; + return (validator.validate(text, pos) == QValidator::Acceptable); +} + +void user_manager_dialog::OnUserRename() +{ + QInputDialog* dialog = new QInputDialog(this); + dialog->setWindowTitle(tr("Rename User")); + dialog->setLabelText(tr("New Username: ")); + dialog->resize(200, 100); + + while (dialog->exec() != QDialog::Rejected) + { + dialog->resize(200, 100); + QString textToValidate = dialog->textValue(); + if (!ValidateUsername(textToValidate)) + { + QMessageBox::warning(this, tr("Error"), tr("Name must be between 3 and 16 characters and only consist of letters, numbers, underscores, and hyphens.")); + continue; + } + int idx = m_table->currentRow(); + int idx_real = m_table->item(idx, 0)->data(Qt::UserRole).toInt(); + + const std::string userId = m_user_list[idx_real]->GetUserId(); + const std::string usernameFile = Emu.GetHddDir() + "home/" + userId + "/localusername"; + fs::write_file(usernameFile, fs::rewrite, textToValidate.toStdString()); + UpdateTable(); + break; + } +} + +void user_manager_dialog::OnUserCreate() +{ + QInputDialog* dialog = new QInputDialog(this); + dialog->setWindowTitle(tr("New User")); + dialog->setLabelText(tr("New Username: ")); + dialog->resize(200, 100); + + while (dialog->exec() != QDialog::Rejected) + { + dialog->resize(200, 100); + QString textToValidate = dialog->textValue(); + if (!ValidateUsername(textToValidate)) + { + QMessageBox::warning(this, tr("Error"), tr("Name must be between 3 and 16 characters and only consist of letters, numbers, underscores, and hyphens.")); + continue; + } + GenerateUser(textToValidate.toStdString()); + UpdateTable(); + break; + } +} + +void user_manager_dialog::OnUserLogin() +{ + int idx = m_table->currentRow(); + int idx_real = m_table->item(idx, 0)->data(Qt::UserRole).toInt(); + std::string selectedUserId = m_user_list[idx_real]->GetUserId(); + + m_selected_user = selectedUserId; + m_emu_settings->SetSetting(emu_settings::SelectedUser, m_selected_user); + m_emu_settings->SaveSettings(); + UpdateTable(); + Q_EMIT OnUserLoginSuccess(); +} + +void user_manager_dialog::OnSort(int logicalIndex) +{ + if (logicalIndex < 0) + { + return; + } + else if (logicalIndex == m_sort_column) + { + m_sort_ascending ^= true; + } + else + { + m_sort_ascending = true; + } + Qt::SortOrder sort_order = m_sort_ascending ? Qt::AscendingOrder : Qt::DescendingOrder; + m_table->sortByColumn(m_sort_column, sort_order); + m_sort_column = logicalIndex; +} + +void user_manager_dialog::ShowContextMenu(const QPoint &pos) +{ + int idx = m_table->currentRow(); + if (idx < 0) + { + return; + } + QPoint globalPos = m_table->mapToGlobal(pos); + QMenu* menu = new QMenu(); + + // Create all the actions before adding them to the menu/submenus. + QAction* userIdAct = new QAction(tr("User ID"), this); + QAction* userNameAct = new QAction(tr("User Name"), this); + + QAction* removeAct = new QAction(tr("&Remove"), this); + QAction* renameAct = new QAction(tr("&Rename"), this); + QAction* loginAct = new QAction(tr("&Login"), this); + QAction* showDirAct = new QAction(tr("&Open User Directory"), this); + + //Create submenu for sort options. + m_sort_options = new QMenu(tr("&Sort By")); + m_sort_options->addAction(userIdAct); + m_sort_options->addAction(userNameAct); + + // Add all options and submenus to the context menu. + menu->addMenu(m_sort_options); + menu->addSeparator(); + menu->addAction(removeAct); + menu->addAction(renameAct); + menu->addAction(loginAct); + menu->addAction(showDirAct); + + // Only enable actions if selected user is not logged in user. + int idx_real = m_table->item(idx, 0)->data(Qt::UserRole).toInt(); + std::string idx_user = m_user_list[idx_real]->GetUserId(); + bool enable = idx_user != m_selected_user; + + removeAct->setEnabled(enable); + renameAct->setEnabled(enable); + + // Connects and Events + connect(removeAct, &QAction::triggered, this, &user_manager_dialog::OnUserRemove); + connect(renameAct, &QAction::triggered, this, &user_manager_dialog::OnUserRename); + connect(loginAct, &QAction::triggered, this, &user_manager_dialog::OnUserLogin); + connect(showDirAct, &QAction::triggered, [=]() + { + int idx_real = m_table->item(idx, 0)->data(Qt::UserRole).toInt(); + QString path = qstr(m_user_list[idx_real]->GetUserDir()); + QDesktopServices::openUrl(QUrl("file:///" + path)); + }); + + connect(userIdAct, &QAction::triggered, this, [=] {OnSort(0); }); + connect(userNameAct, &QAction::triggered, this, [=] {OnSort(1); }); + + menu->exec(globalPos); +} + +void user_manager_dialog::closeEvent(QCloseEvent *event) +{ + m_gui_settings->SetValue(gui::um_geometry, saveGeometry()); + QDialog::closeEvent(event); +} diff --git a/rpcs3/rpcs3qt/user_manager_dialog.h b/rpcs3/rpcs3qt/user_manager_dialog.h new file mode 100644 index 0000000000..3daa6af319 --- /dev/null +++ b/rpcs3/rpcs3qt/user_manager_dialog.h @@ -0,0 +1,58 @@ +#pragma once + +#include "stdafx.h" +#include "gui_settings.h" +#include "emu_settings.h" +#include "Emu/System.h" + +#include "Utilities/File.h" +#include "user_account.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class user_manager_dialog : public QDialog +{ + Q_OBJECT +public: + explicit user_manager_dialog(std::shared_ptr gui_settings, std::shared_ptr emu_settings, const std::string& dir = "", QWidget* parent = nullptr); +Q_SIGNALS: + void OnUserLoginSuccess(); +private Q_SLOTS: + void OnUserLogin(); + void OnUserCreate(); + void OnUserRemove(); + void OnUserRename(); + void OnSort(int logicalIndex); +private: + void Init(const std::string& dir); + void UpdateTable(); + void GenerateUser(const std::string& username); + bool ValidateUsername(const QString& textToValidate); + + void ShowContextMenu(const QPoint &pos); + + void closeEvent(QCloseEvent* event) override; + + QTableWidget* m_table; + std::string m_dir; + std::string m_selected_user; + std::vector m_user_list; + + std::shared_ptr m_gui_settings; + std::shared_ptr m_emu_settings; + + QMenu* m_sort_options; + + int m_sort_column; + bool m_sort_ascending; +};