Merge pull request #149 from igor725/trophies

Trophies system
This commit is contained in:
SysRay 2024-05-05 17:15:57 +02:00 committed by GitHub
commit 0e4c31a17c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 1520 additions and 186 deletions

View File

@ -14,7 +14,6 @@
"-fexceptions"
],
"ccls.index.threads": 0,
"cmake.parallelJobs": 0,
"cmake.generator": "Ninja",
"cmake.sourceDirectory": "${workspaceFolder}",
@ -25,15 +24,14 @@
"CMAKE_C_COMPILER": "clang-cl.exe",
"CMAKE_CXX_COMPILER": "clang-cl.exe"
},
"C_Cpp.formatting": "clangFormat",
"C_Cpp.clang_format_style": "file:${workspaceRoot}/.clang-format",
"C_Cpp.clang_format_fallbackStyle": "LLVM",
"C_Cpp.clang_format_path": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\Llvm\\x64\\bin\\clang-format.exe",
"C_Cpp.default.compilerPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\Llvm\\bin\\clang-cl.exe",
"C_Cpp.default.intelliSenseMode": "windows-clang-x64",
"C_Cpp.default.cppStandard": "c++20",
"C_Cpp.autoAddFileAssociations": false,
"editor.tabSize": 2,
"editor.insertSpaces": true,
}

View File

@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.24)
include(ExternalProject)
set(PSOFF_LIB_VERSION v.0.2)
set(PSOFF_LIB_VERSION v.0.3)
set(PSOFF_RENDER_VERSION v.0.5-nightly_04.05.24)
set(ProjectName psOff_${CMAKE_BUILD_TYPE})
@ -117,6 +117,7 @@ add_dependencies(psoff logging core psOff_utility)
target_link_libraries(psoff PRIVATE
core.lib
psOff_utility
libboost_filesystem
$<TARGET_OBJECTS:asmHelper>
)

View File

@ -10,6 +10,7 @@ add_subdirectory(initParams)
add_subdirectory(timer)
add_subdirectory(systemContent)
add_subdirectory(networking)
add_subdirectory(trophies)
add_subdirectory(fileManager)
add_subdirectory(memory)
add_subdirectory(dmem)
@ -23,6 +24,7 @@ add_library(core SHARED
$<TARGET_OBJECTS:timer>
$<TARGET_OBJECTS:systemContent>
$<TARGET_OBJECTS:networking>
$<TARGET_OBJECTS:trophies>
$<TARGET_OBJECTS:videoout>
$<TARGET_OBJECTS:fileManager>
$<TARGET_OBJECTS:memory>
@ -37,6 +39,7 @@ target_link_libraries(core PRIVATE
libboost_thread
libboost_chrono
libboost_program_options
libboost_filesystem
sdl2
psoff_render.lib
gamereport.lib
@ -47,6 +50,9 @@ target_link_libraries(core PRIVATE
IPHLPAPI.lib
wepoll.lib
Ws2_32.lib
libcrypto.lib
libssl.lib
pugixml.lib
ntdll.dll
imgui
VulkanMemoryAllocator

View File

@ -11,6 +11,7 @@
#include "util/moduleLoader.h"
#include "util/plt.h"
#include "util/virtualmemory.h"
#include "utility/progloc.h"
#include "utility/utility.h"
#include <boost/uuid/detail/sha1.hpp>
@ -489,17 +490,17 @@ void RuntimeLinker::loadModules(std::string_view libName) {
LOG_DEBUG(L"Needs library %S", name.data());
// 1/2 Sepcial case: old->new, build filepath
std::string filepath = std::string("modules/");
auto filepath = std::format(L"{}/modules/", util::getProgramLoc());
if (name == "libSceGnmDriver") {
filepath += "libSceGraphicsDriver";
filepath += L"libSceGraphicsDriver";
} else {
filepath += std::string(name);
filepath += std::wstring(name.begin(), name.end());
}
filepath += ".dll";
filepath += L".dll";
//- filepath
if (std::filesystem::exists(filepath) && !m_libHandles.contains(name)) {
LOG_DEBUG(L" load library %S", filepath.c_str());
LOG_DEBUG(L" load library %s", filepath.c_str());
auto [handle, symbols] = loadModule(name.data(), filepath.c_str(), 1);
// 2/2 Sepcial case: old->new
@ -828,7 +829,7 @@ uintptr_t RuntimeLinker::execute() {
// Get and load additional Modules needed
{
for (auto const& prog: m_programList) {
LOG_DEBUG(L"Load for %S", prog.first->filename.string().c_str());
LOG_DEBUG(L"Load for %s", prog.first->filename.c_str());
for (auto const& impLib: prog.second->getImportedLibs()) {
loadModules(impLib.first);
}
@ -853,4 +854,4 @@ uintptr_t RuntimeLinker::execute() {
IRuntimeLinker& accessRuntimeLinker() {
static RuntimeLinker inst;
return inst;
}
}

View File

@ -12,10 +12,10 @@
LOG_DEFINE_MODULE(ModuleLoader);
std::pair<void*, std::unique_ptr<Symbols::SymbolExport>> loadModule(const char* libName, const char* filepath, int libVersion) {
std::pair<void*, std::unique_ptr<Symbols::SymbolExport>> loadModule(const char* libName, const wchar_t* filepath, int libVersion) {
LOG_USE_MODULE(ModuleLoader);
HMODULE hModule = LoadLibrary(filepath);
HMODULE hModule = LoadLibraryW(filepath);
if (hModule == NULL) {
LOG_ERR(L"Couldn't load library %S, err:%d", filepath, GetLastError());
return {};
@ -66,4 +66,4 @@ std::pair<void*, std::unique_ptr<Symbols::SymbolExport>> loadModule(const char*
void unloadModule(void* handle) {
FreeLibrary((HMODULE)handle);
}
}

View File

@ -6,6 +6,6 @@
#include <utility>
#include <vector>
std::pair<void*, std::unique_ptr<Symbols::SymbolExport>> loadModule(const char* name, const char* filepath, int libVersion);
std::pair<void*, std::unique_ptr<Symbols::SymbolExport>> loadModule(const char* name, const wchar_t* filepath, int libVersion);
void unloadModule(void* handle);
void unloadModule(void* handle);

View File

@ -0,0 +1,5 @@
add_library(trophies OBJECT
trophies.cpp
)
add_dependencies(trophies third_party)

768
core/trophies/trophies.cpp Normal file
View File

@ -0,0 +1,768 @@
#define __APICALL_EXTERN
#include "trophies.h"
#undef __APICALL_EXTERN
#include "core/fileManager/fileManager.h"
#include "modules/libSceNpTrophy/types.h"
#include "modules_include/system_param.h"
#include "tools/config_emu/config_emu.h"
#include <boost/scoped_ptr.hpp>
#include <fstream>
#include <openssl/evp.h>
#include <pugixml.hpp>
#define TR_AES_BLOCK_SIZE 16
#undef min // We don't need it there
class Trophies: public ITrophies {
enum class cbtype {
UNKNOWN,
TROPHY_UNLOCK,
};
struct callback {
cbtype type;
vvpfunc func;
};
bool m_bKeySet = false;
bool m_bIgnoreMissingLocale = false;
uint8_t m_trkey[TR_AES_BLOCK_SIZE] = {};
std::string m_localizedTrophyFile;
std::filesystem::path m_trophyInfoPath;
std::filesystem::path m_npidCachePath;
std::vector<callback> m_callbacks = {};
std::mutex m_mutexParse;
private:
struct usr_context {
struct trophy {
int32_t id;
uint32_t re; // reserved
uint64_t ts;
};
bool created;
uint32_t label;
int32_t userId;
std::vector<struct trophy> trophies;
} m_ctx[4];
struct trp_header {
uint32_t magic; // should be 0xDCA24D00
uint32_t version;
uint64_t tfile_size; // size of trp file
uint32_t entry_num; // num of entries
uint32_t entry_size; // size of entry
uint32_t dev_flag; // 1: dev
uint8_t digest[20]; // sha hash
uint32_t key_index;
char padding[44];
};
struct trp_entry {
char name[32];
uint64_t pos;
uint64_t len;
uint32_t flag;
char padding[12];
};
static bool caseequal(char a, char b) { return std::tolower(static_cast<unsigned char>(a)) == std::tolower(static_cast<unsigned char>(b)); }
static ErrCodes XML_parse(const char* mem, trp_context* ctx) {
pugi::xml_document doc;
auto res = doc.load_buffer(mem, strlen(mem));
if (res.status != pugi::xml_parse_status::status_ok) return ErrCodes::INVALID_XML;
if (auto tc = doc.child("trophyconf")) {
if (!ctx->itrop.cancelled) {
ctx->itrop.data.trophyset_version.assign(tc.child("trophyset-version").first_child().text().as_string("[nover]"));
ctx->itrop.data.title_name.assign(tc.child("title-name").first_child().text().as_string("[unnamed]"));
ctx->itrop.data.title_detail.assign(tc.child("title-detail").first_child().text().as_string("[unnamed]"));
ctx->itrop.data.trophy_count = ctx->itrop.data.group_count = 0;
}
for (auto node = tc.child("trophy"); node; node = node.next_sibling("trophy")) {
if (!ctx->entry.cancelled) {
ctx->entry.data.id = node.attribute("id").as_int(-1);
ctx->entry.data.group = node.attribute("gid").as_int(-1);
ctx->entry.data.platinum = node.attribute("pid").as_int(-1);
ctx->entry.data.hidden = node.attribute("hidden").as_bool(false);
ctx->entry.data.grade = std::tolower(*(node.attribute("ttype").as_string()));
if (!ctx->lightweight) {
ctx->entry.data.name.assign(node.child("name").first_child().text().as_string("[unnamed]"));
ctx->entry.data.detail.assign(node.child("detail").first_child().text().as_string("[unnamed]"));
}
ctx->entry.cancelled = ctx->entry.func(&ctx->entry.data);
}
if (!ctx->itrop.cancelled) ++ctx->itrop.data.trophy_count;
}
for (auto node = tc.child("group"); node; node = node.next_sibling("group")) {
if (!ctx->group.cancelled) {
ctx->group.data.id = node.attribute("id").as_int(-1);
if (!ctx->lightweight) {
ctx->group.data.name.assign(node.child("name").first_child().text().as_string("[unnamed]"));
ctx->group.data.detail.assign(node.child("detail").first_child().text().as_string("[unnamed]"));
}
ctx->group.cancelled = ctx->group.func(&ctx->group.data);
}
if (!ctx->itrop.cancelled) ++ctx->itrop.data.group_count;
}
if (!ctx->itrop.cancelled) ctx->itrop.func(&ctx->itrop.data);
ctx->entry.cancelled = true;
ctx->group.cancelled = true;
ctx->itrop.cancelled = true;
return ErrCodes::CONTINUE;
}
return ErrCodes::NO_TROPHIES;
}
ErrCodes TRP_readentry(const trp_entry& ent, trp_entry& dent, std::ifstream& trfile, trp_context* ctx) {
if (!ctx->pngim.cancelled) {
static std::string_view ext(".png");
std::string_view name(ent.name);
if (std::equal(ext.rbegin(), ext.rend(), name.rbegin(), caseequal)) { // Test trp file extension
if (((ent.flag >> 24) & 0x03) == 0) {
ctx->pngim.data.pngsize = ent.len;
ctx->pngim.data.pngname.assign(ent.name);
if ((ctx->pngim.data.pngdata = ::calloc(ent.len, 1)) == nullptr) { // Developer should free this memory manually
return ErrCodes::OUT_OF_MEMORY;
}
trfile.seekg(ent.pos);
if (trfile.read((char*)ctx->pngim.data.pngdata, ent.len)) {
ctx->pngim.cancelled = ctx->pngim.func(&ctx->pngim.data);
return ErrCodes::CONTINUE;
}
return ErrCodes::IO_FAIL;
} else {
// Is this even possible?
return ErrCodes::NOT_IMPLEMENTED;
}
}
}
if (!ctx->group.cancelled || !ctx->entry.cancelled || !ctx->itrop.cancelled) {
static std::string_view ext(".esfm");
std::string_view name(ent.name);
if (!std::equal(ext.rbegin(), ext.rend(), name.rbegin(), caseequal)) return ErrCodes::CONTINUE;
if ((ent.len % 16) != 0) return ErrCodes::INVALID_AES;
if (ctx->lightweight) {
static std::string_view lwfile("tropconf.esfm");
if (!std::equal(lwfile.begin(), lwfile.end(), name.begin(), name.end(), caseequal)) return ErrCodes::CONTINUE;
} else if (!m_bIgnoreMissingLocale) {
static std::string_view dfile("trop.esfm");
if (m_localizedTrophyFile.length() == 0) {
// No localized trophy needed, using the English one
if (!std::equal(name.begin(), name.end(), dfile.begin(), dfile.end(), caseequal)) return ErrCodes::CONTINUE;
} else {
// Trying to find localized trophy
if (!std::equal(name.begin(), name.end(), m_localizedTrophyFile.begin(), m_localizedTrophyFile.end(), caseequal)) {
if (std::equal(name.begin(), name.end(), dfile.begin(), dfile.end(), caseequal)) dent = ent;
return ErrCodes::CONTINUE;
}
}
}
boost::scoped_ptr<char> mem(new char[ent.len]);
trfile.seekg(ent.pos); // Seek to file position
if (((ent.flag >> 24) & 0x03) == 0) {
if (!trfile.read(mem.get(), ent.len)) return ErrCodes::IO_FAIL;
} else {
static constexpr int32_t IV_SIZE = TR_AES_BLOCK_SIZE;
static constexpr int32_t ENC_SCE_SIGN_SIZE = TR_AES_BLOCK_SIZE * 3; // 384 encrypted bits is just enough to find interesting for us string
uint8_t d_iv[TR_AES_BLOCK_SIZE];
uint8_t kg_iv[TR_AES_BLOCK_SIZE];
uint8_t enc_xmlh[ENC_SCE_SIGN_SIZE];
::memset(kg_iv, 0, TR_AES_BLOCK_SIZE);
if (!trfile.read((char*)d_iv, TR_AES_BLOCK_SIZE)) return ErrCodes::IO_FAIL;
if (!trfile.read((char*)enc_xmlh, ENC_SCE_SIGN_SIZE)) return ErrCodes::IO_FAIL;
const auto trydecrypt = [this, &mem, &ent, d_iv, kg_iv, enc_xmlh, &trfile](uint32_t npid) -> bool {
uint8_t outbuffer[512];
uint8_t inbuffer[512];
::memset(outbuffer, 0, 512);
::memset(inbuffer, 0, 512);
::sprintf_s((char*)inbuffer, sizeof(inbuffer), "NPWR%05d_00", npid);
int outlen;
// Key creation context
{
EVP_CIPHER_CTX* key_ctx = EVP_CIPHER_CTX_new();
if (!EVP_EncryptInit(key_ctx, EVP_aes_128_cbc(), m_trkey, kg_iv)) {
EVP_CIPHER_CTX_free(key_ctx);
return false;
}
if (!EVP_EncryptUpdate(key_ctx, outbuffer, &outlen, inbuffer, TR_AES_BLOCK_SIZE)) {
EVP_CIPHER_CTX_free(key_ctx);
return false;
}
/**
* Cipher finalizing is not really necessary there,
* since we use only 16 bytes encrypted by the update function above
*/
EVP_CIPHER_CTX_free(key_ctx);
}
//- Key creation context
// Data decipher context
EVP_CIPHER_CTX* data_ctx = EVP_CIPHER_CTX_new();
{
if (!EVP_DecryptInit(data_ctx, EVP_aes_128_cbc(), outbuffer, d_iv)) {
EVP_CIPHER_CTX_free(data_ctx);
return false;
}
EVP_CIPHER_CTX_set_padding(data_ctx, 0);
if (!EVP_DecryptUpdate(data_ctx, outbuffer, &outlen, enc_xmlh, ENC_SCE_SIGN_SIZE)) {
EVP_CIPHER_CTX_free(data_ctx);
return false;
}
if (::_strnicmp((char*)outbuffer, "<!--Sce-Np-Trophy-Signature", 27) != 0) {
EVP_CIPHER_CTX_free(data_ctx);
return false;
}
// We found valid NPID, now we can continue our thing
::memcpy(mem.get(), outbuffer, outlen);
char* mem_off = mem.get() + outlen;
// Seeking to unread encrypted data position (skip Init Vector + Signature Comkment Part)
trfile.seekg(ent.pos + TR_AES_BLOCK_SIZE + ENC_SCE_SIGN_SIZE);
size_t copied;
while ((copied = size_t(mem_off - mem.get())) < ent.len) {
size_t len = std::min(ent.len - copied, sizeof(inbuffer));
// Reading the rest of AES data block by block
if (!trfile.read((char*)inbuffer, len)) {
EVP_CIPHER_CTX_free(data_ctx);
return false;
}
if (!EVP_DecryptUpdate(data_ctx, (uint8_t*)mem_off, &outlen, inbuffer, len)) {
EVP_CIPHER_CTX_free(data_ctx);
return false;
}
mem_off += outlen;
}
if (!EVP_DecryptFinal(data_ctx, (uint8_t*)mem_off, &outlen)) {
EVP_CIPHER_CTX_free(data_ctx);
return false;
}
EVP_CIPHER_CTX_free(data_ctx);
/**
* OpenSSL AES decrypt garbage data workaround.
* TODO: Fix it actually... EVP_DecryptUpdate writes
* garbage bytes to the buffer after XML file ends for
* some reason. I don't figured out yet why.
*/
auto p = std::string_view(mem.get()).find("</trophyconf>");
if (p != std::string_view::npos) *(mem.get() + p + 13) = '\0';
*mem_off = '\0'; // Finally
}
//- Data decipher context
return true;
}; // lambda: trydecrypt
int npid;
bool success = false;
{
// Trying to obtain cached NPID for this title and use it for trophies decrypting
std::ifstream npid_f(m_npidCachePath);
if (npid_f.is_open()) {
npid_f >> npid;
success = trydecrypt(npid);
}
}
if (!success) { // We failed to decrypt xml with saved NPID, now we search it
for (uint32_t n = 0; n < 99999; n++) {
trfile.seekg(ent.pos + TR_AES_BLOCK_SIZE);
if ((success = trydecrypt(n)) == true) {
npid = n;
break;
}
}
if (success) { // NPID found, saving it to cache file
std::ofstream npid_f(m_npidCachePath, std::ios::out);
if (npid_f.is_open()) {
npid_f << npid;
}
}
}
if (!success) return ErrCodes::DECRYPT;
}
return XML_parse(mem.get(), ctx);
} // group & trophy callbacks
return ErrCodes::CONTINUE;
}
void callUnlockCallback(trp_unlock_data* cbdata) {
for (auto& cb: m_callbacks) {
if (cb.type == cbtype::TROPHY_UNLOCK) cb.func(cbdata);
}
if (cbdata->image.pngdata) {
::free(cbdata->image.pngdata);
cbdata->image.pngdata = nullptr;
cbdata->image.pngsize = 0ull;
}
}
int32_t _unlockTrophyEx(usr_context* ctx, int32_t trophyId, int32_t* platinumId) {
if (!m_bKeySet) return -1;
int32_t platId = getPlatinumIdFor(trophyId);
if (platId == -2) return Err::NpTrophy::PLATINUM_CANNOT_UNLOCK;
if (platId == -3) return Err::NpTrophy::BROKEN_DATA;
auto pngname = std::format("TROP{:3}.PNG", trophyId);
int32_t ret = Ok;
bool platinumMet = (platId >= 0);
trp_unlock_data cbdata = {0};
trp_context tctx = {
.entry =
{
.func = [this, ctx, platId, &platinumMet, &ret, &cbdata, trophyId](trp_ent_cb::data_t* data) -> bool {
if (ret != 0) return true;
if (data->id == trophyId) {
cbdata.id = trophyId;
cbdata.grade = data->grade;
cbdata.name.assign(data->name);
cbdata.descr.assign(data->detail);
} else if (platinumMet && data->grade != 'p' && data->platinum == platId) {
platinumMet = getUnlockTime(ctx->userId, data->id) > 0;
}
if (data->grade == 'p' && data->id == platId) {
cbdata.pname.assign(data->name);
cbdata.descr.assign(data->detail);
}
return false;
},
},
.pngim =
{
.func = [ret, &cbdata, pngname](trp_png_cb::data_t* data) -> bool {
if (data->pngname == pngname) {
cbdata.image.pngdata = data->pngdata;
cbdata.image.pngsize = data->pngsize;
return true;
}
return false;
},
},
};
ErrCodes ec;
if ((ec = parseTRP(&tctx)) != ErrCodes::SUCCESS) {
ret = Err::NpTrophy::BROKEN_DATA;
}
if (ret != Ok) {
if (cbdata.image.pngdata) ::free(cbdata.image.pngdata);
return ret;
}
if (cbdata.id != trophyId) return Err::NpTrophy::INVALID_TROPHY_ID;
if (platinumMet) {
cbdata.platGained = true;
*platinumId = cbdata.platId = platId;
} else {
cbdata.platGained = false;
cbdata.platId = platId;
*platinumId = -1;
}
callUnlockCallback(&cbdata);
return Ok;
}
void checkPlatinumTrophies(int32_t userId) {
if (!m_bKeySet) return;
struct platdata {
int32_t id;
std::string name;
std::string descr;
};
std::vector<platdata> unlocked_plats = {};
trp_context tctx = {
.entry =
{
.func = [this, userId, &unlocked_plats](trp_ent_cb::data_t* data) -> bool {
// Marking all platinum trophies as unlocked to check if they actually unlocked later
if (data->grade == 'p' && getUnlockTime(userId, data->id) == 0ull) {
unlocked_plats.push_back({data->id, data->name, data->detail});
}
return false;
},
},
};
if (parseTRP(&tctx) != ErrCodes::SUCCESS) return;
tctx.entry.func = [this, userId, &unlocked_plats](trp_ent_cb::data_t* data) -> bool {
if (data->grade != 'p' && data->platinum != -1) {
// Some trophy is not unlocked yet, removing the platinum trophy from the unlocked list then
if (getUnlockTime(userId, data->id) == 0ull) {
for (auto it = unlocked_plats.begin(); it != unlocked_plats.end();) {
if (it->id == data->platinum) {
unlocked_plats.erase(it);
break;
}
}
}
}
return false;
};
if (parseTRP(&tctx) != ErrCodes::SUCCESS || unlocked_plats.size() == 0ull) return;
uint64_t uTime = (uint64_t)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::utc_clock::now().time_since_epoch()).count();
for (auto& plat: unlocked_plats) {
trp_unlock_data cbdata = {
.grade = 'p',
.name = plat.name,
.descr = plat.descr,
};
callUnlockCallback(&cbdata);
m_ctx[userId].trophies.push_back({plat.id, 0, uTime});
saveTrophyData(userId);
}
}
public:
Trophies() {
auto [lock, jData] = accessConfig()->accessModule(ConfigModFlag::GENERAL);
std::string hexdata;
if (getJsonParam(jData, "trophyKey", hexdata) && hexdata.length() > 31) {
if (hexdata.starts_with("0x")) hexdata.erase(0, 2);
if (hexdata.find_first_not_of("0123456789abcdefABCDEF") != hexdata.npos) return;
m_bKeySet = true;
for (uint32_t i = 0; i < 32; i += 2) {
auto byte = hexdata.substr(i, 2);
m_trkey[i / 2] = (uint8_t)std::strtol(byte.c_str(), nullptr, 16);
}
}
{
SystemParamLang systemlang;
try {
(*jData)["systemlang"].get_to(systemlang);
} catch (const json::exception& e) {
systemlang = SystemParamLang::EnglishUS;
}
if (systemlang != SystemParamLang::EnglishUS) m_localizedTrophyFile = std::format("TROP_{:02}.ESFM", (uint32_t)systemlang);
}
auto gfd = accessFileManager().getGameFilesDir();
m_trophyInfoPath = gfd / "tropinfo";
m_npidCachePath = gfd / ".npcommid";
}
// Callbacks
void addTrophyUnlockCallback(vvpfunc func) final { m_callbacks.push_back({cbtype::TROPHY_UNLOCK, func}); }
//- Callbacks
ErrCodes parseTRP(trp_context* ctx) final {
if (!m_bKeySet) return ErrCodes::NO_KEY_SET;
if (ctx == nullptr) return ErrCodes::INVALID_CONTEXT;
// We don't want every callback to be cancelled before we even begin
ctx->group.cancelled = (ctx->group.func == nullptr);
ctx->entry.cancelled = (ctx->entry.func == nullptr);
ctx->pngim.cancelled = (ctx->pngim.func == nullptr);
ctx->itrop.cancelled = (ctx->itrop.func == nullptr);
if (ctx->cancelled()) return ErrCodes::NO_CALLBACKS;
std::unique_lock lock(m_mutexParse);
m_bIgnoreMissingLocale = false;
auto mpath = accessFileManager().getMappedPath("/app0/sce_sys/trophy/trophy00.trp");
if (!mpath.has_value()) return ErrCodes::NO_TROPHIES;
std::ifstream trfile(mpath->c_str(), std::ios::in | std::ios::binary);
if (trfile.is_open()) {
trp_header hdr;
if (!trfile.read((char*)&hdr, sizeof(hdr))) return ErrCodes::IO_FAIL;
if (hdr.magic != 0x004DA2DC) return ErrCodes::INVALID_MAGIC;
if (hdr.version != 0x03000000) return ErrCodes::INVALID_VERSION;
/**
* This file's numbers encoded in big-endian for some twisted reason.
* Probably because of format history.
*/
hdr.entry_num = _byteswap_ulong(hdr.entry_num);
hdr.entry_size = _byteswap_ulong(hdr.entry_size);
if (hdr.entry_size != sizeof(trp_entry)) return ErrCodes::INVALID_ENTSIZE;
trp_entry ent, dent = {0};
for (uint32_t i = 0; i < hdr.entry_num; ++i) {
if (ctx->cancelled()) return ErrCodes::SUCCESS;
trfile.seekg(sizeof(trp_header) + (sizeof(trp_entry) * i));
if (!trfile.read((char*)&ent, sizeof(trp_entry))) return ErrCodes::IO_FAIL;
ent.pos = _byteswap_uint64(ent.pos);
ent.len = _byteswap_uint64(ent.len);
auto ret = TRP_readentry(ent, dent, trfile, ctx);
if (ret != ErrCodes::CONTINUE) return ret;
} // entries loop
// Group or trophy callback is not cancelled yet, looks like we missed localized esfm file, trying to use the default one
if (!ctx->group.cancelled || !ctx->entry.cancelled || !ctx->itrop.cancelled) {
m_bIgnoreMissingLocale = true;
if (dent.len != 0) {
auto ret = TRP_readentry(dent, dent, trfile, ctx);
if (ret != ErrCodes::CONTINUE) return ret;
}
}
return ErrCodes::SUCCESS;
} // trfile.is_open()
return ErrCodes::NO_TROPHIES;
}
const char* getError(ErrCodes ec) final {
switch (ec) {
case ErrCodes::SUCCESS: return "No errors";
case ErrCodes::INVALID_CONTEXT: return "Passed context is nullptr";
case ErrCodes::OUT_OF_MEMORY: return "Failed to allocate data array";
case ErrCodes::NO_KEY_SET: return "Invalid trophy key, decrypting is impossible";
case ErrCodes::INVALID_MAGIC: return "Invalid trophy file magic, trophy00.trp is likely corruted";
case ErrCodes::INVALID_VERSION: return "Unsupported trophy file version";
case ErrCodes::INVALID_ENTSIZE: return "Invalid trophy file entry size, trophy00.trp is likely corruted";
case ErrCodes::INVALID_AES: return "Trophy file contains unaligned AES blocks";
case ErrCodes::INVALID_XML: return "TPR file contains invalid XML data, can't parse it";
case ErrCodes::NOT_IMPLEMENTED: return "This feature is not implemented yet";
case ErrCodes::IO_FAIL: return "Your operating system reported IO failure";
case ErrCodes::NO_CALLBACKS: return "No callbacks passed to parser";
case ErrCodes::DECRYPT: return "Trophy file decryption failed";
case ErrCodes::NO_TROPHIES: return "Trophy file is likely missing or does not contain requested esfm file";
case ErrCodes::MAX_TROPHY_REACHED: return "The game hit the hard limit of 128 trophies";
default: return "Unknown error code!";
}
}
int32_t loadTrophyData(int32_t userId) {
if (userId < 1 || userId > 4) return Err::NpTrophy::INVALID_CONTEXT;
auto& ctx = m_ctx[userId];
if (!ctx.created) return Err::NpTrophy::INVALID_CONTEXT;
std::ifstream file(m_trophyInfoPath, std::ios::in | std::ios::binary);
if (file.is_open()) {
uint32_t tc = 0;
if (!file.read((char*)&tc, 4)) return Err::NpTrophy::UNKNOWN;
for (uint32_t i = 0; i < tc; i++) {
usr_context::trophy t;
if (!file.read((char*)&t.id, 4)) return Err::NpTrophy::UNKNOWN;
if (!file.read((char*)&t.ts, 8)) return Err::NpTrophy::UNKNOWN;
ctx.trophies.push_back(t);
}
}
return Ok;
}
int32_t saveTrophyData(int32_t userId) {
if (userId < 1 || userId > 4) return false;
auto& ctx = m_ctx[userId];
if (!ctx.created) return false;
std::ofstream file(m_trophyInfoPath, std::ios::out | std::ios::binary);
if (file.is_open()) {
uint32_t num = ctx.trophies.size();
if (!file.write((char*)&num, 4)) return Err::NpTrophy::INSUFFICIENT_SPACE;
for (auto& trop: ctx.trophies) {
if (!file.write((char*)&trop.id, 4)) return Err::NpTrophy::INSUFFICIENT_SPACE;
if (!file.write((char*)&trop.ts, 8)) return Err::NpTrophy::INSUFFICIENT_SPACE;
}
return true;
}
return false;
}
int32_t createContext(int32_t userId, uint32_t label) final {
if (userId < 1 || userId > 4) return Err::NpTrophy::INVALID_USER_ID;
auto& ctx = m_ctx[userId];
if (ctx.created) return Err::NpTrophy::CONTEXT_ALREADY_EXISTS;
ctx.userId = userId;
ctx.created = true;
ctx.label = label;
int32_t errcode;
if ((errcode = loadTrophyData(userId)) == Ok) {
checkPlatinumTrophies(userId);
return Ok;
}
return errcode;
}
int32_t destroyContext(int32_t userId) final {
if (userId < 1 || userId > 4) return Err::NpTrophy::INVALID_CONTEXT;
auto& ctx = m_ctx[userId];
if (!ctx.created) return Err::NpTrophy::INVALID_CONTEXT;
if (!saveTrophyData(userId)) return Err::NpTrophy::INSUFFICIENT_SPACE;
ctx.created = false;
ctx.label = 0;
return Ok;
}
int32_t getProgress(int32_t userId, uint32_t progress[4], uint32_t* count) final {
if (userId < 1 || userId > 4) return Err::NpTrophy::INVALID_CONTEXT;
auto& ctx = m_ctx[userId];
if (!ctx.created) return Err::NpTrophy::INVALID_CONTEXT;
if (count != nullptr) {
trp_context ctx = {
.lightweight = true,
.itrop =
{
.func = [count](trp_inf_cb::data_t* data) -> bool {
*count = data->trophy_count;
return true;
},
},
};
if (parseTRP(&ctx) != ErrCodes::SUCCESS) return Err::NpTrophy::BROKEN_DATA;
}
SCE_NP_TROPHY_FLAG_ZERO((SceNpTrophyFlagArray*)progress);
for (auto& trop: ctx.trophies) {
SCE_NP_TROPHY_FLAG_SET(trop.id, (SceNpTrophyFlagArray*)progress);
}
return true;
}
uint64_t getUnlockTime(int32_t userId, int32_t trophyId) final {
if (userId < 1 || userId > 4) return 0ull;
auto& ctx = m_ctx[userId];
if (!ctx.created) return false;
for (auto& trop: ctx.trophies) {
if (trop.id == trophyId) return trop.ts;
}
return 0ull;
}
int32_t getPlatinumIdFor(int32_t trophyId) {
if (trophyId < 0 || trophyId > 128) return -3;
int32_t platId = -1;
trp_context ctx = {
.lightweight = true,
.entry =
{
.func = [&platId, trophyId](trp_ent_cb::data_t* data) -> bool {
if (data->id == trophyId) {
platId = (data->grade == 'p') ? -2 : data->platinum;
return true;
}
return false;
},
},
};
if (parseTRP(&ctx) != ErrCodes::SUCCESS) platId = -3;
return platId;
}
int32_t unlockTrophy(int32_t userId, int32_t trophyId, int32_t* platinumId) final {
if (userId < 1 || userId > 4) return Err::NpTrophy::INVALID_CONTEXT;
auto& ctx = m_ctx[userId];
if (!ctx.created) return Err::NpTrophy::INVALID_CONTEXT;
if (getUnlockTime(ctx.userId, trophyId) != 0ull) return Err::NpTrophy::ALREADY_UNLOCKED;
*platinumId = -1;
int32_t errcode;
if ((errcode = _unlockTrophyEx(&ctx, trophyId, platinumId)) != Ok) {
if (errcode == -1) { // No trophy decryption key
trp_unlock_data cbdata = {
.grade = 'b',
.name = "Unknown trophy",
.descr = "No trophy key specified, can't decrypt the trophy data",
};
callUnlockCallback(&cbdata);
errcode = Ok;
}
}
uint64_t uTime = (uint64_t)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::utc_clock::now().time_since_epoch()).count();
ctx.trophies.push_back({trophyId, 0, uTime});
if (*platinumId != -1) ctx.trophies.push_back({*platinumId, 0, uTime});
return saveTrophyData(userId) ? errcode : Err::NpTrophy::INSUFFICIENT_SPACE;
}
bool resetUserInfo(int32_t userId) final { return false; }
};
ITrophies& accessTrophies() {
static Trophies ti;
return ti;
}

143
core/trophies/trophies.h Normal file
View File

@ -0,0 +1,143 @@
#pragma once
#include "utility/utility.h"
#include <cstdint>
#include <functional>
#include <string>
typedef std::function<void(const void*)> vvpfunc;
class ITrophies {
CLASS_NO_COPY(ITrophies);
CLASS_NO_MOVE(ITrophies);
protected:
ITrophies() = default;
public:
virtual ~ITrophies() = default;
enum class ErrCodes {
SUCCESS = 0, // No errors, we're fine
INVALID_CONTEXT, // Context is nullptr
OUT_OF_MEMORY, // Failed to allocate data array
CONTINUE, // Not an actual error code. For internal usage only!
NO_KEY_SET, // No root key installed
INVALID_MAGIC, // TRP file has invalid magic in its header
INVALID_VERSION, // TRP file version is not valid
INVALID_ENTSIZE, // TRP file has bigger entries
INVALID_AES, // TRP file contains unaligned AES blocks
INVALID_XML, // TPR file contains invalid XML data, can't parse it
NOT_IMPLEMENTED, // This feature is not implemented yet
IO_FAIL, // Failed to read TRP file
NO_CALLBACKS, // Parser called with no callbacks, it's pointless
DECRYPT, // TRP file decryption failed
NO_TROPHIES, // Failed to open TRP file or the said file does not contain any esfm file
MAX_TROPHY_REACHED, // The game hit the hard limit of 128 trophies
};
struct trp_grp_cb {
struct data_t {
int32_t id;
std::string name;
std::string detail;
} data;
bool cancelled;
std::function<bool(data_t*)> func;
};
struct trp_ent_cb {
struct data_t {
int32_t id;
int32_t group;
int32_t platinum;
bool hidden;
uint8_t grade;
std::string name;
std::string detail;
} data;
bool cancelled;
std::function<bool(data_t*)> func;
};
struct trp_png_cb {
struct data_t {
std::string pngname;
void* pngdata;
size_t pngsize;
} data;
bool cancelled;
std::function<bool(data_t*)> func;
};
struct trp_inf_cb {
struct data_t {
std::string title_name;
std::string title_detail;
std::string trophyset_version;
uint32_t trophy_count;
uint32_t group_count;
} data;
bool cancelled;
std::function<bool(data_t*)> func;
};
struct trp_context {
bool lightweight;
trp_ent_cb entry = {.data = {}, .cancelled = false, .func = nullptr};
trp_grp_cb group = {.data = {}, .cancelled = false, .func = nullptr};
trp_png_cb pngim = {.data = {}, .cancelled = false, .func = nullptr};
trp_inf_cb itrop = {.data = {}, .cancelled = false, .func = nullptr};
inline bool cancelled() { return entry.cancelled && group.cancelled && pngim.cancelled && itrop.cancelled; }
};
struct trp_unlock_data {
struct image {
void* pngdata;
size_t pngsize;
} image;
int32_t id;
int32_t platId;
bool platGained;
uint8_t grade;
std::string name;
std::string descr;
std::string pname;
std::string pdescr;
};
virtual ErrCodes parseTRP(trp_context* context) = 0;
virtual const char* getError(ErrCodes ec) = 0;
// Callbacks
virtual void addTrophyUnlockCallback(vvpfunc func) = 0;
//- Callbacks
virtual int32_t createContext(int32_t userId, uint32_t label) = 0;
virtual int32_t destroyContext(int32_t userId) = 0;
virtual int32_t getProgress(int32_t userId, uint32_t progress[4], uint32_t* count) = 0;
virtual uint64_t getUnlockTime(int32_t userId, int32_t trophyId) = 0;
virtual int32_t unlockTrophy(int32_t userId, int32_t trophyId, int32_t* platinumId) = 0;
virtual bool resetUserInfo(int32_t userId) = 0;
};
#if defined(__APICALL_EXTERN)
#define __APICALL __declspec(dllexport)
#elif defined(__APICALL_IMPORT)
#define __APICALL __declspec(dllimport)
#else
#define __APICALL
#endif
__APICALL ITrophies& accessTrophies();
#undef __APICALL

View File

@ -6,6 +6,7 @@ add_library(videoout OBJECT
vulkan/vulkanHelper.cpp
overlay/overlay.cpp
overlay/overtrophy/overtrophy.cpp
)
add_dependencies(videoout third_party psOff_utility gamereport initParams config_emu psoff_render)
@ -13,4 +14,4 @@ add_dependencies(videoout third_party psOff_utility gamereport initParams config
target_include_directories(videoout PRIVATE
${Vulkan_INCLUDE_DIRS}
${CMAKE_BINARY_DIR}/third_party/src/third_party/include
)
)

View File

@ -1,6 +1,7 @@
#include "imageHandler.h"
#include "core/initParams/initParams.h"
#include "core/timer/timer.h"
#include "logging.h"
#include "vulkan/vulkanSetup.h"
@ -66,6 +67,8 @@ class ImageHandler: public IImageHandler {
void notify_done(ImageData const&) final;
void calc_fps(uint64_t proctime) final;
VkSwapchainKHR getSwapchain() const final { return m_swapchain; }
};
@ -220,6 +223,13 @@ void ImageHandler::notify_done(ImageData const& imageData) {
m_present_condVar.notify_all();
}
void ImageHandler::calc_fps(uint64_t proctime) {
auto& timer = accessTimer();
auto const curTime = (uint64_t)(1e6 * timer.getTimeS());
auto elapsed_us = curTime - proctime;
m_fps = (m_fps * 5.0 + (1e6 / (double)elapsed_us)) / 6.0;
}
void ImageHandler::init(vulkan::VulkanObj* obj, VkSurfaceKHR surface) {
LOG_USE_MODULE(ImageHandler);
@ -364,4 +374,4 @@ void ImageHandler::deinit() {
if (m_swapchain != nullptr) vkDestroySwapchainKHR(m_deviceInfo->device, m_swapchain, nullptr);
printf("deinit ImageHandler| done\n");
}
}

View File

@ -52,6 +52,8 @@ class IImageHandler {
ImageHandlerCB* m_callback;
double m_fps;
public:
virtual ~IImageHandler() = default;
@ -61,6 +63,8 @@ class IImageHandler {
auto getQueue() const { return m_queue; }
auto getFPS() const { return m_fps; }
virtual void init(vulkan::VulkanObj* obj, VkSurfaceKHR surface) = 0;
virtual void deinit() = 0;
@ -68,9 +72,10 @@ class IImageHandler {
virtual std::optional<ImageData> getImage_blocking() = 0;
virtual void notify_done(ImageData const&) = 0;
virtual void calc_fps(uint64_t proctime) = 0;
virtual VkSwapchainKHR getSwapchain() const = 0;
};
std::unique_ptr<IImageHandler> createImageHandler(std::shared_ptr<vulkan::DeviceInfo>& deviceInfo, VkExtent2D extentWindow, vulkan::QueueInfo* queue,
ImageHandlerCB* callback);
ImageHandlerCB* callback);

View File

@ -1,7 +1,7 @@
#include "overlay.h"
#include "../imageHandler.h"
#include "logging.h"
#include "overtrophy/overtrophy.h"
#include <imgui/imgui.h>
#include <imgui/imgui_impl_sdl2.h>
@ -12,6 +12,7 @@ LOG_DEFINE_MODULE(Overlay);
class OverlayHandler: public IOverlayHandler {
std::shared_ptr<vulkan::DeviceInfo> m_deviceInfo;
std::shared_ptr<IImageHandler> m_imageHandler;
VkDescriptorPool m_descriptorPool;
@ -21,8 +22,9 @@ class OverlayHandler: public IOverlayHandler {
void draw();
public:
OverlayHandler(std::shared_ptr<vulkan::DeviceInfo>& deviceInfo, SDL_Window* window, vulkan::QueueInfo* queue, VkFormat displayFormat)
: m_deviceInfo(deviceInfo) {
OverlayHandler(std::shared_ptr<vulkan::DeviceInfo>& deviceInfo, std::shared_ptr<IImageHandler>& imagehandler, SDL_Window* window, vulkan::QueueInfo* queue,
VkFormat displayFormat)
: m_deviceInfo(deviceInfo), m_imageHandler(imagehandler) {
init(window, queue, displayFormat);
}
@ -38,15 +40,15 @@ class OverlayHandler: public IOverlayHandler {
void submit(ImageData const& imageData) final;
void processEvent(SDL_Event const* event) final {
[[unlikely]] if (!m_isInit || !m_isStop)
[[unlikely]] if (!m_isInit || m_isStop)
return;
ImGui_ImplSDL2_ProcessEvent(event);
}
};
std::unique_ptr<IOverlayHandler> createOverlay(std::shared_ptr<vulkan::DeviceInfo>& deviceInfo, SDL_Window* window, vulkan::QueueInfo* queue,
VkFormat displayFormat) {
return std::make_unique<OverlayHandler>(deviceInfo, window, queue, displayFormat);
std::unique_ptr<IOverlayHandler> createOverlay(std::shared_ptr<vulkan::DeviceInfo>& deviceInfo, std::shared_ptr<IImageHandler>& imagehandler,
SDL_Window* window, vulkan::QueueInfo* queue, VkFormat displayFormat) {
return std::make_unique<OverlayHandler>(deviceInfo, imagehandler, window, queue, displayFormat);
}
void OverlayHandler::init(SDL_Window* window, vulkan::QueueInfo* queue, VkFormat displayFormat) {
@ -85,6 +87,8 @@ void OverlayHandler::init(SDL_Window* window, vulkan::QueueInfo* queue, VkFormat
ImGuiIO& io = ImGui::GetIO();
io.IniFilename = nullptr;
io.LogFilename = nullptr;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
@ -120,6 +124,7 @@ void OverlayHandler::init(SDL_Window* window, vulkan::QueueInfo* queue, VkFormat
};
ImGui_ImplVulkan_Init(&initInfo);
(void)accessTrophyOverlay(); // Should be called there to avoid initialization inside NewFrame()
m_isInit = true;
}
@ -131,7 +136,8 @@ void OverlayHandler::submit(ImageData const& imageData) {
ImGui_ImplVulkan_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
draw();
ImGui::EndFrame();
ImGui::Render();
ImDrawData* drawData = ImGui::GetDrawData();
@ -162,4 +168,5 @@ void OverlayHandler::submit(ImageData const& imageData) {
void OverlayHandler::draw() {
// ImGui::ShowDemoWindow();
}
accessTrophyOverlay().draw(m_imageHandler->getFPS());
}

View File

@ -1,4 +1,5 @@
#pragma once
#include "../imageHandler.h"
#include "../vulkan/vulkanSetup.h"
#include "core/videoout/vulkan/vulkanTypes.h"
#include "utility/utility.h"
@ -24,5 +25,5 @@ class IOverlayHandler {
virtual void processEvent(SDL_Event const* event) = 0;
};
std::unique_ptr<IOverlayHandler> createOverlay(std::shared_ptr<vulkan::DeviceInfo>& deviceInfo, SDL_Window* window, vulkan::QueueInfo* queue,
VkFormat displayFormat);
std::unique_ptr<IOverlayHandler> createOverlay(std::shared_ptr<vulkan::DeviceInfo>& deviceInfo, std::shared_ptr<IImageHandler>& imageHandler,
SDL_Window* window, vulkan::QueueInfo* queue, VkFormat displayFormat);

View File

@ -0,0 +1,142 @@
#include "overtrophy.h"
#include "core/trophies/trophies.h"
#include <imgui/imgui.h>
class OverTrophy: public IOverTrophy {
struct notify {
float slide = 1.0f;
float timer = 5.0f;
bool animdone = false;
uint8_t grade;
std::string title;
std::string subtext;
void* pngdata;
size_t pngsize;
~notify() {
if (pngdata != nullptr) ::free(pngdata);
}
};
std::vector<notify> m_notifications = {};
ImFont* m_defaultFont;
ImFont* m_titleFont;
ImFont* m_textFont;
ImFont* CreateFont(float px) {
ImGuiIO& io = ImGui::GetIO();
ImFontConfig cfg;
cfg.SizePixels = px;
cfg.OversampleH = cfg.OversampleV = 1;
cfg.PixelSnapH = true;
static const ImWchar ranges[] = {
0x0020, // Range start
0xFFFC, // Range end
0x0000,
};
cfg.GlyphRanges = ranges;
return io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\tahoma.ttf", px, &cfg);
}
public:
OverTrophy() {
m_defaultFont = CreateFont(15);
m_titleFont = CreateFont(24);
m_textFont = CreateFont(17);
accessTrophies().addTrophyUnlockCallback([this](const void* data) {
auto unlock = (ITrophies::trp_unlock_data*)data;
addNotify(unlock->grade, unlock->name, unlock->descr);
if (unlock->platGained) addNotify('p', unlock->pname, unlock->pdescr);
});
}
void addNotify(uint8_t grade, std::string title, std::string details) {
m_notifications.emplace_back(0.0f, 5.0f, false, grade, std::move(title), std::move(details), nullptr, 0ull);
}
void draw(double fps) final {
fps = std::max(1.0, fps);
const double delta = (1 / fps);
const float dsw = ImGui::GetIO().DisplaySize.x;
const float slidemod = delta * 2.78f;
float wpos = 0.0f;
for (auto it = m_notifications.begin(); it != m_notifications.end();) {
auto& notify = *it;
if (notify.animdone == false) {
if (notify.slide < 1.0f) {
notify.slide += slidemod;
} else if (notify.slide > 1.0f) {
notify.animdone = true;
notify.slide = 1.0f;
}
} else {
if ((notify.timer -= delta) < 0.0f) {
if (notify.slide > 0.0f) {
notify.slide -= slidemod;
} else {
it = m_notifications.erase(it);
continue;
}
}
}
const char* title;
switch (notify.grade) {
case 'b': {
title = "Bronze trophy unlocked!";
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(199, 124, 48, 255));
} break;
case 's': {
title = "Silver trophy unlocked!";
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(150, 164, 182, 255));
} break;
case 'g': {
title = "Gold trophy unlocked!";
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(228, 194, 78, 255));
} break;
case 'p': {
title = "Platinum trophy unlocked!";
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(191, 191, 191, 255));
} break;
default: {
title = "New trophy unlocked!";
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(199, 124, 48, 255));
} break;
}
ImGui::PushFont(m_defaultFont);
ImGui::SetNextWindowPos(ImVec2(dsw - 360 * notify.slide, wpos + 10.0f));
ImGui::SetNextWindowSize(ImVec2(350, 0));
ImGui::Begin(title, nullptr,
ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoFocusOnAppearing);
ImGui::PopStyleColor();
ImGui::PushFont(m_titleFont);
ImGui::Text("%s", notify.title.c_str());
ImGui::PopFont();
ImGui::PushFont(m_textFont);
ImGui::TextWrapped("%s", notify.subtext.c_str());
ImGui::PopFont();
wpos += ImGui::GetWindowHeight() + 10.0f;
ImGui::End();
ImGui::PopFont();
++it;
}
}
};
IOverTrophy& accessTrophyOverlay() {
static OverTrophy ot;
return ot;
}

View File

@ -0,0 +1,16 @@
#pragma once
#include "utility/utility.h"
class IOverTrophy {
CLASS_NO_COPY(IOverTrophy);
CLASS_NO_MOVE(IOverTrophy);
public:
IOverTrophy() = default;
virtual ~IOverTrophy() = default;
virtual void draw(double fps) = 0;
};
IOverTrophy& accessTrophyOverlay();

View File

@ -220,7 +220,7 @@ class VideoOut: public IVideoOut, private IEventsGraphics {
std::unique_ptr<IGraphics> m_graphics;
std::unique_ptr<IImageHandler> m_imageHandler;
std::shared_ptr<IImageHandler> m_imageHandler;
std::unique_ptr<IOverlayHandler> m_overlayHandler;
std::thread m_threadSDL2;
@ -992,7 +992,7 @@ std::thread VideoOut::createSDLThread() {
m_imageHandler->init(m_vulkanObj, window.surface);
auto [format, _] = vulkan::getDisplayFormat(m_vulkanObj);
m_overlayHandler = createOverlay(m_vulkanObj->deviceInfo, window.window, queue, format);
m_overlayHandler = createOverlay(m_vulkanObj->deviceInfo, m_imageHandler, window.window, queue, format);
*item.result = 0;
} else {
@ -1019,18 +1019,12 @@ std::thread VideoOut::createSDLThread() {
lock.lock();
}
m_graphics->submitDone();
auto& timer = accessTimer();
auto const curTime = (uint64_t)(1e6 * timer.getTimeS());
auto const procTime = timer.queryPerformance();
auto elapsed_us = curTime - flipStatus.processTime;
m_imageHandler->calc_fps(flipStatus.processTime);
doFlip(window, 1 + handleIndex);
double const fps = (window.config.fps * 5.0 + (1e6 / (double)elapsed_us)) / 6.0;
auto title = getTitle(1 + handleIndex, flipStatus.count, round(fps), window.fliprate);
window.config.fps = fps;
window.config.fps = m_imageHandler->getFPS();
auto title = getTitle(1 + handleIndex, flipStatus.count, round(window.config.fps), window.fliprate);
SDL_SetWindowTitle(window.window, title.c_str());
func_pollSDL(window.window);

View File

@ -63,6 +63,12 @@
"description": "Index of the user who launched the game, should be less than \"onlineUsers\" or equal.",
"minimum": 1,
"maximum": 4
},
"trophyKey": {
"type": "string",
"description": "ERK trophy key in hex format. You should dump it from your console, otherwise trophies will not work.",
"pattern": "^(0x)?[0-9A-Fa-f]*$",
"maximum": 32
}
},
"required": [
@ -70,6 +76,7 @@
"netMAC",
"profiles",
"systemlang",
"userIndex"
"userIndex",
"trophyKey"
]
}

View File

@ -73,6 +73,7 @@ You can change the layout now in _controls.json_, if you want to.
"systemlang": 0, // System langauge, see the list below to get these indexes
"netEnabled": false, // Wether enable networking features or not. Better to keep it disabled, not ready for the actual use.
"netMAC": "00:00:00:00:00:00", // Your ethernet adapter's MAC address. Zero-MAC means "first usable device".
"trophyKey": "", // ERK trophy key in hex format. You should dump it from your console, otherwise trophies will not work.
"onlineUsers": 1, // Number of authorized users, 1..4
"userIndex": 1, // Id of the user that started the game
"profiles": [ // User profiles, you can change your name there

View File

@ -10,6 +10,7 @@
#include "core/timer/timer.h"
#include "core/videoout/videoout.h"
#include "logging.h"
#include "utility/progloc.h"
#include <chrono>
#include <filesystem>
@ -113,11 +114,12 @@ int main(int argc, char** argv) {
// -
std::filesystem::path const dirSaveFiles = [&] {
auto root = util::getProgramLoc();
auto value = systemContent.getString("TITLE_ID");
if (!value) {
return std::filesystem::path("GAMEFILES") / dirRoot;
return root / std::filesystem::path("GAMEFILES") / dirRoot;
}
return std::filesystem::path("GAMEFILES") / *value;
return root / std::filesystem::path("GAMEFILES") / *value;
}();
fileManager.setGameFilesDir(dirSaveFiles);

View File

@ -6,4 +6,7 @@ project(${libName})
add_library(${libName} SHARED entry.cpp)
setupModule(${libName})
add_dependencies(${libName} core)
target_link_libraries(${libName} PRIVATE core.lib)
setupModule(${libName})

View File

@ -2,18 +2,39 @@
#include <stdint.h>
namespace Err {
constexpr int32_t SCE_NP_TROPHY_INVALID_HANDLE = (-1);
constexpr int32_t SCE_NP_TROPHY_INVALID_CONTEXT = (-1);
constexpr int32_t SCE_NP_TROPHY_INVALID_TROPHY_ID = (-1);
constexpr int32_t SCE_NP_TROPHY_INVALID_GROUP_ID = (-2);
constexpr int32_t SCE_NP_TROPHY_BASE_GAME_GROUP_ID = (-1);
namespace NpTrophy {
constexpr int32_t SCREENSHOT_DISABLED = -2141907413;
constexpr int32_t INVALID_USER_ID = -2141907419;
constexpr int32_t EXCEEDS_MAX = -2141907422;
constexpr int32_t INVALID_NP_TITLE_ID = -2141907424;
constexpr int32_t ICON_FILE_NOT_FOUND = -2141907436;
constexpr int32_t CONTEXT_ALREADY_EXISTS = -2141907437;
constexpr int32_t INSUFFICIENT_SPACE = -2141907438;
constexpr int32_t BROKEN_DATA = -2141907439;
constexpr int32_t PLATINUM_CANNOT_UNLOCK = -2141907443;
constexpr int32_t ALREADY_UNLOCKED = -2141907444;
constexpr int32_t INVALID_GROUP_ID = -2141907445;
constexpr int32_t INVALID_TROPHY_ID = -2141907446;
constexpr int32_t INVALID_CONTEXT = -2141907447;
constexpr int32_t INSUFFICIENT_BUFFER = -2141907451;
constexpr int32_t INVALID_ARGUMENT = -2141907452;
constexpr int32_t UNKNOWN = -2141907456;
} // namespace NpTrophy
} // namespace Err
constexpr uint32_t SCE_NP_TROPHY_NUM_MAX = (128);
constexpr uint32_t SCE_NP_TROPHY_SCREENSHOT_TARGET_NUM_MAX = (4);
constexpr uint32_t SCE_NP_TROPHY_GAME_TITLE_MAX_SIZE = (128);
constexpr uint32_t SCE_NP_TROPHY_GAME_DESCR_MAX_SIZE = (1024);
constexpr uint32_t SCE_NP_TROPHY_GROUP_TITLE_MAX_SIZE = (128);
constexpr uint32_t SCE_NP_TROPHY_GROUP_DESCR_MAX_SIZE = (1024);
constexpr uint32_t SCE_NP_TROPHY_NAME_MAX_SIZE = (128);
constexpr uint32_t SCE_NP_TROPHY_DESCR_MAX_SIZE = (1024);
namespace NpTrophy {
constexpr int32_t INVALID_HANDLE = -1;
constexpr int32_t INVALID_CONTEXT = -1;
constexpr int32_t INVALID_TROPHY_ID = -1;
constexpr int32_t INVALID_GROUP_ID = -2;
constexpr int32_t BASE_GAME_GROUP_ID = -1;
constexpr uint32_t NUM_MAX = (128);
constexpr uint32_t SCREENSHOT_TARGET_NUM_MAX = (4);
constexpr uint32_t GAME_TITLE_MAX_SIZE = (128);
constexpr uint32_t GAME_DESCR_MAX_SIZE = (1024);
constexpr uint32_t GROUP_TITLE_MAX_SIZE = (128);
constexpr uint32_t GROUP_DESCR_MAX_SIZE = (1024);
constexpr uint32_t NAME_MAX_SIZE = (128);
constexpr uint32_t DESCR_MAX_SIZE = (1024);
} // namespace NpTrophy

View File

@ -1,150 +1,321 @@
#include "../libSceNpManager/types.h"
#include "common.h"
#include "core/fileManager/fileManager.h"
#include "core/trophies/trophies.h"
#include "logging.h"
#include "types.h"
#include <fstream>
LOG_DEFINE_MODULE(libSceNpTrophy);
namespace {} // namespace
namespace {
static int32_t searchForPng(SceNpTrophyContext context, const char* name, void* buffer, size_t* size) {
int32_t errcode = Err::NpTrophy::ICON_FILE_NOT_FOUND;
ITrophies::trp_context ctx = {
.pngim =
{
.func = [&errcode, name, buffer, size](ITrophies::trp_png_cb::data_t* data) -> bool {
if (data->pngname == name) {
if (data->pngsize <= *size) {
::memcpy(buffer, data->pngdata, data->pngsize);
*size = data->pngsize;
errcode = Ok;
} else {
LOG_USE_MODULE(libSceNpTrophy);
LOG_ERR(L"Specified buffer is insufficient to save a PNG image (g: %llu, e: %llu)!", *size, data->pngsize);
errcode = Err::NpTrophy::INSUFFICIENT_BUFFER;
*size = 0;
}
::free(data->pngdata);
return true;
}
return false;
},
},
};
if (accessTrophies().parseTRP(&ctx) != ITrophies::ErrCodes::SUCCESS) errcode = Err::NpTrophy::BROKEN_DATA;
return errcode;
}
} // namespace
extern "C" {
EXPORT const char* MODULE_NAME = "libSceNpTrophy";
// Unused handles API
EXPORT SYSV_ABI int sceNpTrophyCreateHandle(SceNpTrophyHandle* handle) {
*handle = 1;
return Ok;
}
EXPORT SYSV_ABI int sceNpTrophyDestroyHandle(SceNpTrophyHandle handle) {
EXPORT SYSV_ABI int sceNpTrophyDestroyHandle(SceNpTrophyHandle) {
return Ok;
}
EXPORT SYSV_ABI int sceNpTrophyAbortHandle(SceNpTrophyHandle handle) {
EXPORT SYSV_ABI int sceNpTrophyAbortHandle(SceNpTrophyHandle) {
return Ok;
}
//- Unused handles API
EXPORT SYSV_ABI int sceNpTrophyCreateContext(SceNpTrophyContext* context, int32_t userId, SceNpServiceLabel serviceLabel, uint64_t options) {
*context = 1;
return Ok;
*context = userId;
return accessTrophies().createContext(userId, (uint32_t)serviceLabel);
}
EXPORT SYSV_ABI int sceNpTrophyDestroyContext(SceNpTrophyContext context) {
return accessTrophies().destroyContext(context);
}
// We don't need any registration
EXPORT SYSV_ABI int sceNpTrophyRegisterContext(SceNpTrophyContext context, SceNpTrophyHandle, uint64_t options) {
return Ok;
}
EXPORT SYSV_ABI int sceNpTrophyRegisterContext(SceNpTrophyContext context, SceNpTrophyHandle handle, uint64_t options) {
return Ok;
EXPORT SYSV_ABI int sceNpTrophyUnlockTrophy(SceNpTrophyContext context, SceNpTrophyHandle, SceNpTrophyId trophyId, SceNpTrophyId* platinumId) {
return accessTrophies().unlockTrophy(context, trophyId, platinumId);
}
EXPORT SYSV_ABI int sceNpTrophyUnlockTrophy(SceNpTrophyContext context, SceNpTrophyHandle handle, SceNpTrophyId trophyId, SceNpTrophyId* platinumId) {
*platinumId = Err::SCE_NP_TROPHY_INVALID_TROPHY_ID;
return Ok;
EXPORT SYSV_ABI int sceNpTrophyGetTrophyUnlockState(SceNpTrophyContext context, SceNpTrophyHandle, SceNpTrophyFlagArray* flags, uint32_t* count) {
return accessTrophies().getProgress(context, flags->flagBits, count);
}
EXPORT SYSV_ABI int sceNpTrophyGetTrophyUnlockState(SceNpTrophyContext context, SceNpTrophyHandle handle, SceNpTrophyFlagArray* flags, uint32_t* count) {
if (flags != nullptr) {
flags->flagBits[0] = 0;
flags->flagBits[1] = 0;
flags->flagBits[2] = 0;
flags->flagBits[3] = 0;
}
*count = 2;
EXPORT SYSV_ABI int sceNpTrophyGetGameInfo(SceNpTrophyContext context, SceNpTrophyHandle, SceNpTrophyGameDetails* details, SceNpTrophyGameData* gdata) {
if (details == nullptr && gdata == nullptr) return Err::NpTrophy::INVALID_ARGUMENT;
LOG_USE_MODULE(libSceNpTrophy);
SceNpTrophyFlagArray unlock_progr = {0};
uint32_t unlock_count = 0; // Total unlocked trophies count
uint32_t trophy_count = 0; // Total trophies count
return Ok;
}
int32_t errcode;
if ((errcode = accessTrophies().getProgress(context, unlock_progr.flagBits, nullptr)) != Ok) return errcode;
errcode = Err::NpTrophy::INVALID_NP_TITLE_ID;
EXPORT SYSV_ABI int sceNpTrophyGetGameInfo(SceNpTrophyContext context, SceNpTrophyHandle handle, SceNpTrophyGameDetails* details, SceNpTrophyGameData* data) {
if (details != nullptr) {
details->numGroups = 0;
details->numTrophies = 1;
details->numPlatinum = 0;
details->numGold = 0;
details->numSilver = 0;
details->numBronze = 1;
strcpy_s(details->title, "tropyName");
strcpy_s(details->description, "tropyDesc");
ITrophies::trp_context ctx = {
.lightweight = false,
.entry =
{
.func = [details, gdata, unlock_progr](ITrophies::trp_ent_cb::data_t* data) -> bool {
bool unlocked = SCE_NP_TROPHY_FLAG_ISSET(data->id, (&unlock_progr));
switch (data->grade) {
case 'b': // Bronze trophy
if (details) ++details->numBronze;
if (unlocked && gdata) ++gdata->unlockedBronze;
break;
case 's': // Silver trophy
if (details) ++details->numSilver;
if (unlocked && gdata) ++gdata->unlockedSilver;
break;
case 'g': // Gold trophy
if (details) ++details->numGold;
if (unlocked && gdata) ++gdata->unlockedGold;
break;
case 'p': // Platinum trophy
if (details) ++details->numPlatinum;
if (unlocked && gdata) ++gdata->unlockedPlatinum;
break;
}
return false;
},
},
.itrop =
{
.func = [&errcode, details, &trophy_count](ITrophies::trp_inf_cb::data_t* data) -> bool {
if (details) data->title_name.copy(details->title, sizeof(details->title));
if (details) data->title_detail.copy(details->description, sizeof(details->description));
if (details) details->numTrophies = data->trophy_count;
if (details) details->numGroups = data->group_count;
trophy_count = data->trophy_count;
errcode = Ok;
return true;
},
},
};
ITrophies::ErrCodes ec;
if ((ec = accessTrophies().parseTRP(&ctx)) != ITrophies::ErrCodes::SUCCESS) {
LOG_ERR(L"Failed to parse trophy data: %S", accessTrophies().getError(ec));
return ec == ITrophies::ErrCodes::MAX_TROPHY_REACHED ? Err::NpTrophy::EXCEEDS_MAX : errcode;
}
if (data != nullptr) {
data->unlockedTrophies = 0;
data->unlockedPlatinum = 0;
data->unlockedGold = 0;
data->unlockedSilver = 0;
data->unlockedBronze = 0;
data->progressPercentage = 0;
}
return Ok;
if (gdata) gdata->progressPercentage = (unlock_count / (float)trophy_count) * 100;
return errcode;
}
EXPORT SYSV_ABI int sceNpTrophyGetGroupInfo(SceNpTrophyContext context, SceNpTrophyHandle handle, SceNpTrophyGroupId groupId, SceNpTrophyGroupDetails* details,
SceNpTrophyGroupData* data) {
if (details != nullptr) {
details->groupId = groupId;
details->numTrophies = 0;
details->numPlatinum = 0;
details->numGold = 0;
details->numSilver = 0;
details->numBronze = 0;
EXPORT SYSV_ABI int sceNpTrophyGetGroupInfo(SceNpTrophyContext context, SceNpTrophyHandle, SceNpTrophyGroupId groupId, SceNpTrophyGroupDetails* details,
SceNpTrophyGroupData* grdata) {
if (grdata == nullptr && details == nullptr) return Err::NpTrophy::INVALID_ARGUMENT;
LOG_USE_MODULE(libSceNpTrophy);
SceNpTrophyFlagArray unlock_progr = {0};
uint32_t unlock_count = 0; // Unlocked trophies in specified group
uint32_t trophy_count = 0; // Trophies in specified group
strcpy_s(details->title, "groupName");
strcpy_s(details->description, "groupDesc");
int32_t errcode;
if ((errcode = accessTrophies().getProgress(context, unlock_progr.flagBits, nullptr)) != Ok) return errcode;
errcode = Err::NpTrophy::INVALID_GROUP_ID;
ITrophies::trp_context ctx = {
.lightweight = false,
.entry =
{
.func = [groupId, details, grdata, unlock_progr, &trophy_count, &unlock_count](ITrophies::trp_ent_cb::data_t* data) -> bool {
if (data->group == groupId) {
bool unlocked = SCE_NP_TROPHY_FLAG_ISSET(data->id, (&unlock_progr));
switch (data->grade) {
case 'b': // Bronze trophy
if (details) ++details->numBronze;
if (unlocked && grdata) ++grdata->unlockedBronze;
break;
case 's': // Silver trophy
if (details) ++details->numSilver;
if (unlocked && grdata) ++grdata->unlockedSilver;
break;
case 'g': // Gold trophy
if (details) ++details->numGold;
if (unlocked && grdata) ++grdata->unlockedGold;
break;
case 'p': // Platinum trophy
if (details) ++details->numPlatinum;
if (unlocked && grdata) ++grdata->unlockedPlatinum;
break;
}
if (details) ++details->numTrophies;
if (unlocked && grdata) ++grdata->unlockedTrophies;
if (unlocked) ++unlock_count;
++trophy_count;
}
return false;
},
},
.group =
{
.func = [&errcode, groupId, details, grdata](ITrophies::trp_grp_cb::data_t* data) -> bool {
if (data->id == groupId) {
if (details != nullptr) {
details->groupId = groupId;
data->name.copy(details->title, sizeof(details->title));
data->detail.copy(details->description, sizeof(details->description));
}
if (grdata != nullptr) {
grdata->groupId = groupId;
}
errcode = Ok;
return true; // We found our group, the rest of data is useless
}
return false; // Waiting for the next group
},
},
};
ITrophies::ErrCodes ec;
if ((ec = accessTrophies().parseTRP(&ctx)) != ITrophies::ErrCodes::SUCCESS) {
LOG_ERR(L"Failed to parse trophy data: %S", accessTrophies().getError(ec));
return Err::NpTrophy::BROKEN_DATA;
}
if (data != nullptr) {
data->groupId = groupId;
data->unlockedTrophies = 0;
data->unlockedPlatinum = 0;
data->unlockedGold = 0;
data->unlockedSilver = 0;
data->unlockedBronze = 0;
data->progressPercentage = 0;
}
if (grdata) grdata->progressPercentage = (unlock_count / (float)trophy_count) * 100;
return Ok;
}
EXPORT SYSV_ABI int sceNpTrophyGetTrophyInfo(SceNpTrophyContext context, SceNpTrophyHandle handle, SceNpTrophyId trophyId, SceNpTrophyDetails* details,
SceNpTrophyData* data) {
if (details != nullptr) {
details->trophyId = trophyId;
details->trophyGrade = SceNpTrophyGrade::GRADE_BRONZE;
details->groupId = -1;
details->hidden = false;
EXPORT SYSV_ABI int sceNpTrophyGetTrophyInfo(SceNpTrophyContext context, SceNpTrophyHandle, SceNpTrophyId trophyId, SceNpTrophyDetails* details,
SceNpTrophyData* tdata) {
LOG_USE_MODULE(libSceNpTrophy);
SceNpTrophyFlagArray unlock_progr = {0};
strcpy_s(details->name, "tropyName");
strcpy_s(details->description, "tropyDesc");
int32_t errcode;
if ((errcode = accessTrophies().getProgress(context, unlock_progr.flagBits, nullptr)) != Ok) return errcode;
errcode = Err::NpTrophy::INVALID_TROPHY_ID;
ITrophies::trp_context ctx = {
.lightweight = false,
.entry =
{
.func = [&errcode, context, trophyId, details, tdata, unlock_progr](ITrophies::trp_ent_cb::data_t* data) -> bool {
if (data->id == trophyId) {
bool unlocked = SCE_NP_TROPHY_FLAG_ISSET(data->id, (&unlock_progr));
if (details != nullptr) {
details->trophyId = trophyId;
details->groupId = data->group;
details->hidden = data->hidden;
data->name.copy(details->name, sizeof(details->name));
data->detail.copy(details->description, sizeof(details->description));
switch (data->grade) {
case 'b': // Bronze trophy
details->trophyGrade = SceNpTrophyGrade::BRONZE;
break;
case 's': // Silver trophy
details->trophyGrade = SceNpTrophyGrade::SILVER;
break;
case 'g': // Gold trophy
details->trophyGrade = SceNpTrophyGrade::GOLD;
break;
case 'p': // Platinum trophy
details->trophyGrade = SceNpTrophyGrade::PLATINUM;
break;
}
}
if (tdata != nullptr) {
tdata->trophyId = trophyId;
tdata->timestamp = (tdata->unlocked = unlocked) ? accessTrophies().getUnlockTime(context, trophyId) : 0ull;
}
errcode = Ok;
return true; // We found our trophy, the rest of data is useless
}
return false;
},
},
};
ITrophies::ErrCodes ec;
if ((ec = accessTrophies().parseTRP(&ctx)) != ITrophies::ErrCodes::SUCCESS) {
LOG_ERR(L"Failed to parse trophy data: %S", accessTrophies().getError(ec));
return ec == ITrophies::ErrCodes::MAX_TROPHY_REACHED ? Err::NpTrophy::EXCEEDS_MAX : Err::NpTrophy::BROKEN_DATA;
}
if (data != nullptr) {
data->trophyId = trophyId;
data->unlocked = false;
data->timestamp = 0;
}
return Ok;
return errcode;
}
EXPORT SYSV_ABI int sceNpTrophyGetGameIcon(SceNpTrophyContext context, SceNpTrophyHandle handle, void* buffer, size_t* size) {
*size = 8;
*(uint64_t*)buffer = 0;
return Ok;
EXPORT SYSV_ABI int sceNpTrophyGetGameIcon(SceNpTrophyContext context, SceNpTrophyHandle, void* buffer, size_t* size) {
return searchForPng(context, "ICON0.PNG", buffer, size);
}
EXPORT SYSV_ABI int sceNpTrophyGetGroupIcon(SceNpTrophyContext context, SceNpTrophyHandle handle, SceNpTrophyGroupId groupId, void* buffer, size_t* size) {
return Ok;
EXPORT SYSV_ABI int sceNpTrophyGetGroupIcon(SceNpTrophyContext context, SceNpTrophyHandle, SceNpTrophyGroupId groupId, void* buffer, size_t* size) {
auto name = std::format("GR{:3}.PNG", groupId);
return searchForPng(context, name.c_str(), buffer, size);
}
EXPORT SYSV_ABI int sceNpTrophyGetTrophyIcon(SceNpTrophyContext context, SceNpTrophyHandle handle, SceNpTrophyId trophyId, void* buffer, size_t* size) {
*size = 8;
*(uint64_t*)buffer = 0;
return Ok;
EXPORT SYSV_ABI int sceNpTrophyGetTrophyIcon(SceNpTrophyContext context, SceNpTrophyHandle, SceNpTrophyId trophyId, void* buffer, size_t* size) {
auto name = std::format("TROP{:3}.PNG", trophyId);
return searchForPng(context, name.c_str(), buffer, size);
}
EXPORT SYSV_ABI int sceNpTrophyShowTrophyList(SceNpTrophyContext context, SceNpTrophyHandle handle) {
EXPORT SYSV_ABI int sceNpTrophyShowTrophyList(SceNpTrophyContext context, SceNpTrophyHandle) {
return Ok;
}
EXPORT SYSV_ABI int sceNpTrophyCaptureScreenshot(SceNpTrophyHandle handle, const SceNpTrophyScreenshotTarget* targets, uint32_t numTargets) {
return Ok;
return Err::NpTrophy::SCREENSHOT_DISABLED;
}
}
}

View File

@ -7,10 +7,10 @@ typedef int32_t SceNpTrophyId;
typedef int32_t SceNpTrophyGroupId;
enum class SceNpTrophyGrade : unsigned int {
GRADE_PLATINUM = 1, // PLATINUM
GRADE_GOLD = 2, // GOLD
GRADE_SILVER = 3, // SILVER
GRADE_BRONZE = 4, // BRONZE
PLATINUM = 1, // PLATINUM
GOLD = 2, // GOLD
SILVER = 3, // SILVER
BRONZE = 4, // BRONZE
};
@ -54,8 +54,8 @@ struct SceNpTrophyGameDetails {
uint32_t numGold;
uint32_t numSilver;
uint32_t numBronze;
char title[SCE_NP_TROPHY_GAME_TITLE_MAX_SIZE];
char description[SCE_NP_TROPHY_GAME_DESCR_MAX_SIZE];
char title[NpTrophy::GAME_TITLE_MAX_SIZE];
char description[NpTrophy::GAME_DESCR_MAX_SIZE];
};
struct SceNpTrophyGameData {
@ -76,8 +76,8 @@ struct SceNpTrophyGroupDetails {
uint32_t numGold;
uint32_t numSilver;
uint32_t numBronze;
char title[SCE_NP_TROPHY_GROUP_TITLE_MAX_SIZE];
char description[SCE_NP_TROPHY_GROUP_DESCR_MAX_SIZE];
char title[NpTrophy::GROUP_TITLE_MAX_SIZE];
char description[NpTrophy::GROUP_DESCR_MAX_SIZE];
};
struct SceNpTrophyGroupData {
@ -99,8 +99,8 @@ struct SceNpTrophyDetails {
SceNpTrophyGroupId groupId;
bool hidden;
uint8_t reserved[3];
char name[SCE_NP_TROPHY_NAME_MAX_SIZE];
char description[SCE_NP_TROPHY_DESCR_MAX_SIZE];
char name[NpTrophy::NAME_MAX_SIZE];
char description[NpTrophy::DESCR_MAX_SIZE];
};
struct SceNpTrophyData {

View File

@ -4,7 +4,7 @@ add_library(config_emu SHARED config_emu.cpp)
add_dependencies(config_emu third_party)
target_link_libraries(config_emu PUBLIC libboost_thread)
target_link_libraries(config_emu PUBLIC libboost_thread libboost_filesystem)
target_compile_definitions(config_emu PUBLIC BOOST_ALL_NO_LIB WIN32_LEAN_AND_MEAN)
target_compile_options(config_emu PRIVATE "/Zi")

View File

@ -2,34 +2,36 @@
#include "config_emu.h"
#undef __APICALL_EXTERN
#include "utility/progloc.h"
#include <fstream>
#include <future>
namespace {
class Item {
public:
std::string_view const _name;
std::wstring_view const _name;
bool _dontfix; // If this flag is set then load function won't try fix the file according to default object
json _json;
boost::mutex _mutex;
std::future<void> _future;
Item(std::string_view name, bool df = false): _name(name), _dontfix(df) {}
Item(std::wstring_view name, bool df = false): _name(name), _dontfix(df) {}
std::pair<boost::unique_lock<boost::mutex>, json*> access() {
_future.wait();
return std::make_pair(boost::unique_lock(_mutex), &_json);
}
bool save() {
auto path = std::string("./config/") + _name.data();
bool save(const std::wstring& root) {
auto path = root + _name.data();
try {
std::ofstream json_file(path);
json_file << std::setw(2) << _json;
return true;
} catch (const json::exception& e) {
printf("Failed to save %s: %s\n", _name.data(), e.what());
printf("Failed to save %ls: %s\n", _name.data(), e.what());
return false;
}
};
@ -41,13 +43,15 @@ static inline bool isJsonTypesSimilar(json& a, json& b) {
} // namespace
class Config: public IConfig {
friend class Item;
std::wstring m_cfgroot;
Item m_logging = {"logging.json"};
Item m_graphics = {"graphics.json"};
Item m_audio = {"audio.json"};
Item m_controls = {"controls.json"};
Item m_general = {"general.json"};
Item m_resolve = {"resolve.json", true /*dontfix flag*/};
Item m_logging = {L"logging.json"};
Item m_graphics = {L"graphics.json"};
Item m_audio = {L"audio.json"};
Item m_controls = {L"controls.json"};
Item m_general = {L"general.json"};
Item m_resolve = {L"resolve.json", true /*dontfix flag*/};
public:
Config();
@ -80,20 +84,23 @@ bool Config::save(uint32_t flags) {
bool result = true;
if (!std::filesystem::is_directory("./config/")) std::filesystem::create_directory("./config/");
if (flags & (uint32_t)ConfigModFlag::LOGGING) result &= m_logging.save();
if (flags & (uint32_t)ConfigModFlag::GRAPHICS) result &= m_graphics.save();
if (flags & (uint32_t)ConfigModFlag::AUDIO) result &= m_audio.save();
if (flags & (uint32_t)ConfigModFlag::CONTROLS) result &= m_controls.save();
if (flags & (uint32_t)ConfigModFlag::GENERAL) result &= m_general.save();
if (flags & (uint32_t)ConfigModFlag::RESOLVE) result &= m_resolve.save();
if (!std::filesystem::is_directory(m_cfgroot)) std::filesystem::create_directory(m_cfgroot);
if (flags & (uint32_t)ConfigModFlag::LOGGING) result &= m_logging.save(m_cfgroot);
if (flags & (uint32_t)ConfigModFlag::GRAPHICS) result &= m_graphics.save(m_cfgroot);
if (flags & (uint32_t)ConfigModFlag::AUDIO) result &= m_audio.save(m_cfgroot);
if (flags & (uint32_t)ConfigModFlag::CONTROLS) result &= m_controls.save(m_cfgroot);
if (flags & (uint32_t)ConfigModFlag::GENERAL) result &= m_general.save(m_cfgroot);
if (flags & (uint32_t)ConfigModFlag::RESOLVE) result &= m_resolve.save(m_cfgroot);
return true;
}
Config::Config() {
m_cfgroot = util::getProgramLoc();
m_cfgroot += L"/config/";
auto load = [this](Item* item, json defaults = {}, ConfigModFlag dflag = ConfigModFlag::NONE) {
auto path = std::string("./config/") + item->_name.data();
auto path = m_cfgroot + item->_name.data();
bool should_resave = false, should_backup = false;
try {
@ -101,13 +108,13 @@ Config::Config() {
item->_json = json::parse(json_file, nullptr, true, true);
if ((item->_json).type() == defaults.type()) {
printf("Config %s loaded successfully\n", item->_name.data());
printf("Config %ls loaded successfully\n", item->_name.data());
} else {
should_backup = true;
should_resave = true;
}
} catch (const json::exception& e) {
if ((std::filesystem::exists(path))) printf("Failed to parse %s: %s\n", item->_name.data(), e.what());
if ((std::filesystem::exists(path))) printf("Failed to parse %ls: %s\n", item->_name.data(), e.what());
item->_json = defaults;
should_resave = true;
@ -170,7 +177,7 @@ Config::Config() {
if (!item->_dontfix && fixMissing(item->_json, defaults)) {
should_resave = true;
printf("%s: some missing or invalid parameters has been fixed!\n", item->_name.data());
printf("%ls: some missing or invalid parameters has been fixed!\n", item->_name.data());
}
// Just the same thing as above, but for removing unused keys this time
@ -198,7 +205,7 @@ Config::Config() {
if (!item->_dontfix && removeUnused(item->_json, defaults)) {
should_backup = true;
should_resave = true;
printf("%s: some unused parameters has been removed!\n", item->_name.data());
printf("%ls: some unused parameters has been removed!\n", item->_name.data());
}
if (should_backup == true && std::filesystem::exists(path)) {
@ -207,7 +214,7 @@ Config::Config() {
newp.replace_extension(".back");
std::filesystem::rename(path, newp);
} catch (const std::filesystem::filesystem_error& e) {
printf("%s: failed to create a backup: %s", path.c_str(), e.what());
printf("%ls: failed to create a backup: %s", path.c_str(), e.what());
}
}
@ -269,6 +276,7 @@ Config::Config() {
{"systemlang", 1u},
{"netEnabled", false},
{"netMAC", "00:00:00:00:00:00"},
{"trophyKey", ""},
{"userIndex", 1u},
{"onlineUsers", 1u},
{"profiles", defprofiles},

View File

@ -488,8 +488,8 @@ void GameReport::ShowReportWindow(const Info& info) {
}
SDL_MessageBoxButtonData btns[2] {
{.flags = 0, .buttonid = 1, .text = get_lang_string(lang_id, LangString::BUTTONS_YES)},
{.flags = 0, .buttonid = 2, .text = get_lang_string(lang_id, LangString::BUTTONS_NO)},
{.flags = 0, .buttonid = 1, .text = get_lang_string(lang_id, LangString::BUTTONS_YES)},
};
SDL_MessageBoxData mbd {
@ -539,8 +539,8 @@ void GameReport::ShowReportWindow(const Info& info) {
mbd.message = get_lang_string(lang_id, LangString::LOGGING_TEXT);
mbd.flags = SDL_MESSAGEBOX_WARNING;
mbd.numbuttons = 2;
btns[0].text = get_lang_string(lang_id, LangString::BUTTONS_OK);
btns[1].text = get_lang_string(lang_id, LangString::BUTTONS_CANCEL);
btns[1].text = get_lang_string(lang_id, LangString::BUTTONS_OK);
btns[0].text = get_lang_string(lang_id, LangString::BUTTONS_CANCEL);
if (SDL_ShowMessageBox(&mbd, &btn) != 0) {
LOG_ERR(L"[gamereport] Failed to generate SDL MessageBox: %S", SDL_GetError());

22
utility/progloc.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#ifndef BOOST_ALL_NO_LIB
#define BOOST_ALL_NO_LIB
#endif
#include <boost/dll/runtime_symbol_info.hpp>
namespace util {
inline std::wstring getProgramLoc() {
boost::system::error_code ec;
auto path = boost::dll::program_location(ec);
if (ec.failed()) {
printf("Failed to getProgramLoc(): %s", ec.message().c_str());
exit(1);
}
path.remove_filename();
return path.c_str();
}
} // namespace util

View File

@ -6,6 +6,7 @@
#include <fstream>
#include <memory>
#include <windows.h>
LOG_DEFINE_MODULE(util);
namespace util {
@ -59,4 +60,4 @@ int getPageSize(void) {
}();
return pageSize;
}
} // namespace util
} // namespace util

View File

@ -108,4 +108,4 @@ int getPageSize(void);
#define CLASS_NO_MOVE(name) \
name(name&&) noexcept = delete; \
name& operator=(name&&) noexcept = delete
name& operator=(name&&) noexcept = delete