From f86ac251a89e38eb73a15870eed3398fcf74ab56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandro=20S=C3=A1nchez=20Bach?= Date: Thu, 20 Mar 2014 19:23:14 +0100 Subject: [PATCH] TROPUSR Loader & sceNpTrophy stuff * Added a loader for the TROPUSR.DAT files. * Added a few structs/enums to sceNpTrophy.h * Added more sceNpTrophy functions and updated other ones. * Updated cellHddGame error codes in cellSysutil.h * Added addresses of cellHddGame to cellSysutil_init. NOTE: There is a known problem in the `if (!Emu.GetVFS().ExistsFile(filepath))` in `TROPUSRLoader::Load` which causes the games to overwrite their TROPUSR.DAT file every time they boot and they "forget" the unlocked trophies. However, as long as the game is running the unlocked trophies should be still there. --- rpcs3/Emu/SysCalls/Modules/cellSysutil.cpp | 5 +- rpcs3/Emu/SysCalls/Modules/cellSysutil.h | 15 +- rpcs3/Emu/SysCalls/Modules/sceNpTrophy.cpp | 159 +++++++++++++--- rpcs3/Emu/SysCalls/Modules/sceNpTrophy.h | 15 +- rpcs3/Loader/TROPUSR.cpp | 212 +++++++++++++++++++++ rpcs3/Loader/TROPUSR.h | 83 ++++++++ rpcs3/rpcs3.vcxproj | 1 + rpcs3/rpcs3.vcxproj.filters | 3 + 8 files changed, 457 insertions(+), 36 deletions(-) create mode 100644 rpcs3/Loader/TROPUSR.cpp create mode 100644 rpcs3/Loader/TROPUSR.h diff --git a/rpcs3/Emu/SysCalls/Modules/cellSysutil.cpp b/rpcs3/Emu/SysCalls/Modules/cellSysutil.cpp index 9f9b0aa3af..7ce3935dd4 100644 --- a/rpcs3/Emu/SysCalls/Modules/cellSysutil.cpp +++ b/rpcs3/Emu/SysCalls/Modules/cellSysutil.cpp @@ -943,5 +943,8 @@ void cellSysutil_init() cellSysutil.AddFunc(0x744c1544, cellSysCacheClear); cellSysutil.AddFunc(0x9117df20, cellHddGameCheck); - + //cellSysutil.AddFunc(0x4bdec82a, cellHddGameCheck2); + //cellSysutil.AddFunc(0xf82e2ef7, cellHddGameGetSizeKB); + //cellSysutil.AddFunc(0x9ca9ffa7, cellHddGameSetSystemVer); + //cellSysutil.AddFunc(0xafd605b3, cellHddGameExitBroken); } diff --git a/rpcs3/Emu/SysCalls/Modules/cellSysutil.h b/rpcs3/Emu/SysCalls/Modules/cellSysutil.h index 3084fc537a..eb53f7ea34 100644 --- a/rpcs3/Emu/SysCalls/Modules/cellSysutil.h +++ b/rpcs3/Emu/SysCalls/Modules/cellSysutil.h @@ -146,13 +146,14 @@ enum CellMsgDialogType enum { // Return Codes - CELL_HDDGAME_RET_CANCEL = 0, - CELL_HDDGAME_ERROR_CBRESULT = 0, - CELL_HDDGAME_ERROR_ACCESS_ERROR = 0, - CELL_HDDGAME_ERROR_INTERNAL = 0, - CELL_HDDGAME_ERROR_PARAM = 0, - CELL_HDDGAME_ERROR_BROKEN = 0, - CELL_HDDGAME_ERROR_FAILURE = 0, + CELL_HDDGAME_RET_CANCEL = 1, + CELL_HDDGAME_ERROR_CBRESULT = 0x8002ba01, + CELL_HDDGAME_ERROR_ACCESS_ERROR = 0x8002ba02, + CELL_HDDGAME_ERROR_INTERNAL = 0x8002ba03, + CELL_HDDGAME_ERROR_PARAM = 0x8002ba04, + CELL_HDDGAME_ERROR_NOSPACE = 0x8002ba05, + CELL_HDDGAME_ERROR_BROKEN = 0x8002ba06, + CELL_HDDGAME_ERROR_FAILURE = 0x8002ba07, // Callback Result CELL_HDDGAME_CBRESULT_OK_CANCEL = 1, diff --git a/rpcs3/Emu/SysCalls/Modules/sceNpTrophy.cpp b/rpcs3/Emu/SysCalls/Modules/sceNpTrophy.cpp index 7f02dd1e6f..41d34dd6e9 100644 --- a/rpcs3/Emu/SysCalls/Modules/sceNpTrophy.cpp +++ b/rpcs3/Emu/SysCalls/Modules/sceNpTrophy.cpp @@ -1,11 +1,14 @@ #include "stdafx.h" #include "Emu/SysCalls/SysCalls.h" #include "Emu/SysCalls/SC_FUNC.h" +#include "wx/xml/xml.h" #include "sceNp.h" #include "sceNpTrophy.h" #include "Loader/TRP.h" +#include "Loader/TROPUSR.h" +#include "Emu/SysCalls/lv2/SC_Time.h" void sceNpTrophy_unload(); void sceNpTrophy_init(); @@ -17,6 +20,8 @@ struct sceNpTrophyInternalContext // TODO std::string trp_name; vfsStream* trp_stream; + + TROPUSRLoader* tropusr; }; struct sceNpTrophyInternal @@ -149,9 +154,14 @@ int sceNpTrophyRegisterContext(u32 context, u32 handle, u32 statusCb_addr, u32 a } // TODO: Get the path of the current user - if (!trp.Install("/dev_hdd0/home/00000001/trophy/" + ctxt.trp_name)) + std::string trophyPath = "/dev_hdd0/home/00000001/trophy/" + ctxt.trp_name; + if (!trp.Install(trophyPath)) return SCE_NP_TROPHY_ERROR_ILLEGAL_UPDATE; + TROPUSRLoader* tropusr = new TROPUSRLoader(); + tropusr->Load(trophyPath + "/TROPUSR.DAT", trophyPath + "/TROPCONF.SFM"); + ctxt.tropusr = tropusr; + // TODO: Callbacks return CELL_OK; @@ -183,7 +193,6 @@ int sceNpTrophyGetRequiredDiskSpace(u32 context, u32 handle, mem64_t reqspace, u // TODO: There are other possible errors sceNpTrophyInternalContext& ctxt = s_npTrophyInstance.contexts[context]; - if (!ctxt.trp_stream) return SCE_NP_TROPHY_ERROR_CONF_DOES_NOT_EXIST; @@ -214,21 +223,46 @@ int sceNpTrophyGetGameInfo(u32 context, u32 handle, mem_ptr_ttitle, "Trophy Set", SCE_NP_TROPHY_NAME_MAX_SIZE); - memcpy(details->description, "Hey! Implement a XML reader, and load the description from TROP.SFM", SCE_NP_TROPHY_DESCR_MAX_SIZE); - details->numTrophies = 0; - details->numPlatinum = 0; - details->numGold = 0; - details->numSilver = 0; - details->numBronze = 0; - data->unlockedTrophies = 0; - data->unlockedPlatinum = 0; - data->unlockedGold = 0; - data->unlockedSilver = 0; - data->unlockedBronze = 0; + std::string titleName; + std::string titleDetail; + for (wxXmlNode *n = doc.GetRoot()->GetChildren(); n; n = n->GetNext()) { + if (n->GetName() == "title-name") + titleName = n->GetNodeContent().mb_str(); + if (n->GetName() == "title-detail") + titleDetail = n->GetNodeContent().mb_str(); + if (n->GetName() == "trophy") + { + u32 trophy_id = atoi(n->GetAttribute("id").mb_str()); + + details->numTrophies++; + switch (n->GetAttribute("ttype").mb_str()[0]) { + case 'B': details->numBronze++; break; + case 'S': details->numSilver++; break; + case 'G': details->numGold++; break; + case 'P': details->numPlatinum++; break; + } + + if (ctxt.tropusr->GetTrophyUnlockState(trophy_id)) + { + data->unlockedTrophies++; + switch (n->GetAttribute("ttype").mb_str()[0]) { + case 'B': data->unlockedBronze++; break; + case 'S': data->unlockedSilver++; break; + case 'G': data->unlockedGold++; break; + case 'P': data->unlockedPlatinum++; break; + } + } + } + } + + memcpy(details->title, titleName.c_str(), SCE_NP_TROPHY_NAME_MAX_SIZE); + memcpy(details->description, titleDetail.c_str(), SCE_NP_TROPHY_DESCR_MAX_SIZE); return CELL_OK; } @@ -238,9 +272,29 @@ int sceNpTrophyDestroyHandle() return CELL_OK; } -int sceNpTrophyUnlockTrophy() +int sceNpTrophyUnlockTrophy(u32 context, u32 handle, s32 trophyId, mem32_t platinumId) { - UNIMPLEMENTED_FUNC(sceNpTrophy); + sceNpTrophy.Warning("sceNpTrophyUnlockTrophy(context=%d, handle=%d, trophyId=%d, platinumId_addr=0x%x)", + context, handle, trophyId, platinumId.GetAddr()); + + if (!s_npTrophyInstance.m_bInitialized) + return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED; + if (!platinumId.IsGood()) + return SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT; + // TODO: There are other possible errors + + sceNpTrophyInternalContext& ctxt = s_npTrophyInstance.contexts[context]; + if (trophyId >= ctxt.tropusr->GetTrophiesCount()) + return SCE_NP_TROPHY_ERROR_INVALID_TROPHY_ID; + if (ctxt.tropusr->GetTrophyUnlockState(trophyId)) + return SCE_NP_TROPHY_ERROR_ALREADY_UNLOCKED; + + u64 timestamp1 = get_system_time(); // TODO: Either timestamp1 or timestamp2 is wrong + u64 timestamp2 = get_system_time(); // TODO: Either timestamp1 or timestamp2 is wrong + ctxt.tropusr->UnlockTrophy(trophyId, timestamp1, timestamp2); + ctxt.tropusr->Save("/dev_hdd0/home/00000001/trophy/" + ctxt.trp_name + "/TROPUSR.DAT"); + + platinumId = SCE_NP_TROPHY_INVALID_TROPHY_ID; // TODO return CELL_OK; } @@ -250,9 +304,31 @@ int sceNpTrophyTerm() return CELL_OK; } -int sceNpTrophyGetTrophyUnlockState() +int sceNpTrophyGetTrophyUnlockState(u32 context, u32 handle, mem_ptr_t flags, mem32_t count) { - UNIMPLEMENTED_FUNC(sceNpTrophy); + sceNpTrophy.Warning("sceNpTrophyGetTrophyUnlockState(context=%d, handle=%d, flags_addr=0x%x, count_addr=0x%x)", + context, handle, flags.GetAddr(), count.GetAddr()); + + if (!s_npTrophyInstance.m_bInitialized) + return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED; + if (!flags.IsGood() || !count.IsGood()) + return SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT; + // TODO: There are other possible errors + + sceNpTrophyInternalContext& ctxt = s_npTrophyInstance.contexts[context]; + count = ctxt.tropusr->GetTrophiesCount(); + if (count.GetValue() > 128) + ConLog.Warning("sceNpTrophyGetTrophyUnlockState: More than 128 trophies detected!"); + + // Pack up to 128 bools in u32 flag_bits[4] + for (u32 id=0; idGetTrophyUnlockState(id)) + flags->flag_bits[id/32] |= 1<<(id%32); + else + flags->flag_bits[id/32] &= ~(1<<(id%32)); + } + return CELL_OK; } @@ -273,14 +349,43 @@ int sceNpTrophyGetTrophyInfo(u32 context, u32 handle, s32 trophyId, mem_ptr_tname, "Some Trophy", SCE_NP_TROPHY_NAME_MAX_SIZE); - memcpy(details->description, "Hey! Implement a XML reader, and load the description from TROP.SFM", SCE_NP_TROPHY_DESCR_MAX_SIZE); - details->hidden = false; - details->trophyId = trophyId; - details->trophyGrade = SCE_NP_TROPHY_GRADE_GOLD; + std::string name; + std::string detail; + for (wxXmlNode *n = doc.GetRoot()->GetChildren(); n; n = n->GetNext()) { + if (n->GetName() == "trophy" && (trophyId == atoi(n->GetAttribute("id").mb_str()))) + { + details->trophyId = trophyId; + switch (n->GetAttribute("ttype").mb_str()[0]) { + case 'B': details->trophyGrade = SCE_NP_TROPHY_GRADE_BRONZE; break; + case 'S': details->trophyGrade = SCE_NP_TROPHY_GRADE_SILVER; break; + case 'G': details->trophyGrade = SCE_NP_TROPHY_GRADE_GOLD; break; + case 'P': details->trophyGrade = SCE_NP_TROPHY_GRADE_PLATINUM; break; + } + + switch (n->GetAttribute("hidden").mb_str()[0]) { + case 'y': details->hidden = true; break; + case 'n': details->hidden = false; break; + } + + for (wxXmlNode *n2 = n->GetChildren(); n2; n2 = n2->GetNext()) { + if (n2->GetName() == "name") name = n2->GetNodeContent().mb_str(); + if (n2->GetName() == "detail") detail = n2->GetNodeContent().mb_str(); + } + + data->trophyId = trophyId; + data->unlocked = ctxt.tropusr->GetTrophyUnlockState(trophyId); + data->timestamp.tick = ctxt.tropusr->GetTrophyTimestamp(trophyId); + } + } + + memcpy(details->name, name.c_str(), SCE_NP_TROPHY_NAME_MAX_SIZE); + memcpy(details->description, detail.c_str(), SCE_NP_TROPHY_DESCR_MAX_SIZE); return CELL_OK; } diff --git a/rpcs3/Emu/SysCalls/Modules/sceNpTrophy.h b/rpcs3/Emu/SysCalls/Modules/sceNpTrophy.h index eae2c74ed7..6245c0688c 100644 --- a/rpcs3/Emu/SysCalls/Modules/sceNpTrophy.h +++ b/rpcs3/Emu/SysCalls/Modules/sceNpTrophy.h @@ -55,6 +55,13 @@ enum SCE_NP_TROPHY_GAME_DESCR_MAX_SIZE = 1024, SCE_NP_TROPHY_NAME_MAX_SIZE = 128, SCE_NP_TROPHY_DESCR_MAX_SIZE = 1024, + + SCE_NP_TROPHY_FLAG_SETSIZE = 128, + SCE_NP_TROPHY_FLAG_BITS_SHIFT = 5, + + SCE_NP_TROPHY_INVALID_CONTEXT = 0, + SCE_NP_TROPHY_INVALID_HANDLE = 0, + SCE_NP_TROPHY_INVALID_TROPHY_ID = -1, }; enum SceNpTrophyGrade @@ -97,9 +104,15 @@ struct SceNpTrophyDetails u8 reserved[3]; }; -struct SceNpTrophyData { +struct SceNpTrophyData +{ CellRtcTick timestamp; be_t trophyId; // SceNpTrophyId bool unlocked; u8 reserved[3]; }; + +struct SceNpTrophyFlagArray +{ + u32 flag_bits[SCE_NP_TROPHY_FLAG_SETSIZE >> SCE_NP_TROPHY_FLAG_BITS_SHIFT]; +}; diff --git a/rpcs3/Loader/TROPUSR.cpp b/rpcs3/Loader/TROPUSR.cpp new file mode 100644 index 0000000000..7c0e1fac6b --- /dev/null +++ b/rpcs3/Loader/TROPUSR.cpp @@ -0,0 +1,212 @@ +#include "stdafx.h" +#include "TROPUSR.h" + +#include "wx/xml/xml.h" + +TROPUSRLoader::TROPUSRLoader() +{ + m_file = NULL; + memset(&m_header, 0, sizeof(m_header)); +} + +TROPUSRLoader::~TROPUSRLoader() +{ + Close(); +} + +bool TROPUSRLoader::Load(std::string& filepath, std::string& configpath) +{ + if (m_file) + Close(); + + if (!Emu.GetVFS().ExistsFile(filepath)) + Generate(filepath, configpath); + + m_file = Emu.GetVFS().OpenFile(filepath, vfsRead); + LoadHeader(); + LoadTableHeaders(); + LoadTables(); + + m_file->Close(); + m_file = NULL; + return true; +} + +bool TROPUSRLoader::LoadHeader() +{ + if(!m_file->IsOpened()) + return false; + + m_file->Seek(0); + if (m_file->Read(&m_header, sizeof(TROPUSRHeader)) != sizeof(TROPUSRHeader)) + return false; + return true; +} + +bool TROPUSRLoader::LoadTableHeaders() +{ + if(!m_file->IsOpened()) + return false; + + m_file->Seek(0x30); + m_tableHeaders.clear(); + m_tableHeaders.resize(m_header.tables_count); + for (TROPUSRTableHeader& tableHeader : m_tableHeaders) { + if (m_file->Read(&tableHeader, sizeof(TROPUSRTableHeader)) != sizeof(TROPUSRTableHeader)) + return false; + } + return true; +} + +bool TROPUSRLoader::LoadTables() +{ + if(!m_file->IsOpened()) + return false; + + for (const TROPUSRTableHeader& tableHeader : m_tableHeaders) + { + m_file->Seek(tableHeader.offset); + + if (tableHeader.type == 4) + { + m_table4.clear(); + m_table4.resize(tableHeader.entries_count); + for (auto& entry : m_table4) { + if (m_file->Read(&entry, sizeof(TROPUSREntry4)) != sizeof(TROPUSREntry4)) + return false; + } + } + + if (tableHeader.type == 6) + { + m_table6.clear(); + m_table6.resize(tableHeader.entries_count); + for (auto& entry : m_table6) { + if (m_file->Read(&entry, sizeof(TROPUSREntry6)) != sizeof(TROPUSREntry6)) + return false; + } + } + + // TODO: Other tables + } + + return true; +} + +// TODO: TROPUSRLoader::Save deletes the TROPUSR and creates it again. This is probably very slow. +bool TROPUSRLoader::Save(std::string& filepath) +{ + if (m_file) + Close(); + + if (!Emu.GetVFS().ExistsFile(filepath)) + Emu.GetVFS().CreateFile(filepath); + + m_file = Emu.GetVFS().OpenFile(filepath, vfsWrite); + m_file->Write(&m_header, sizeof(TROPUSRHeader)); + + for (const TROPUSRTableHeader& tableHeader : m_tableHeaders) + m_file->Write(&tableHeader, sizeof(TROPUSRTableHeader)); + + for (const auto& entry : m_table4) + m_file->Write(&entry, sizeof(TROPUSREntry4)); + for (const auto& entry : m_table6) + m_file->Write(&entry, sizeof(TROPUSREntry6)); + + m_file->Close(); + return true; +} + +bool TROPUSRLoader::Generate(std::string& filepath, std::string& configpath) +{ + wxString path; + wxXmlDocument doc; + Emu.GetVFS().GetDevice(configpath.c_str(), path); + doc.Load(path); + + m_table4.clear(); + m_table6.clear(); + for (wxXmlNode *n = doc.GetRoot()->GetChildren(); n; n = n->GetNext()) + { + if (n->GetName() == "trophy") + { + u32 trophy_id = atoi(n->GetAttribute("id").mb_str()); + u32 trophy_grade; + switch (n->GetAttribute("ttype").mb_str()[0]) + { + case 'B': trophy_grade = 4; break; + case 'S': trophy_grade = 3; break; + case 'G': trophy_grade = 2; break; + case 'P': trophy_grade = 1; break; + default: trophy_grade = 0; + } + + TROPUSREntry4 entry4 = {4, sizeof(TROPUSREntry4)-0x10, m_table4.size(), 0, trophy_id, trophy_grade, 0xFFFFFFFF}; + TROPUSREntry6 entry6 = {6, sizeof(TROPUSREntry6)-0x10, m_table6.size(), 0, trophy_id, 0, 0, 0, 0, 0}; + + m_table4.push_back(entry4); + m_table6.push_back(entry6); + } + } + + u64 offset = sizeof(TROPUSRHeader) + 2 * sizeof(TROPUSRTableHeader); + TROPUSRTableHeader table4header = {4, sizeof(TROPUSREntry4)-0x10, 1, m_table4.size(), offset, 0}; + offset += m_table4.size() * sizeof(TROPUSREntry4); + TROPUSRTableHeader table6header = {6, sizeof(TROPUSREntry6)-0x10, 1, m_table6.size(), offset, 0}; + offset += m_table6.size() * sizeof(TROPUSREntry6); + + m_tableHeaders.clear(); + m_tableHeaders.push_back(table4header); + m_tableHeaders.push_back(table6header); + + m_header.magic = 0x818F54AD; + m_header.unk1 = 0x00010000; + m_header.tables_count = m_tableHeaders.size(); + m_header.unk2 = 0; + + Save(filepath); + return true; +} + +u32 TROPUSRLoader::GetTrophiesCount() +{ + return m_table6.size(); +} + +u32 TROPUSRLoader::GetTrophyUnlockState(u32 id) +{ + if (id >= m_table6.size()) + ConLog.Warning("TROPUSRLoader::GetUnlockState: Invalid id=%d", id); + + return m_table6[id].trophy_state; // Let's assume the trophies are stored ordered +} + +u32 TROPUSRLoader::GetTrophyTimestamp(u32 id) +{ + if (id >= m_table6.size()) + ConLog.Warning("TROPUSRLoader::GetTrophyTimestamp: Invalid id=%d", id); + + // TODO: What timestamp does sceNpTrophyGetTrophyInfo want, timestamp1 or timestamp2? + return m_table6[id].timestamp2; // Let's assume the trophies are stored ordered +} + +bool TROPUSRLoader::UnlockTrophy(u32 id, u64 timestamp1, u64 timestamp2) +{ + if (id >= m_table6.size()) + return false; + + m_table6[id].trophy_state = 1; + m_table6[id].timestamp1 = timestamp1; + m_table6[id].timestamp2 = timestamp2; + return true; +} + +bool TROPUSRLoader::Close() +{ + if (m_file && m_file->Close()) + { + m_file = NULL; + return true; + } + return false; +} diff --git a/rpcs3/Loader/TROPUSR.h b/rpcs3/Loader/TROPUSR.h new file mode 100644 index 0000000000..a01fd75230 --- /dev/null +++ b/rpcs3/Loader/TROPUSR.h @@ -0,0 +1,83 @@ +#pragma once + +struct TROPUSRHeader +{ + be_t magic; // 81 8F 54 AD + be_t unk1; + be_t tables_count; + be_t unk2; + char reserved[32]; +}; + +struct TROPUSRTableHeader +{ + be_t type; + be_t entries_size; + be_t unk1; // Seems to be 1 + be_t entries_count; + be_t offset; + be_t reserved; +}; + +struct TROPUSREntry4 +{ + // Entry Header + be_t entry_type; // Always 0x4 + be_t entry_size; // Always 0x50 + be_t entry_id; // Entry ID + be_t entry_unk1; // Just zeroes? + + // Entry Contents + be_t trophy_id; // Trophy ID + be_t trophy_grade; // This seems interesting + be_t unk5; // Seems to be FF FF FF FF + char unk6[68]; // Just zeroes? +}; + +struct TROPUSREntry6 +{ + // Entry Header + be_t entry_type; // Always 6 + be_t entry_size; // Always 0x60 + be_t entry_id; // Entry ID + be_t entry_unk1; // Just zeroes? + + // Entry Contents + be_t trophy_id; // Trophy ID + be_t trophy_state; // Wild guess: 00 00 00 00 = Locked, 00 00 00 01 = Unlocked + be_t unk4; // This seems interesting + be_t unk5; // Just zeroes? + be_t timestamp1; + be_t timestamp2; + char unk6[64]; // Just zeroes? +}; + +class TROPUSRLoader +{ + vfsStream* m_file; + TROPUSRHeader m_header; + std::vector m_tableHeaders; + + std::vector m_table4; + std::vector m_table6; + + virtual bool Generate(std::string& filepath, std::string& configpath); + virtual bool LoadHeader(); + virtual bool LoadTableHeaders(); + virtual bool LoadTables(); + +public: + TROPUSRLoader(); + ~TROPUSRLoader(); + + virtual bool Load(std::string& filepath, std::string& configpath); + virtual bool Save(std::string& filepath); + virtual bool Close(); + + virtual u32 GetTrophiesCount(); + + virtual u32 GetTrophyUnlockState(u32 id); + virtual u32 GetTrophyTimestamp(u32 id); + + virtual bool UnlockTrophy(u32 id, u64 timestamp1, u64 timestamp2); +}; diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index a02f6bf916..bf0d5f4eaf 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -338,6 +338,7 @@ + diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index 7e4015ba55..b70898668a 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -469,6 +469,9 @@ Emu\SysCalls\lv2 + + Loader +