Trophies file parser

This commit is contained in:
igor725 2024-05-03 04:31:12 +03:00
parent f204f20b49
commit e8d4f46bdf
No known key found for this signature in database
GPG Key ID: 46F13BBE46F8569D
12 changed files with 3252 additions and 25 deletions

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>
@ -46,6 +48,8 @@ target_link_libraries(core PRIVATE
onecore.lib
IPHLPAPI.lib
Ws2_32.lib
libcrypto.lib
libssl.lib
ntdll.dll
VulkanMemoryAllocator
${Vulkan_LIBRARIES}

View File

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

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

@ -0,0 +1,388 @@
#define __APICALL_EXTERN
#include "trophies.h"
#undef __APICALL_EXTERN
#include "core/fileManager/fileManager.h"
#include "logging.h"
#include "modules_include/system_param.h"
#include "tools/config_emu/config_emu.h"
#include "xml3all.h"
#include <fstream>
#include <openssl/evp.h>
#define TR_AES_BLOCK_SIZE 16
// We don't need it there
#undef min
LOG_DEFINE_MODULE(core_trophies);
class Trophies: public ITrophies {
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)); }
bool m_bKeySet = false;
uint8_t m_trkey[TR_AES_BLOCK_SIZE] = {};
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);
}
}
}
ErrCodes parseTRP(trp_grp_cb* grpcb = nullptr, trp_ent_cb* trpcb = nullptr, trp_png_cb* pngcb = nullptr, bool lightweight = false) final {
if (grpcb == nullptr && trpcb == nullptr && pngcb == nullptr) return ErrCodes::NO_CALLBACKS;
if (pngcb != nullptr && lightweight == true) return ErrCodes::NO_PNG;
if (m_bKeySet == false) return ErrCodes::NO_KEY_SET;
LOG_USE_MODULE(core_trophies);
auto mpath = accessFileManager().getMappedPath("/app0/sce_sys/trophy/trophy00.trp");
if (mpath.has_value() == false) 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;
std::string localized_trp;
{
SystemParamLang systemlang;
auto [lock, jData] = accessConfig()->accessModule(ConfigModFlag::GENERAL);
try {
(*jData)["systemlang"].get_to(systemlang);
} catch (const json::exception& e) {
systemlang = SystemParamLang::EnglishUS;
}
if (systemlang != SystemParamLang::EnglishUS) localized_trp = std::format("TROP_{:02}.ESFM", (uint32_t)systemlang);
}
trp_entry ent, dent = {0};
for (uint32_t i = 0; i < hdr.entry_num; ++i) {
if ((pngcb != nullptr && pngcb->cancelled) && (grpcb != nullptr && grpcb->cancelled) && (trpcb != nullptr && trpcb->cancelled))
return ErrCodes::SUCCESS;
if (!trfile.read((char*)&ent, sizeof(trp_entry))) return ErrCodes::IO_FAIL;
ent.pos = _byteswap_uint64(ent.pos);
ent.len = _byteswap_uint64(ent.len);
if (pngcb != nullptr && !pngcb->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) {
pngcb->data.pngsize = ent.len;
pngcb->data.pngdata = new char[ent.len]; // Developer should free this memory manually
auto ppos = trfile.tellg();
trfile.seekg(ent.pos);
if (trfile.read((char*)pngcb->data.pngdata, ent.len)) {
pngcb->cancelled = pngcb->func(&pngcb->data);
trfile.seekg(ppos);
continue;
}
return ErrCodes::IO_FAIL;
} else {
// Is this even possible?
return ErrCodes::NOT_IMPLEMENTED;
}
}
}
if (grpcb != nullptr || trpcb != nullptr) {
static std::string_view ext(".esfm");
std::string_view name(ent.name);
if (!std::equal(ext.rbegin(), ext.rend(), name.rbegin(), caseequal)) continue;
if ((ent.len % 16) != 0) return ErrCodes::INVALID_AES;
if (lightweight == true) {
static std::string_view lwfile("tropconf.esfm");
if (!std::equal(lwfile.begin(), lwfile.end(), name.begin(), name.end(), caseequal)) continue;
} else {
static std::string_view dfile("trop.esfm");
if (localized_trp.length() == 0) {
// No localized trophy needed, using the English one
if (!std::equal(name.begin(), name.end(), dfile.begin(), dfile.end(), caseequal)) continue;
} else {
// Trying to find localized trophy
if (!std::equal(name.begin(), name.end(), localized_trp.begin(), localized_trp.end(), caseequal)) {
if (std::equal(name.begin(), name.end(), dfile.begin(), dfile.end(), caseequal)) dent = ent;
// End of trophy.trp file reached
if (i == (hdr.entry_num - 1)) {
// Failed to find localized and non-localized trophy, probably file packed incorrectly
if (dent.len == 0) continue;
// We failed to find localized trophy, so we use the default one
ent = dent;
} else {
// Not the last entry yet, check the next one
continue;
}
}
}
}
auto mem = new char[ent.len];
auto ppos = trfile.tellg();
trfile.seekg(ent.pos); // Seek to file position
if (((ent.flag >> 24) & 0x03) == 0) {
auto ppos = trfile.tellg();
if (!trfile.read(mem, ent.len)) {
delete[] mem;
return ErrCodes::IO_FAIL;
}
} else {
uint8_t d_iv[TR_AES_BLOCK_SIZE];
uint8_t kg_iv[TR_AES_BLOCK_SIZE];
uint8_t enc_xmlh[48];
::memset(kg_iv, 0, TR_AES_BLOCK_SIZE);
if (!trfile.read((char*)d_iv, TR_AES_BLOCK_SIZE)) {
delete[] mem;
return ErrCodes::IO_FAIL;
}
// 384 encrypted bits is just enough to find interesting for us string
if (!trfile.read((char*)enc_xmlh, 48)) {
delete[] mem;
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;
}
if (!EVP_DecryptUpdate(data_ctx, outbuffer, &outlen, enc_xmlh, 48)) {
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, outbuffer, outlen);
char* mem_off = mem + outlen;
trfile.seekg(ent.pos + 64);
size_t copied;
while ((copied = size_t(mem_off - mem)) < ent.len) {
size_t len = std::min(ent.len - copied, sizeof(inbuffer));
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;
}
EVP_CIPHER_CTX_free(data_ctx);
*mem_off = '\0'; // Finally
}
//- Data decipher context
return true;
}; // lambda: trydecrypt
int npid;
bool success = false;
auto npid_path = accessFileManager().getGameFilesDir() / ".npcommid";
{
std::ifstream npid_f(npid_path);
if (npid_f.is_open()) {
npid_f >> npid;
success = trydecrypt(npid);
}
}
if (success == false) {
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 == true) {
std::ofstream npid_f(npid_path, std::ios::out);
if (npid_f.is_open()) {
npid_f << npid;
}
}
}
// Restore cursor position to file list
trfile.seekg(ppos);
if (success == false) {
LOG_ERR(L"Failed to guess ID for trophy file");
delete[] mem;
return ErrCodes::DECRYPT;
}
}
XML3::XML xml(mem, strlen(mem));
delete[] mem;
{ // xml parser
auto& rootel = xml.GetRootElement();
if (rootel.GetElementName() == "trophyconf") {
for (auto& chel: rootel.GetChildren()) {
auto& cheln = chel->GetElementName();
if (trpcb != nullptr && cheln == "trophy") {
trpcb->data.id = -1;
trpcb->data.group = -1;
trpcb->data.type = 0xFF;
trpcb->data.name.clear();
trpcb->data.detail.clear();
for (auto& chvar: chel->GetVariables()) {
auto& vname = chvar->GetName();
if (vname == "id") {
trpcb->data.id = chvar->GetValueInt(-1);
} else if (vname == "hidden") {
trpcb->data.hidden = chvar->GetValue() != "no";
} else if (vname == "ttype") {
trpcb->data.type = chvar->GetValue().at(0);
} else if (vname == "gid") {
trpcb->data.group = chvar->GetValueInt(-1);
}
}
// There is no `name` and `detail` fields if we read TROPCONF.ESFM
if (lightweight == true) continue;
for (auto& chch: chel->GetChildren()) {
auto& cname = chch->GetElementName();
if (cname == "name") {
trpcb->data.name.assign(chch->GetContent());
} else if (cname == "detail") {
trpcb->data.detail.assign(chch->GetContent());
}
}
trpcb->cancelled = trpcb->func(&trpcb->data);
} else if (grpcb != nullptr && cheln == "group") {
for (auto& chvar: chel->GetVariables()) {
auto& vname = chvar->GetName();
if (vname == "id") {
grpcb->data.id = chvar->GetValueInt(-1);
}
// There is no `name` and `detail` fields if we read TROPCONF.ESFM
if (lightweight == true) continue;
for (auto& chch: chel->GetChildren()) {
auto& cname = chch->GetElementName();
if (cname == "name") {
grpcb->data.name.assign(chch->GetContent());
} else if (cname == "detail") {
grpcb->data.detail.assign(chch->GetContent());
}
}
grpcb->cancelled = grpcb->func(&grpcb->data);
}
}
}
// We already parsed trophy info, we don't need more of it
if (trpcb != nullptr) trpcb->cancelled = true;
if (grpcb != nullptr) grpcb->cancelled = true;
} // element: trophyconf
} // xml parser
} // group & trophy callbacks
} // entries loop
return ErrCodes::SUCCESS;
} // trfile.is_open()
return ErrCodes::NO_TROPHIES;
} // parseTRP()
};
ITrophies& accessTrophies() {
static Trophies ti;
return ti;
}

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

@ -0,0 +1,80 @@
#pragma once
#include "utility/utility.h"
#include <cstdint>
#include <functional>
#include <string>
class ITrophies {
CLASS_NO_COPY(ITrophies);
CLASS_NO_MOVE(ITrophies);
protected:
ITrophies() = default;
public:
~ITrophies() = default;
enum class ErrCodes {
SUCCESS = 0,
NO_KEY_SET,
INVALID_MAGIC,
INVALID_VERSION,
INVALID_ENTSIZE,
INVALID_AES,
NOT_IMPLEMENTED,
IO_FAIL,
NO_CALLBACKS,
NO_PNG,
DECRYPT,
NO_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;
bool hidden;
uint8_t type;
std::string name;
std::string detail;
} data;
bool cancelled;
std::function<bool(data_t*)> func;
};
struct trp_png_cb {
struct data_t {
void* pngdata;
size_t pngsize;
} data;
bool cancelled;
std::function<bool(data_t*)> func;
};
virtual ErrCodes parseTRP(trp_grp_cb* grpcb = nullptr, trp_ent_cb* trpcb = nullptr, trp_png_cb* pngcb = nullptr, bool lightweight = false) = 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

2706
core/trophies/xml3all.h Normal file

File diff suppressed because it is too large Load Diff

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

@ -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,26 @@
#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 INVALID_ARGUMENT = -2141907452;
constexpr int32_t ALREADY_EXISTS = -2141907437;
constexpr int32_t EXCEEDS_MAX = -2141907422;
} // 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,11 +1,22 @@
#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 {
struct trp_context {
bool created;
SceNpServiceLabel label;
};
static trp_context contexts[4] = {};
} // namespace
extern "C" {
@ -25,7 +36,20 @@ EXPORT SYSV_ABI int sceNpTrophyAbortHandle(SceNpTrophyHandle handle) {
}
EXPORT SYSV_ABI int sceNpTrophyCreateContext(SceNpTrophyContext* context, int32_t userId, SceNpServiceLabel serviceLabel, uint64_t options) {
*context = 1;
auto& ctx = contexts[userId];
if (ctx.created) return Err::NpTrophy::ALREADY_EXISTS;
// static std::once_flag init;
// std::call_once(init, []() {
// ITrophies::trp_ent_cb ent = {.func = [](ITrophies::trp_ent_cb::data_t* data) {
// LOG_USE_MODULE(libSceNpTrophy);
// LOG_ERR(L"Trophy! %S: %S (id:%d)", data->name.c_str(), data->detail.c_str(), data->id);
// return false; // Do not cancel this callback
// }};
// accessTrophies().parseTRP(nullptr, &ent);
// });
ctx.created = true;
ctx.label = serviceLabel;
*context = userId;
return Ok;
}
@ -38,7 +62,7 @@ EXPORT SYSV_ABI int sceNpTrophyRegisterContext(SceNpTrophyContext context, SceNp
}
EXPORT SYSV_ABI int sceNpTrophyUnlockTrophy(SceNpTrophyContext context, SceNpTrophyHandle handle, SceNpTrophyId trophyId, SceNpTrophyId* platinumId) {
*platinumId = Err::SCE_NP_TROPHY_INVALID_TROPHY_ID;
*platinumId = NpTrophy::INVALID_TROPHY_ID;
return Ok;
}
@ -147,4 +171,4 @@ EXPORT SYSV_ABI int sceNpTrophyShowTrophyList(SceNpTrophyContext context, SceNpT
EXPORT SYSV_ABI int sceNpTrophyCaptureScreenshot(SceNpTrophyHandle handle, const SceNpTrophyScreenshotTarget* targets, uint32_t numTargets) {
return Ok;
}
}
}

View File

@ -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

@ -269,6 +269,7 @@ Config::Config() {
{"systemlang", 1u},
{"netEnabled", false},
{"netMAC", "00:00:00:00:00:00"},
{"trophyKey", ""},
{"userIndex", 1u},
{"onlineUsers", 1u},
{"profiles", defprofiles},