Vita3K/vita3k/modules/SceNpTrophy/SceNpTrophy.cpp

409 lines
15 KiB
C++

// Vita3K emulator project
// Copyright (C) 2021 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 <np/functions.h>
#include <np/trophy/context.h>
#include <util/log.h>
#include "SceNpTrophy.h"
EXPORT(int, sceNpTrophyAbortHandle) {
STUBBED("Stubbed with SCE_OK");
return 0;
}
EXPORT(int, sceNpTrophyCreateContext, np::trophy::ContextHandle *context, const np::CommunicationID *comm_id,
void *comm_sign, const std::uint64_t options) {
if (!host.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(host.np, &host.io, host.pref_path, comm_id, static_cast<std::uint32_t>(host.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(host.np.trophy_state, *context);
LOG_TRACE("Trophy context for {}_{:0>2d} create successfuly!", ctx_ptr->comm_id.data, ctx_ptr->comm_id.num);
return 0;
}
EXPORT(int, sceNpTrophyCreateHandle, SceNpTrophyHandle *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) {
const bool result = destroy_trophy_context(host.np.trophy_state, handle);
if (!result) {
return SCE_NP_TROPHY_ERROR_INVALID_CONTEXT;
}
return 0;
}
EXPORT(int, sceNpTrophyDestroyHandle, SceNpTrophyHandle handle) {
STUBBED("Stubbed with SCE_OK");
return 0;
}
#define NP_TROPHY_GET_FUNCTION_STARTUP(context_handle) \
if (!host.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(host.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) {
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) {
if (!host.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(host.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((char *)details->title, name.c_str(), name.size() + 1);
memcpy((char *)details->description, detail.c_str(), detail.size() + 1);
for (std::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 (std::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) {
return UNIMPLEMENTED();
}
EXPORT(int, sceNpTrophyGetGroupInfo) {
return UNIMPLEMENTED();
}
EXPORT(int, sceNpTrophyGetTrophyIcon, np::trophy::ContextHandle context_handle, SceNpTrophyHandle api_handle,
SceNpTrophyID trophy_id, void *buffer, SceSize *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) {
if (!host.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(host.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 (auto 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((char *)details->name, name.c_str(), name.size() + 1);
memcpy((char *)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, std::uint32_t *count) {
if (!host.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(host.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) {
if (host.np.trophy_state.inited) {
return SCE_NP_TROPHY_ERROR_ALREADY_INITIALIZED;
}
if (!init(host.np.trophy_state)) {
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
}
return 0;
}
EXPORT(int, sceNpTrophyTerm) {
if (!host.np.trophy_state.inited) {
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
}
if (!deinit(host.np.trophy_state)) {
return SCE_NP_TROPHY_ERROR_ABORT;
}
return 0;
}
static int do_trophy_callback(HostState &host, 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}", context->comm_id.data, 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 (host.np.trophy_state.trophy_unlock_callback) {
std::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);
host.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) {
if (!host.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(host.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;
}
}
*platinum_id = -1; // SCE_NP_TROPHY_INVALID_TROPHY_ID
if ((context->platinum_trophy_id >= 0) && (context->total_trophy_unlocked() == context->trophy_count)) {
// Force unlock platinum trophy
context->unlock_trophy(context->platinum_trophy_id, &error, true);
*platinum_id = context->platinum_trophy_id;
}
const int err = do_trophy_callback(host, context, trophy_id);
if (err < 0) {
return err;
}
if (*platinum_id != -1) {
// Do trophy callback for platinum too! But this time, ignore the error
do_trophy_callback(host, context, context->platinum_trophy_id);
}
return 0;
}
BRIDGE_IMPL(sceNpTrophyAbortHandle)
BRIDGE_IMPL(sceNpTrophyCreateContext)
BRIDGE_IMPL(sceNpTrophyCreateHandle)
BRIDGE_IMPL(sceNpTrophyDestroyContext)
BRIDGE_IMPL(sceNpTrophyDestroyHandle)
BRIDGE_IMPL(sceNpTrophyGetGameIcon)
BRIDGE_IMPL(sceNpTrophyGetGameInfo)
BRIDGE_IMPL(sceNpTrophyGetGroupIcon)
BRIDGE_IMPL(sceNpTrophyGetGroupInfo)
BRIDGE_IMPL(sceNpTrophyGetTrophyIcon)
BRIDGE_IMPL(sceNpTrophyGetTrophyInfo)
BRIDGE_IMPL(sceNpTrophyGetTrophyUnlockState)
BRIDGE_IMPL(sceNpTrophyInit)
BRIDGE_IMPL(sceNpTrophyTerm)
BRIDGE_IMPL(sceNpTrophyUnlockTrophy)