mirror of
https://github.com/SysRay/psOff_public.git
synced 2024-11-23 22:39:40 +00:00
commit
0e4c31a17c
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -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,
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
5
core/trophies/CMakeLists.txt
Normal file
5
core/trophies/CMakeLists.txt
Normal file
@ -0,0 +1,5 @@
|
||||
add_library(trophies OBJECT
|
||||
trophies.cpp
|
||||
)
|
||||
|
||||
add_dependencies(trophies third_party)
|
768
core/trophies/trophies.cpp
Normal file
768
core/trophies/trophies.cpp
Normal 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
143
core/trophies/trophies.h
Normal 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
|
@ -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
|
||||
)
|
||||
)
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
|
142
core/videoout/overlay/overtrophy/overtrophy.cpp
Normal file
142
core/videoout/overlay/overtrophy/overtrophy.cpp
Normal 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;
|
||||
}
|
16
core/videoout/overlay/overtrophy/overtrophy.h
Normal file
16
core/videoout/overlay/overtrophy/overtrophy.h
Normal 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();
|
@ -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);
|
||||
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
@ -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
|
||||
|
6
main.cpp
6
main.cpp
@ -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);
|
||||
|
@ -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})
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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},
|
||||
|
@ -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
22
utility/progloc.h
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user