mirror of
https://github.com/Vita3K/Vita3K.git
synced 2025-04-02 11:02:10 -04:00
601 lines
22 KiB
C++
601 lines
22 KiB
C++
// Vita3K emulator project
|
|
// Copyright (C) 2025 Vita3K team
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along
|
|
// with this program; if not, write to the Free Software Foundation, Inc.,
|
|
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
#include <module/module.h>
|
|
|
|
#include <np/functions.h>
|
|
#include <np/state.h>
|
|
#include <np/trophy/context.h>
|
|
#include <rtc/rtc.h>
|
|
#include <util/log.h>
|
|
|
|
#include <util/tracy.h>
|
|
TRACY_MODULE_NAME(SceNpTrophy);
|
|
|
|
using SceNpTrophyHandle = int32_t;
|
|
using SceNpTrophyID = int32_t;
|
|
using SceNpTrophyGroupId = int32_t;
|
|
|
|
#define SCE_NP_TROPHY_ERROR_UNKNOWN 0x80551600
|
|
#define SCE_NP_TROPHY_ERROR_NOT_INITIALIZED 0x80551601
|
|
#define SCE_NP_TROPHY_ERROR_ALREADY_INITIALIZED 0x80551602
|
|
#define SCE_NP_TROPHY_ERROR_NO_MEMORY 0x80551603
|
|
#define SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT 0x80551604
|
|
#define SCE_NP_TROPHY_ERROR_INSUFFICIENT_BUFFER 0x80551605
|
|
#define SCE_NP_TROPHY_ERROR_EXCEEDS_MAX 0x80551606
|
|
#define SCE_NP_TROPHY_ERROR_ABORT 0x80551607
|
|
#define SCE_NP_TROPHY_ERROR_INVALID_HANDLE 0x80551608
|
|
#define SCE_NP_TROPHY_ERROR_INVALID_CONTEXT 0x80551609
|
|
#define SCE_NP_TROPHY_ERROR_INVALID_NPCOMMID 0x8055160a
|
|
#define SCE_NP_TROPHY_ERROR_INVALID_NPCOMMSIGN 0x8055160b
|
|
#define SCE_NP_TROPHY_ERROR_NPCOMMSIGN_VERIFICATION_FAILURE 0x8055160c
|
|
#define SCE_NP_TROPHY_ERROR_INVALID_GROUP_ID 0x8055160d
|
|
#define SCE_NP_TROPHY_ERROR_INVALID_TROPHY_ID 0x8055160e
|
|
#define SCE_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED 0x8055160f
|
|
#define SCE_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK 0x80551610
|
|
#define SCE_NP_TROPHY_ERROR_ACCOUNTID_NOT_MATCH 0x80551611
|
|
#define SCE_NP_TROPHY_ERROR_SETUP_REQUIRED 0x80551612
|
|
#define SCE_NP_TROPHY_ERROR_ALREADY_SETUP 0x80551613
|
|
#define SCE_NP_TROPHY_ERROR_BROKEN_DATA 0x80551614
|
|
#define SCE_NP_TROPHY_ERROR_INSUFFICIENT_EM_SPACE 0x80551615
|
|
#define SCE_NP_TROPHY_ERROR_CONTEXT_ALREADY_EXISTS 0x80551616
|
|
#define SCE_NP_TROPHY_ERROR_TRP_FILE_VERIFICATION_FAILURE 0x80551617
|
|
#define SCE_NP_TROPHY_ERROR_ICON_FILE_NOT_FOUND 0x80551618
|
|
#define SCE_NP_TROPHY_ERROR_TRP_FILE_NOT_FOUND 0x80551619
|
|
#define SCE_NP_TROPHY_ERROR_INVALID_TRP_FILE_FORMAT 0x8055161a
|
|
#define SCE_NP_TROPHY_ERROR_UNSUPPORTED_TRP_FILE 0x8055161b
|
|
#define SCE_NP_TROPHY_ERROR_INVALID_TROPHY_CONF_FORMAT 0x8055161c
|
|
#define SCE_NP_TROPHY_ERROR_UNSUPPORTED_TROPHY_CONF 0x8055161d
|
|
#define SCE_NP_TROPHY_ERROR_TROPHY_NOT_UNLOCKED 0x8055161e
|
|
#define SCE_NP_TROPHY_ERROR_UNLOCK_DENIED 0x8055161f
|
|
#define SCE_NP_TROPHY_ERROR_INSUFFICIENT_MC_SPACE 0x80551620
|
|
#define SCE_NP_TROPHY_ERROR_DEBUG_FAILURE 0x80551621
|
|
|
|
#define SCE_NP_TROPHY_GAME_TITLE_MAX_SIZE 128
|
|
#define SCE_NP_TROPHY_GAME_DESCR_MAX_SIZE 1024
|
|
#define SCE_NP_TROPHY_NAME_MAX_SIZE 128
|
|
#define SCE_NP_TROPHY_DESCR_MAX_SIZE 1024
|
|
#define SCE_NP_TROPHY_GROUP_TITLE_MAX_SIZE 128
|
|
#define SCE_NP_TROPHY_GROUP_DESCR_MAX_SIZE 1024
|
|
|
|
struct SceNpTrophyGameDetails {
|
|
SceSize size;
|
|
SceUInt32 numGroups;
|
|
SceUInt32 numTrophies;
|
|
SceUInt32 numPlatinum;
|
|
SceUInt32 numGold;
|
|
SceUInt32 numSilver;
|
|
SceUInt32 numBronze;
|
|
SceChar8 title[SCE_NP_TROPHY_GAME_TITLE_MAX_SIZE];
|
|
SceChar8 description[SCE_NP_TROPHY_GAME_DESCR_MAX_SIZE];
|
|
};
|
|
|
|
struct SceNpTrophyGameData {
|
|
SceSize size;
|
|
SceUInt32 unlockedTrophies;
|
|
SceUInt32 unlockedPlatinum;
|
|
SceUInt32 unlockedGold;
|
|
SceUInt32 unlockedSilver;
|
|
SceUInt32 unlockedBronze;
|
|
SceUInt32 progressPercentage;
|
|
};
|
|
|
|
struct SceNpTrophyGroupDetails {
|
|
SceSize size;
|
|
SceNpTrophyGroupId groupId;
|
|
SceUInt32 numTrophies;
|
|
SceUInt32 numPlatinum;
|
|
SceUInt32 numGold;
|
|
SceUInt32 numSilver;
|
|
SceUInt32 numBronze;
|
|
SceChar8 title[SCE_NP_TROPHY_GROUP_TITLE_MAX_SIZE];
|
|
SceChar8 description[SCE_NP_TROPHY_GROUP_DESCR_MAX_SIZE];
|
|
};
|
|
|
|
struct SceNpTrophyGroupData {
|
|
SceSize size;
|
|
SceNpTrophyGroupId groupId;
|
|
SceUInt32 unlockedTrophies;
|
|
SceUInt32 unlockedPlatinum;
|
|
SceUInt32 unlockedGold;
|
|
SceUInt32 unlockedSilver;
|
|
SceUInt32 unlockedBronze;
|
|
SceUInt32 progressPercentage;
|
|
};
|
|
|
|
struct SceNpTrophyDetails {
|
|
SceSize size;
|
|
SceNpTrophyID trophyId;
|
|
np::trophy::SceNpTrophyGrade trophyGrade;
|
|
SceNpTrophyGroupId groupId;
|
|
SceBool hidden;
|
|
SceChar8 name[SCE_NP_TROPHY_NAME_MAX_SIZE];
|
|
SceChar8 description[SCE_NP_TROPHY_DESCR_MAX_SIZE];
|
|
};
|
|
|
|
struct SceNpTrophyData {
|
|
SceSize size;
|
|
SceNpTrophyID trophyId;
|
|
SceBool unlocked;
|
|
SceUInt8 reserved[4];
|
|
SceRtcTick timestamp;
|
|
};
|
|
|
|
EXPORT(int, sceNpTrophyAbortHandle) {
|
|
TRACY_FUNC(sceNpTrophyAbortHandle);
|
|
STUBBED("Stubbed with SCE_OK");
|
|
return 0;
|
|
}
|
|
|
|
EXPORT(int, sceNpTrophyCreateContext, np::trophy::ContextHandle *context, const np::CommunicationID *comm_id,
|
|
void *comm_sign, const uint64_t options) {
|
|
TRACY_FUNC(sceNpTrophyCreateContext, context, comm_id, comm_sign, options);
|
|
if (!emuenv.np.trophy_state.inited) {
|
|
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
if (comm_id && comm_id->num > 99) {
|
|
return SCE_NP_TROPHY_ERROR_INVALID_NPCOMMID;
|
|
}
|
|
|
|
np::NpTrophyError err = np::NpTrophyError::TROPHY_ERROR_NONE;
|
|
*context = create_trophy_context(emuenv.np, &emuenv.io, emuenv.pref_path, comm_id, static_cast<uint32_t>(emuenv.cfg.sys_lang),
|
|
&err);
|
|
|
|
if (*context == np::trophy::INVALID_CONTEXT_HANDLE) {
|
|
switch (err) {
|
|
case np::NpTrophyError::TROPHY_CONTEXT_EXIST: {
|
|
return SCE_NP_TROPHY_ERROR_CONTEXT_ALREADY_EXISTS;
|
|
}
|
|
|
|
case np::NpTrophyError::TROPHY_CONTEXT_FILE_NON_EXIST: {
|
|
return SCE_NP_TROPHY_ERROR_TRP_FILE_NOT_FOUND;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
np::trophy::Context *ctx_ptr = get_trophy_context(emuenv.np.trophy_state, *context);
|
|
LOG_TRACE("Trophy context for {}_{:0>2d} create successfuly!", std::string(ctx_ptr->comm_id.data, 9), ctx_ptr->comm_id.num);
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXPORT(int, sceNpTrophyCreateHandle, SceNpTrophyHandle *handle) {
|
|
TRACY_FUNC(sceNpTrophyCreateHandle, handle);
|
|
// We don't handle "handle" for now. It's just a mechanic for async and its abortion.
|
|
// Everything emulated here is sync :)
|
|
STUBBED("Stubbed handle with 1");
|
|
*handle = 1;
|
|
return 0;
|
|
}
|
|
|
|
EXPORT(int, sceNpTrophyDestroyContext, np::trophy::ContextHandle handle) {
|
|
TRACY_FUNC(sceNpTrophyDestroyContext, handle);
|
|
const bool result = destroy_trophy_context(emuenv.np.trophy_state, handle);
|
|
|
|
if (!result) {
|
|
return SCE_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXPORT(int, sceNpTrophyDestroyHandle, SceNpTrophyHandle handle) {
|
|
TRACY_FUNC(sceNpTrophyDestroyHandle, handle);
|
|
STUBBED("Stubbed with SCE_OK");
|
|
return 0;
|
|
}
|
|
|
|
#define NP_TROPHY_GET_FUNCTION_STARTUP(context_handle) \
|
|
if (!emuenv.np.trophy_state.inited) { \
|
|
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED; \
|
|
} \
|
|
if (!size) { \
|
|
return SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT; \
|
|
} \
|
|
np::trophy::Context *context = get_trophy_context(emuenv.np.trophy_state, context_handle); \
|
|
if (!context) { \
|
|
return SCE_NP_TROPHY_ERROR_INVALID_CONTEXT; \
|
|
}
|
|
|
|
EXPORT(int, sceNpTrophyGetGameIcon, np::trophy::ContextHandle context_handle, SceNpTrophyHandle api_handle,
|
|
void *buffer, SceSize *size) {
|
|
TRACY_FUNC(sceNpTrophyGetGameIcon, context_handle, api_handle, buffer, size);
|
|
NP_TROPHY_GET_FUNCTION_STARTUP(context_handle)
|
|
return context->copy_file_data_from_trophy_file("ICON0.PNG", buffer, size);
|
|
}
|
|
|
|
EXPORT(int, sceNpTrophyGetGameInfo, np::trophy::ContextHandle context_handle, SceNpTrophyHandle api_handle, SceNpTrophyGameDetails *details, SceNpTrophyGameData *data) {
|
|
TRACY_FUNC(sceNpTrophyGetGameInfo, context_handle, api_handle, details, data);
|
|
if (!emuenv.np.trophy_state.inited) {
|
|
return RET_ERROR(SCE_NP_TROPHY_ERROR_NOT_INITIALIZED);
|
|
}
|
|
|
|
if ((context_handle == np::trophy::INVALID_CONTEXT_HANDLE)
|
|
|| (api_handle == -1)
|
|
|| (!details && !data)
|
|
|| (details && (details->size != sizeof(SceNpTrophyGameDetails)))
|
|
|| (data && (data->size != sizeof(SceNpTrophyGameData)))) {
|
|
return RET_ERROR(SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT);
|
|
}
|
|
|
|
np::trophy::Context *context = get_trophy_context(emuenv.np.trophy_state, context_handle);
|
|
if (!context) {
|
|
return RET_ERROR(SCE_NP_TROPHY_ERROR_INVALID_CONTEXT);
|
|
}
|
|
|
|
if (details) {
|
|
details->numGroups = context->group_count;
|
|
details->numTrophies = context->trophy_count;
|
|
|
|
std::string name, detail;
|
|
if (!context->get_trophy_set(name, detail))
|
|
return RET_ERROR(SCE_NP_TROPHY_ERROR_UNSUPPORTED_TROPHY_CONF);
|
|
|
|
memcpy(details->title, name.c_str(), name.size() + 1);
|
|
memcpy(details->description, detail.c_str(), detail.size() + 1);
|
|
|
|
for (uint32_t i = 0; i < context->trophy_count; i++) {
|
|
switch (context->trophy_kinds[i]) {
|
|
case np::trophy::SceNpTrophyGrade::SCE_NP_TROPHY_GRADE_PLATINUM:
|
|
details->numPlatinum++;
|
|
break;
|
|
case np::trophy::SceNpTrophyGrade::SCE_NP_TROPHY_GRADE_GOLD:
|
|
details->numGold++;
|
|
break;
|
|
case np::trophy::SceNpTrophyGrade::SCE_NP_TROPHY_GRADE_SILVER:
|
|
details->numSilver++;
|
|
break;
|
|
case np::trophy::SceNpTrophyGrade::SCE_NP_TROPHY_GRADE_BRONZE:
|
|
details->numBronze++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (data) {
|
|
data->unlockedTrophies = context->total_trophy_unlocked();
|
|
|
|
for (uint32_t i = 0; i < context->trophy_count; i++) {
|
|
if (context->is_trophy_unlocked(i)) {
|
|
switch (context->trophy_kinds[i]) {
|
|
case np::trophy::SceNpTrophyGrade::SCE_NP_TROPHY_GRADE_PLATINUM:
|
|
data->unlockedPlatinum++;
|
|
break;
|
|
case np::trophy::SceNpTrophyGrade::SCE_NP_TROPHY_GRADE_GOLD:
|
|
data->unlockedGold++;
|
|
break;
|
|
case np::trophy::SceNpTrophyGrade::SCE_NP_TROPHY_GRADE_SILVER:
|
|
data->unlockedSilver++;
|
|
break;
|
|
case np::trophy::SceNpTrophyGrade::SCE_NP_TROPHY_GRADE_BRONZE:
|
|
data->unlockedBronze++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
data->progressPercentage = data->unlockedTrophies * 100 / context->trophy_count;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXPORT(int, sceNpTrophyGetGroupIcon) {
|
|
TRACY_FUNC(sceNpTrophyGetGroupIcon);
|
|
return UNIMPLEMENTED();
|
|
}
|
|
|
|
EXPORT(int, sceNpTrophyGetGroupInfo, np::trophy::ContextHandle context_handle, SceNpTrophyHandle api_handle, SceNpTrophyGroupId group_id, SceNpTrophyGroupDetails *details, SceNpTrophyGroupData *data) {
|
|
TRACY_FUNC(sceNpTrophyGetGroupInfo, context_handle, api_handle, group_id, details, data);
|
|
STUBBED("More then one default trophies group is not supported");
|
|
if (!emuenv.np.trophy_state.inited) {
|
|
return RET_ERROR(SCE_NP_TROPHY_ERROR_NOT_INITIALIZED);
|
|
}
|
|
|
|
if ((context_handle == np::trophy::INVALID_CONTEXT_HANDLE)
|
|
|| (api_handle == -1)
|
|
|| ((group_id < -1) || (group_id >= np::trophy::MAX_GROUPS))
|
|
|| (!details && !data)
|
|
|| (details && (details->size != sizeof(*details)))
|
|
|| (data && (data->size != sizeof(*data)))) {
|
|
return RET_ERROR(SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT);
|
|
}
|
|
|
|
np::trophy::Context *context = get_trophy_context(emuenv.np.trophy_state, context_handle);
|
|
if (!context) {
|
|
return RET_ERROR(SCE_NP_TROPHY_ERROR_INVALID_CONTEXT);
|
|
}
|
|
|
|
if (group_id != -1) { // SCE_NP_TROPHY_BASE_GAME_GROUP_ID
|
|
return UNIMPLEMENTED();
|
|
}
|
|
|
|
if (details) {
|
|
details->groupId = group_id;
|
|
details->numTrophies = context->trophy_count;
|
|
|
|
std::string name, detail;
|
|
if (!context->get_trophy_set(name, detail))
|
|
return RET_ERROR(SCE_NP_TROPHY_ERROR_UNSUPPORTED_TROPHY_CONF);
|
|
|
|
memcpy(details->title, name.c_str(), name.size() + 1);
|
|
memcpy(details->description, detail.c_str(), detail.size() + 1);
|
|
|
|
for (uint32_t i = 0; i < context->trophy_count; i++) {
|
|
switch (context->trophy_kinds[i]) {
|
|
case np::trophy::SceNpTrophyGrade::SCE_NP_TROPHY_GRADE_PLATINUM:
|
|
details->numPlatinum++;
|
|
break;
|
|
case np::trophy::SceNpTrophyGrade::SCE_NP_TROPHY_GRADE_GOLD:
|
|
details->numGold++;
|
|
break;
|
|
case np::trophy::SceNpTrophyGrade::SCE_NP_TROPHY_GRADE_SILVER:
|
|
details->numSilver++;
|
|
break;
|
|
case np::trophy::SceNpTrophyGrade::SCE_NP_TROPHY_GRADE_BRONZE:
|
|
details->numBronze++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (data) {
|
|
data->unlockedTrophies = context->total_trophy_unlocked();
|
|
|
|
for (uint32_t i = 0; i < context->trophy_count; i++) {
|
|
if (context->is_trophy_unlocked(i)) {
|
|
switch (context->trophy_kinds[i]) {
|
|
case np::trophy::SceNpTrophyGrade::SCE_NP_TROPHY_GRADE_PLATINUM:
|
|
data->unlockedPlatinum++;
|
|
break;
|
|
case np::trophy::SceNpTrophyGrade::SCE_NP_TROPHY_GRADE_GOLD:
|
|
data->unlockedGold++;
|
|
break;
|
|
case np::trophy::SceNpTrophyGrade::SCE_NP_TROPHY_GRADE_SILVER:
|
|
data->unlockedSilver++;
|
|
break;
|
|
case np::trophy::SceNpTrophyGrade::SCE_NP_TROPHY_GRADE_BRONZE:
|
|
data->unlockedBronze++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
data->progressPercentage = data->unlockedTrophies * 100 / context->trophy_count;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
EXPORT(int, sceNpTrophyGetTrophyIcon, np::trophy::ContextHandle context_handle, SceNpTrophyHandle api_handle,
|
|
SceNpTrophyID trophy_id, void *buffer, SceSize *size) {
|
|
TRACY_FUNC(sceNpTrophyGetTrophyIcon, context_handle, api_handle, trophy_id, buffer, size);
|
|
NP_TROPHY_GET_FUNCTION_STARTUP(context_handle)
|
|
|
|
// Trophy should only be in this region
|
|
if (trophy_id < 0 || trophy_id >= np::trophy::MAX_TROPHIES) {
|
|
return SCE_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
|
|
}
|
|
|
|
// Make filename
|
|
const std::string trophy_icon_filename = fmt::format("TROP{:0>3d}.PNG", trophy_id);
|
|
return context->copy_file_data_from_trophy_file(trophy_icon_filename.c_str(), buffer, size);
|
|
}
|
|
|
|
EXPORT(int, sceNpTrophyGetTrophyInfo, np::trophy::ContextHandle context_handle, SceNpTrophyHandle api_handle, SceNpTrophyID trophy_id, SceNpTrophyDetails *details, SceNpTrophyData *data) {
|
|
TRACY_FUNC(sceNpTrophyGetTrophyInfo, context_handle, api_handle, trophy_id, details, data);
|
|
if (!emuenv.np.trophy_state.inited) {
|
|
return RET_ERROR(SCE_NP_TROPHY_ERROR_NOT_INITIALIZED);
|
|
}
|
|
|
|
if ((context_handle == np::trophy::INVALID_CONTEXT_HANDLE)
|
|
|| (api_handle == -1)
|
|
|| ((trophy_id < 0) || (trophy_id >= np::trophy::MAX_TROPHIES))
|
|
|| (!details && !data)
|
|
|| (details && (details->size != sizeof(SceNpTrophyDetails)))
|
|
|| (data && (data->size != sizeof(SceNpTrophyData)))) {
|
|
return RET_ERROR(SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT);
|
|
}
|
|
|
|
np::trophy::Context *context = get_trophy_context(emuenv.np.trophy_state, context_handle);
|
|
if (!context) {
|
|
return RET_ERROR(SCE_NP_TROPHY_ERROR_INVALID_CONTEXT);
|
|
}
|
|
|
|
if (details) {
|
|
details->trophyId = trophy_id;
|
|
details->trophyGrade = context->trophy_kinds[trophy_id];
|
|
details->hidden = context->is_trophy_hidden(trophy_id);
|
|
int32_t id = 0;
|
|
for (uint32_t gid = 0; gid < context->group_count + 1; gid++) {
|
|
for (uint32_t count = 0; count < context->trophy_count_by_group[gid]; count++, id++) {
|
|
if (trophy_id == id) {
|
|
details->groupId = gid;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string name, detail;
|
|
if (!context->get_trophy_details(trophy_id, name, detail))
|
|
return RET_ERROR(SCE_NP_TROPHY_ERROR_UNSUPPORTED_TROPHY_CONF);
|
|
|
|
memcpy(details->name, name.c_str(), name.size() + 1);
|
|
memcpy(details->description, detail.c_str(), detail.size() + 1);
|
|
}
|
|
|
|
if (data) {
|
|
data->trophyId = trophy_id;
|
|
data->unlocked = context->is_trophy_unlocked(trophy_id);
|
|
data->timestamp.tick = context->unlock_timestamps[trophy_id];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXPORT(int, sceNpTrophyGetTrophyUnlockState, np::trophy::ContextHandle context_handle, SceNpTrophyHandle api_handle,
|
|
np::trophy::TrophyFlagArray *flag_array, uint32_t *count) {
|
|
TRACY_FUNC(sceNpTrophyGetTrophyUnlockState, context_handle, api_handle, flag_array, count);
|
|
if (!emuenv.np.trophy_state.inited) {
|
|
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
if (!flag_array || !count) {
|
|
return SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
|
|
// Get context
|
|
np::trophy::Context *context = get_trophy_context(emuenv.np.trophy_state, context_handle);
|
|
if (!context) {
|
|
return SCE_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
*count = context->trophy_count;
|
|
std::memcpy(flag_array, context->trophy_progress, sizeof(np::trophy::TrophyFlagArray));
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXPORT(int, sceNpTrophyInit, void *opt) {
|
|
TRACY_FUNC(sceNpTrophyInit, opt);
|
|
if (emuenv.np.trophy_state.inited) {
|
|
return SCE_NP_TROPHY_ERROR_ALREADY_INITIALIZED;
|
|
}
|
|
|
|
if (!init(emuenv.np.trophy_state)) {
|
|
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXPORT(int, sceNpTrophyTerm) {
|
|
TRACY_FUNC(sceNpTrophyTerm);
|
|
if (!emuenv.np.trophy_state.inited) {
|
|
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
if (!deinit(emuenv.np.trophy_state)) {
|
|
return SCE_NP_TROPHY_ERROR_ABORT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_trophy_callback(EmuEnvState &emuenv, np::trophy::Context *context, SceNpTrophyID trophy_id) {
|
|
NpTrophyUnlockCallbackData callback_data;
|
|
|
|
if (context->trophy_kinds[trophy_id] == np::trophy::SceNpTrophyGrade::SCE_NP_TROPHY_GRADE_UNKNOWN) {
|
|
// Yes this ID is not present. So return INVALID_ID
|
|
return SCE_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
|
|
}
|
|
|
|
callback_data.np_com_id = fmt::format("{}_{:0>2d}", std::string(context->comm_id.data, 9), context->comm_id.num);
|
|
callback_data.trophy_id = fmt::format("{:0>3d}", trophy_id);
|
|
callback_data.trophy_kind = context->trophy_kinds[trophy_id];
|
|
if (!context->get_trophy_details(trophy_id, callback_data.trophy_name, callback_data.trophy_detail)) {
|
|
return SCE_NP_TROPHY_ERROR_UNSUPPORTED_TROPHY_CONF;
|
|
}
|
|
|
|
LOG_INFO("Trophy unlocked, name: {}, detail: {}, id = {}", callback_data.trophy_name, callback_data.trophy_detail, trophy_id);
|
|
|
|
// Call this async.
|
|
if (emuenv.np.trophy_state.trophy_unlock_callback) {
|
|
uint32_t buf_size = 0;
|
|
|
|
// Make filename
|
|
const std::string trophy_icon_filename = fmt::format("TROP{:0>3d}.PNG", trophy_id);
|
|
context->copy_file_data_from_trophy_file(trophy_icon_filename.c_str(), nullptr, &buf_size);
|
|
|
|
callback_data.icon_buf.resize(buf_size);
|
|
context->copy_file_data_from_trophy_file(trophy_icon_filename.c_str(), &callback_data.icon_buf[0], &buf_size);
|
|
|
|
emuenv.np.trophy_state.trophy_unlock_callback(callback_data);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
EXPORT(int, sceNpTrophyUnlockTrophy, np::trophy::ContextHandle context_handle, SceNpTrophyHandle api_handle,
|
|
SceNpTrophyID trophy_id, SceNpTrophyID *platinum_id) {
|
|
TRACY_FUNC(sceNpTrophyUnlockTrophy, context_handle, api_handle, trophy_id, platinum_id);
|
|
if (!emuenv.np.trophy_state.inited) {
|
|
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
// Trophy should only be in this region
|
|
if (trophy_id < 0 || trophy_id >= np::trophy::MAX_TROPHIES) {
|
|
return SCE_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
|
|
}
|
|
|
|
// Get context
|
|
np::trophy::Context *context = get_trophy_context(emuenv.np.trophy_state, context_handle);
|
|
if (!context) {
|
|
return SCE_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
|
}
|
|
|
|
np::NpTrophyError error;
|
|
if (!context->unlock_trophy(trophy_id, &error)) {
|
|
switch (error) {
|
|
case np::NpTrophyError::TROPHY_PLATINUM_IS_UNBREAKABLE: {
|
|
return SCE_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK;
|
|
}
|
|
|
|
case np::NpTrophyError::TROPHY_ALREADY_UNLOCKED: {
|
|
return SCE_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED;
|
|
}
|
|
|
|
case np::NpTrophyError::TROPHY_ID_INVALID: {
|
|
return SCE_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
|
|
}
|
|
|
|
default:
|
|
return SCE_NP_TROPHY_ERROR_ABORT;
|
|
}
|
|
}
|
|
|
|
if ((context->platinum_trophy_id != np::SCE_NP_TROPHY_INVALID_TROPHY_ID) && (context->total_trophy_unlocked() == (context->trophy_count - 1))) {
|
|
// Force unlock platinum trophy
|
|
context->unlock_trophy(context->platinum_trophy_id, &error, true);
|
|
*platinum_id = context->platinum_trophy_id;
|
|
} else
|
|
*platinum_id = np::SCE_NP_TROPHY_INVALID_TROPHY_ID;
|
|
|
|
const int err = do_trophy_callback(emuenv, context, trophy_id);
|
|
|
|
if (err < 0) {
|
|
return err;
|
|
}
|
|
|
|
if (*platinum_id != np::SCE_NP_TROPHY_INVALID_TROPHY_ID) {
|
|
// Do trophy callback for platinum too! But this time, ignore the error
|
|
do_trophy_callback(emuenv, context, context->platinum_trophy_id);
|
|
}
|
|
|
|
return 0;
|
|
}
|