mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-07-10 01:44:42 -06:00
Faking Np commerce (#4618)
* moved np_commerce to dedicated folder * Implemented fake np commerce * we have a store now * shadnet icon * stephen reviews * more stephen reviews
This commit is contained in:
parent
221efa40c7
commit
583e6851fd
@ -628,20 +628,10 @@ set(VDEC_LIB src/core/libraries/videodec/videodec2_impl.cpp
|
|||||||
set(NP_LIBS src/core/libraries/np/np_error.h
|
set(NP_LIBS src/core/libraries/np/np_error.h
|
||||||
src/core/libraries/np/np_common.cpp
|
src/core/libraries/np/np_common.cpp
|
||||||
src/core/libraries/np/np_common.h
|
src/core/libraries/np/np_common.h
|
||||||
src/core/libraries/np/np_commerce.cpp
|
|
||||||
src/core/libraries/np/np_commerce.h
|
|
||||||
src/core/libraries/np/np_manager.cpp
|
src/core/libraries/np/np_manager.cpp
|
||||||
src/core/libraries/np/np_manager.h
|
src/core/libraries/np/np_manager.h
|
||||||
src/core/libraries/np/np_matching2.cpp
|
src/core/libraries/np/np_matching2.cpp
|
||||||
src/core/libraries/np/np_matching2.h
|
src/core/libraries/np/np_matching2.h
|
||||||
src/core/libraries/np/np_signaling/np_signaling.cpp
|
|
||||||
src/core/libraries/np/np_signaling/np_signaling.h
|
|
||||||
src/core/libraries/np/np_signaling/np_signaling_state.cpp
|
|
||||||
src/core/libraries/np/np_signaling/np_signaling_state.h
|
|
||||||
src/core/libraries/np/np_signaling/np_signaling_helpers.cpp
|
|
||||||
src/core/libraries/np/np_signaling/np_signaling_helpers.h
|
|
||||||
src/core/libraries/np/np_signaling/np_signaling_stubs.cpp
|
|
||||||
src/core/libraries/np/np_signaling/np_signaling_stubs.h
|
|
||||||
src/core/libraries/np/np_trophy.cpp
|
src/core/libraries/np/np_trophy.cpp
|
||||||
src/core/libraries/np/np_trophy.h
|
src/core/libraries/np/np_trophy.h
|
||||||
src/core/libraries/np/np_tus.cpp
|
src/core/libraries/np/np_tus.cpp
|
||||||
@ -654,10 +644,6 @@ set(NP_LIBS src/core/libraries/np/np_error.h
|
|||||||
src/core/libraries/np/np_party.h
|
src/core/libraries/np/np_party.h
|
||||||
src/core/libraries/np/np_auth.cpp
|
src/core/libraries/np/np_auth.cpp
|
||||||
src/core/libraries/np/np_auth.h
|
src/core/libraries/np/np_auth.h
|
||||||
src/core/libraries/np/np_profile_dialog/np_profile_dialog.cpp
|
|
||||||
src/core/libraries/np/np_profile_dialog/np_profile_dialog.h
|
|
||||||
src/core/libraries/np/np_profile_dialog/np_profile_dialog_ui.cpp
|
|
||||||
src/core/libraries/np/np_profile_dialog/np_profile_dialog_ui.h
|
|
||||||
src/core/libraries/np/np_sns_facebook_dialog.cpp
|
src/core/libraries/np/np_sns_facebook_dialog.cpp
|
||||||
src/core/libraries/np/np_sns_facebook_dialog.h
|
src/core/libraries/np/np_sns_facebook_dialog.h
|
||||||
src/core/libraries/np/np_partner.cpp
|
src/core/libraries/np/np_partner.cpp
|
||||||
@ -665,9 +651,25 @@ set(NP_LIBS src/core/libraries/np/np_error.h
|
|||||||
src/core/libraries/np/object_manager.h
|
src/core/libraries/np/object_manager.h
|
||||||
src/core/libraries/np/np_handler.cpp
|
src/core/libraries/np/np_handler.cpp
|
||||||
src/core/libraries/np/np_handler.h
|
src/core/libraries/np/np_handler.h
|
||||||
|
src/core/libraries/np/np_commerce/np_commerce.cpp
|
||||||
|
src/core/libraries/np/np_commerce/np_commerce.h
|
||||||
|
src/core/libraries/np/np_commerce/np_commerce_ui.cpp
|
||||||
|
src/core/libraries/np/np_commerce/np_commerce_ui.h
|
||||||
|
src/core/libraries/np/np_profile_dialog/np_profile_dialog.cpp
|
||||||
|
src/core/libraries/np/np_profile_dialog/np_profile_dialog.h
|
||||||
|
src/core/libraries/np/np_profile_dialog/np_profile_dialog_ui.cpp
|
||||||
|
src/core/libraries/np/np_profile_dialog/np_profile_dialog_ui.h
|
||||||
src/core/libraries/np/np_score/np_score.cpp
|
src/core/libraries/np/np_score/np_score.cpp
|
||||||
src/core/libraries/np/np_score/np_score.h
|
src/core/libraries/np/np_score/np_score.h
|
||||||
src/core/libraries/np/np_score/np_score_ctx.h
|
src/core/libraries/np/np_score/np_score_ctx.h
|
||||||
|
src/core/libraries/np/np_signaling/np_signaling.cpp
|
||||||
|
src/core/libraries/np/np_signaling/np_signaling.h
|
||||||
|
src/core/libraries/np/np_signaling/np_signaling_state.cpp
|
||||||
|
src/core/libraries/np/np_signaling/np_signaling_state.h
|
||||||
|
src/core/libraries/np/np_signaling/np_signaling_helpers.cpp
|
||||||
|
src/core/libraries/np/np_signaling/np_signaling_helpers.h
|
||||||
|
src/core/libraries/np/np_signaling/np_signaling_stubs.cpp
|
||||||
|
src/core/libraries/np/np_signaling/np_signaling_stubs.h
|
||||||
src/core/libraries/np/np_web_api/np_web_api.cpp
|
src/core/libraries/np/np_web_api/np_web_api.cpp
|
||||||
src/core/libraries/np/np_web_api/np_web_api.h
|
src/core/libraries/np/np_web_api/np_web_api.h
|
||||||
src/core/libraries/np/np_web_api/np_web_api_internal.cpp
|
src/core/libraries/np/np_web_api/np_web_api_internal.cpp
|
||||||
|
|||||||
@ -35,7 +35,7 @@
|
|||||||
#include "core/libraries/network/ssl.h"
|
#include "core/libraries/network/ssl.h"
|
||||||
#include "core/libraries/network/ssl2.h"
|
#include "core/libraries/network/ssl2.h"
|
||||||
#include "core/libraries/np/np_auth.h"
|
#include "core/libraries/np/np_auth.h"
|
||||||
#include "core/libraries/np/np_commerce.h"
|
#include "core/libraries/np/np_commerce/np_commerce.h"
|
||||||
#include "core/libraries/np/np_common.h"
|
#include "core/libraries/np/np_common.h"
|
||||||
#include "core/libraries/np/np_manager.h"
|
#include "core/libraries/np/np_manager.h"
|
||||||
#include "core/libraries/np/np_matching2.h"
|
#include "core/libraries/np/np_matching2.h"
|
||||||
|
|||||||
@ -1,129 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include "common/logging/log.h"
|
|
||||||
#include "core/libraries/error_codes.h"
|
|
||||||
#include "core/libraries/libs.h"
|
|
||||||
#include "core/libraries/system/commondialog.h"
|
|
||||||
|
|
||||||
namespace Libraries::Np::NpCommerce {
|
|
||||||
|
|
||||||
using CommonDialog::Error;
|
|
||||||
using CommonDialog::Result;
|
|
||||||
using CommonDialog::Status;
|
|
||||||
|
|
||||||
static Status g_dialog_status = Status::NONE;
|
|
||||||
static Result g_dialog_result = Result::OK;
|
|
||||||
|
|
||||||
s32 PS4_SYSV_ABI sceNpCommerceDialogClose() {
|
|
||||||
LOG_INFO(Lib_NpCommerce, "called");
|
|
||||||
if (g_dialog_status == Status::NONE) {
|
|
||||||
return static_cast<s32>(Error::NOT_INITIALIZED);
|
|
||||||
}
|
|
||||||
if (g_dialog_status != Status::FINISHED) {
|
|
||||||
return static_cast<s32>(Error::NOT_FINISHED);
|
|
||||||
}
|
|
||||||
g_dialog_status = Status::INITIALIZED;
|
|
||||||
return ORBIS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 PS4_SYSV_ABI sceNpCommerceDialogGetResult(s32* result) {
|
|
||||||
LOG_INFO(Lib_NpCommerce, "called");
|
|
||||||
if (result == nullptr) {
|
|
||||||
return static_cast<s32>(Error::ARG_NULL);
|
|
||||||
}
|
|
||||||
if (g_dialog_status != Status::FINISHED) {
|
|
||||||
return static_cast<s32>(Error::NOT_FINISHED);
|
|
||||||
}
|
|
||||||
*result = static_cast<s32>(g_dialog_result);
|
|
||||||
return ORBIS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
s8 PS4_SYSV_ABI sceNpCommerceDialogGetStatus() {
|
|
||||||
LOG_DEBUG(Lib_NpCommerce, "called, status = {}", static_cast<u32>(g_dialog_status));
|
|
||||||
return static_cast<s8>(g_dialog_status);
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 PS4_SYSV_ABI sceNpCommerceDialogInitialize() {
|
|
||||||
LOG_INFO(Lib_NpCommerce, "called");
|
|
||||||
if (g_dialog_status != Status::NONE) {
|
|
||||||
return static_cast<s32>(Error::ALREADY_INITIALIZED);
|
|
||||||
}
|
|
||||||
g_dialog_status = Status::INITIALIZED;
|
|
||||||
return ORBIS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 PS4_SYSV_ABI sceNpCommerceDialogInitializeInternal() {
|
|
||||||
LOG_INFO(Lib_NpCommerce, "called");
|
|
||||||
return sceNpCommerceDialogInitialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
s16 PS4_SYSV_ABI sceNpCommerceDialogOpen(s64 check) {
|
|
||||||
LOG_INFO(Lib_NpCommerce, "called, check = {}", check);
|
|
||||||
if (g_dialog_status != Status::INITIALIZED) {
|
|
||||||
LOG_WARNING(Lib_NpCommerce, "Dialog not initialized");
|
|
||||||
return ORBIS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_dialog_status = Status::FINISHED;
|
|
||||||
g_dialog_result = Result::USER_CANCELED;
|
|
||||||
return ORBIS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 PS4_SYSV_ABI sceNpCommerceDialogTerminate() {
|
|
||||||
LOG_INFO(Lib_NpCommerce, "called");
|
|
||||||
if (g_dialog_status == Status::NONE) {
|
|
||||||
return static_cast<s32>(Error::NOT_INITIALIZED);
|
|
||||||
}
|
|
||||||
if (g_dialog_status == Status::RUNNING) {
|
|
||||||
return static_cast<s32>(Error::NOT_FINISHED);
|
|
||||||
}
|
|
||||||
g_dialog_status = Status::NONE;
|
|
||||||
return ORBIS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 PS4_SYSV_ABI sceNpCommerceDialogUpdateStatus() {
|
|
||||||
LOG_DEBUG(Lib_NpCommerce, "called, status = {}", static_cast<u32>(g_dialog_status));
|
|
||||||
return static_cast<s32>(g_dialog_status);
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 PS4_SYSV_ABI sceNpCommerceHidePsStoreIcon() {
|
|
||||||
LOG_ERROR(Lib_NpCommerce, "(STUBBED) called");
|
|
||||||
return ORBIS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 PS4_SYSV_ABI sceNpCommerceSetPsStoreIconLayout(s32 layout) {
|
|
||||||
LOG_ERROR(Lib_NpCommerce, "(STUBBED) called");
|
|
||||||
return ORBIS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 PS4_SYSV_ABI sceNpCommerceShowPsStoreIcon(s16 icon) {
|
|
||||||
LOG_ERROR(Lib_NpCommerce, "(STUBBED) called");
|
|
||||||
return ORBIS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
|
||||||
LIB_FUNCTION("NU3ckGHMFXo", "libSceNpCommerce", 1, "libSceNpCommerce",
|
|
||||||
sceNpCommerceDialogClose);
|
|
||||||
LIB_FUNCTION("r42bWcQbtZY", "libSceNpCommerce", 1, "libSceNpCommerce",
|
|
||||||
sceNpCommerceDialogGetResult);
|
|
||||||
LIB_FUNCTION("CCbC+lqqvF0", "libSceNpCommerce", 1, "libSceNpCommerce",
|
|
||||||
sceNpCommerceDialogGetStatus);
|
|
||||||
LIB_FUNCTION("0aR2aWmQal4", "libSceNpCommerce", 1, "libSceNpCommerce",
|
|
||||||
sceNpCommerceDialogInitialize);
|
|
||||||
LIB_FUNCTION("9ZiLXAGG5rg", "libSceNpCommerce", 1, "libSceNpCommerce",
|
|
||||||
sceNpCommerceDialogInitializeInternal);
|
|
||||||
LIB_FUNCTION("DfSCDRA3EjY", "libSceNpCommerce", 1, "libSceNpCommerce", sceNpCommerceDialogOpen);
|
|
||||||
LIB_FUNCTION("m-I92Ab50W8", "libSceNpCommerce", 1, "libSceNpCommerce",
|
|
||||||
sceNpCommerceDialogTerminate);
|
|
||||||
LIB_FUNCTION("LR5cwFMMCVE", "libSceNpCommerce", 1, "libSceNpCommerce",
|
|
||||||
sceNpCommerceDialogUpdateStatus);
|
|
||||||
LIB_FUNCTION("dsqCVsNM0Zg", "libSceNpCommerce", 1, "libSceNpCommerce",
|
|
||||||
sceNpCommerceHidePsStoreIcon);
|
|
||||||
LIB_FUNCTION("uKTDW8hk-ts", "libSceNpCommerce", 1, "libSceNpCommerce",
|
|
||||||
sceNpCommerceSetPsStoreIconLayout);
|
|
||||||
LIB_FUNCTION("DHmwsa6S8Tc", "libSceNpCommerce", 1, "libSceNpCommerce",
|
|
||||||
sceNpCommerceShowPsStoreIcon);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Libraries::Np::NpCommerce
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "common/types.h"
|
|
||||||
|
|
||||||
namespace Core::Loader {
|
|
||||||
class SymbolsResolver;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Libraries::Np::NpCommerce {
|
|
||||||
|
|
||||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
|
||||||
|
|
||||||
} // namespace Libraries::Np::NpCommerce
|
|
||||||
192
src/core/libraries/np/np_commerce/np_commerce.cpp
Normal file
192
src/core/libraries/np/np_commerce/np_commerce.cpp
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstring>
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "core/libraries/error_codes.h"
|
||||||
|
#include "core/libraries/libs.h"
|
||||||
|
#include "core/libraries/np/np_commerce/np_commerce_ui.h"
|
||||||
|
#include "core/libraries/system/commondialog.h"
|
||||||
|
#include "core/libraries/system/userservice.h"
|
||||||
|
#include "np_commerce.h"
|
||||||
|
|
||||||
|
namespace Libraries::Np::NpCommerce {
|
||||||
|
|
||||||
|
static Status g_dialog_status = Status::NONE;
|
||||||
|
static s32 g_result_code = ORBIS_COMMON_DIALOG_RESULT_USER_CANCELED;
|
||||||
|
static CommerceDialogState g_state;
|
||||||
|
static CommerceDialogUi g_dialog;
|
||||||
|
|
||||||
|
s32 PS4_SYSV_ABI sceNpCommerceDialogInitialize() {
|
||||||
|
LOG_INFO(Lib_NpCommerce, "called");
|
||||||
|
if (g_dialog_status != Status::NONE) {
|
||||||
|
return static_cast<s32>(Error::ALREADY_INITIALIZED);
|
||||||
|
}
|
||||||
|
g_dialog_status = Status::INITIALIZED;
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 PS4_SYSV_ABI sceNpCommerceDialogTerminate() {
|
||||||
|
LOG_INFO(Lib_NpCommerce, "called");
|
||||||
|
if (g_dialog_status == Status::NONE) {
|
||||||
|
return static_cast<s32>(Error::NOT_INITIALIZED);
|
||||||
|
}
|
||||||
|
g_dialog.Finish(CommerceResult::OK);
|
||||||
|
g_dialog_status = Status::NONE;
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 PS4_SYSV_ABI sceNpCommerceDialogClose() {
|
||||||
|
LOG_INFO(Lib_NpCommerce, "called");
|
||||||
|
switch (g_dialog_status) {
|
||||||
|
case Status::NONE:
|
||||||
|
return static_cast<s32>(Error::NOT_INITIALIZED);
|
||||||
|
case Status::INITIALIZED:
|
||||||
|
return static_cast<s32>(Error::NOT_RUNNING);
|
||||||
|
case Status::FINISHED:
|
||||||
|
return static_cast<s32>(Error::ALREADY_CLOSE);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
g_dialog.Finish(CommerceResult::OK);
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
s8 PS4_SYSV_ABI sceNpCommerceDialogGetStatus() {
|
||||||
|
LOG_DEBUG(Lib_NpCommerce, "called, status = {}", static_cast<u32>(g_dialog_status));
|
||||||
|
return static_cast<s8>(g_dialog_status);
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 PS4_SYSV_ABI sceNpCommerceDialogInitializeInternal() {
|
||||||
|
LOG_INFO(Lib_NpCommerce, "called");
|
||||||
|
return sceNpCommerceDialogInitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 PS4_SYSV_ABI sceNpCommerceDialogUpdateStatus() {
|
||||||
|
LOG_DEBUG(Lib_NpCommerce, "called, status = {}", static_cast<u32>(g_dialog_status));
|
||||||
|
return static_cast<s32>(g_dialog_status);
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 PS4_SYSV_ABI sceNpCommerceDialogGetResult(OrbisNpCommerceDialogResult* result) {
|
||||||
|
if (result == nullptr) {
|
||||||
|
return static_cast<s32>(Error::ARG_NULL);
|
||||||
|
}
|
||||||
|
if (g_dialog_status != Status::FINISHED) {
|
||||||
|
return static_cast<s32>(Error::NOT_FINISHED);
|
||||||
|
}
|
||||||
|
result->result = g_result_code;
|
||||||
|
// authorized is only meaningful in PLUS mode
|
||||||
|
result->authorized = g_state.mode == CommerceMode::PLUS &&
|
||||||
|
g_result_code == ORBIS_NP_COMMERCE_DIALOG_RESULT_PURCHASED;
|
||||||
|
result->userData = g_state.user_data;
|
||||||
|
LOG_INFO(Lib_NpCommerce, "result={} authorized={}", result->result, result->authorized);
|
||||||
|
return g_result_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 PS4_SYSV_ABI sceNpCommerceDialogOpen(const OrbisNpCommerceDialogParam* param) {
|
||||||
|
LOG_INFO(Lib_NpCommerce, "called, param = {}", static_cast<const void*>(param));
|
||||||
|
if (g_dialog_status != Status::INITIALIZED && g_dialog_status != Status::FINISHED) {
|
||||||
|
LOG_WARNING(Lib_NpCommerce, "Open in bad state {}", static_cast<u32>(g_dialog_status));
|
||||||
|
return static_cast<s32>(Error::NOT_INITIALIZED);
|
||||||
|
}
|
||||||
|
if (param == nullptr) {
|
||||||
|
return static_cast<s32>(Error::ARG_NULL);
|
||||||
|
}
|
||||||
|
if (param->mode < ORBIS_NP_COMMERCE_DIALOG_MODE_CATEGORY ||
|
||||||
|
param->mode > ORBIS_NP_COMMERCE_DIALOG_MODE_PLUS) {
|
||||||
|
LOG_ERROR(Lib_NpCommerce, "SceNpCommerceDialog Parameter Error (mode={})", param->mode);
|
||||||
|
g_result_code = static_cast<s32>(Error::PARAM_INVALID);
|
||||||
|
g_dialog_status = Status::FINISHED;
|
||||||
|
return static_cast<s32>(Error::PARAM_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_state = CommerceDialogState{};
|
||||||
|
g_state.mode = static_cast<CommerceMode>(param->mode);
|
||||||
|
g_state.user_id = param->userId;
|
||||||
|
g_state.service_label = param->serviceLabel;
|
||||||
|
g_state.user_data = param->userData;
|
||||||
|
// features is only meaningful in PLUS mode
|
||||||
|
g_state.features = g_state.mode == CommerceMode::PLUS ? param->features : 0;
|
||||||
|
|
||||||
|
// Resolve the calling user's name for display
|
||||||
|
char name_buf[Libraries::UserService::ORBIS_USER_SERVICE_MAX_USER_NAME_LENGTH + 1] = {};
|
||||||
|
if (Libraries::UserService::sceUserServiceGetUserName(param->userId, name_buf,
|
||||||
|
sizeof(name_buf)) >= 0) {
|
||||||
|
g_state.username = name_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param->numTargets > ORBIS_NP_COMMERCE_DIALOG_NUM_TARGETS_MAX) {
|
||||||
|
LOG_WARNING(Lib_NpCommerce, "numTargets {} exceeds max {}, clamping", param->numTargets,
|
||||||
|
ORBIS_NP_COMMERCE_DIALOG_NUM_TARGETS_MAX);
|
||||||
|
}
|
||||||
|
const u32 count = std::min(param->numTargets, ORBIS_NP_COMMERCE_DIALOG_NUM_TARGETS_MAX);
|
||||||
|
if (count > 0 && param->targets != nullptr) {
|
||||||
|
g_state.targets.reserve(count);
|
||||||
|
for (u32 i = 0; i < count; ++i) {
|
||||||
|
const char* label = param->targets[i];
|
||||||
|
if (label != nullptr) {
|
||||||
|
g_state.targets.emplace_back(label, strnlen(label, MAX_LABEL_LEN));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG_INFO(Lib_NpCommerce,
|
||||||
|
"open: mode={} user_id={} serviceLabel={:#x} numTargets={} features={:#x} first='{}'",
|
||||||
|
param->mode, g_state.user_id, g_state.service_label, param->numTargets,
|
||||||
|
g_state.features, g_state.targets.empty() ? "" : g_state.targets.front());
|
||||||
|
|
||||||
|
g_dialog_status = Status::RUNNING;
|
||||||
|
g_result_code = ORBIS_COMMON_DIALOG_RESULT_USER_CANCELED;
|
||||||
|
g_dialog = CommerceDialogUi(&g_state, &g_dialog_status, &g_result_code);
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 PS4_SYSV_ABI sceNpCommerceHidePsStoreIcon() {
|
||||||
|
LOG_INFO(Lib_NpCommerce, "called");
|
||||||
|
PsStoreIconLayer::Instance().Hide();
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 PS4_SYSV_ABI sceNpCommerceSetPsStoreIconLayout(s32 layout) {
|
||||||
|
LOG_INFO(Lib_NpCommerce, "called, layout = {}", layout);
|
||||||
|
if (layout < 0 || layout > 2) {
|
||||||
|
LOG_WARNING(Lib_NpCommerce, "invalid layout {}, ignoring", layout);
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
PsStoreIconLayer::Instance().SetLayout(static_cast<PsStoreIconLayout>(layout));
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 PS4_SYSV_ABI sceNpCommerceShowPsStoreIcon(s32 pos) {
|
||||||
|
LOG_INFO(Lib_NpCommerce, "called, pos = {}", pos);
|
||||||
|
const auto p =
|
||||||
|
(pos >= 0 && pos <= 2) ? static_cast<PsStoreIconPos>(pos) : PsStoreIconPos::CENTER;
|
||||||
|
PsStoreIconLayer::Instance().Show(p);
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||||
|
LIB_FUNCTION("NU3ckGHMFXo", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||||
|
sceNpCommerceDialogClose);
|
||||||
|
LIB_FUNCTION("r42bWcQbtZY", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||||
|
sceNpCommerceDialogGetResult);
|
||||||
|
LIB_FUNCTION("CCbC+lqqvF0", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||||
|
sceNpCommerceDialogGetStatus);
|
||||||
|
LIB_FUNCTION("0aR2aWmQal4", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||||
|
sceNpCommerceDialogInitialize);
|
||||||
|
LIB_FUNCTION("9ZiLXAGG5rg", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||||
|
sceNpCommerceDialogInitializeInternal);
|
||||||
|
LIB_FUNCTION("DfSCDRA3EjY", "libSceNpCommerce", 1, "libSceNpCommerce", sceNpCommerceDialogOpen);
|
||||||
|
LIB_FUNCTION("m-I92Ab50W8", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||||
|
sceNpCommerceDialogTerminate);
|
||||||
|
LIB_FUNCTION("LR5cwFMMCVE", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||||
|
sceNpCommerceDialogUpdateStatus);
|
||||||
|
LIB_FUNCTION("dsqCVsNM0Zg", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||||
|
sceNpCommerceHidePsStoreIcon);
|
||||||
|
LIB_FUNCTION("uKTDW8hk-ts", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||||
|
sceNpCommerceSetPsStoreIconLayout);
|
||||||
|
LIB_FUNCTION("DHmwsa6S8Tc", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||||
|
sceNpCommerceShowPsStoreIcon);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Libraries::Np::NpCommerce
|
||||||
61
src/core/libraries/np/np_commerce/np_commerce.h
Normal file
61
src/core/libraries/np/np_commerce/np_commerce.h
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/types.h"
|
||||||
|
|
||||||
|
namespace Core::Loader {
|
||||||
|
class SymbolsResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Libraries::Np::NpCommerce {
|
||||||
|
|
||||||
|
using CommonDialog::BaseParam;
|
||||||
|
using CommonDialog::Error;
|
||||||
|
using CommonDialog::Status;
|
||||||
|
|
||||||
|
static constexpr u64 MAX_LABEL_LEN = 128;
|
||||||
|
static constexpr u32 ORBIS_NP_COMMERCE_DIALOG_NUM_TARGETS_MAX = 10;
|
||||||
|
|
||||||
|
struct OrbisNpCommerceDialogParam {
|
||||||
|
BaseParam baseParam;
|
||||||
|
s32 size;
|
||||||
|
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||||
|
s32 mode;
|
||||||
|
u32 serviceLabel;
|
||||||
|
const char* const* targets;
|
||||||
|
u32 numTargets;
|
||||||
|
s32 : 32;
|
||||||
|
u64 features;
|
||||||
|
void* userData;
|
||||||
|
u8 reserved[32];
|
||||||
|
};
|
||||||
|
static_assert(sizeof(OrbisNpCommerceDialogParam) == 0x80);
|
||||||
|
|
||||||
|
struct OrbisNpCommerceDialogResult {
|
||||||
|
s32 result;
|
||||||
|
bool authorized;
|
||||||
|
char : 8;
|
||||||
|
short : 16;
|
||||||
|
void* userData;
|
||||||
|
u8 reserved[32];
|
||||||
|
};
|
||||||
|
static_assert(sizeof(OrbisNpCommerceDialogResult) == 0x30);
|
||||||
|
|
||||||
|
// OrbisNpCommerceDialogResult return codes
|
||||||
|
static constexpr s32 ORBIS_COMMON_DIALOG_RESULT_OK = 0;
|
||||||
|
static constexpr s32 ORBIS_COMMON_DIALOG_RESULT_USER_CANCELED = 1;
|
||||||
|
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_RESULT_PURCHASED = 2;
|
||||||
|
|
||||||
|
// OrbisNpCommerceDialogMode
|
||||||
|
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_MODE_CATEGORY = 0; // Browse category mode
|
||||||
|
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_MODE_PRODUCT = 1; // Browse product mode
|
||||||
|
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_MODE_PRODUCT_CODE = 2; // promotion code mode
|
||||||
|
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_MODE_CHECKOUT = 3; // Checkout mode
|
||||||
|
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_MODE_DOWNLOADLIST = 4; // Download mode
|
||||||
|
static constexpr s32 ORBIS_NP_COMMERCE_DIALOG_MODE_PLUS = 5; // PlayStation Plus mode
|
||||||
|
|
||||||
|
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||||
|
|
||||||
|
} // namespace Libraries::Np::NpCommerce
|
||||||
358
src/core/libraries/np/np_commerce/np_commerce_ui.cpp
Normal file
358
src/core/libraries/np/np_commerce/np_commerce_ui.cpp
Normal file
@ -0,0 +1,358 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <imgui.h>
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "core/libraries/np/np_commerce/np_commerce_ui.h"
|
||||||
|
#include "imgui/imgui_std.h"
|
||||||
|
|
||||||
|
using namespace ImGui;
|
||||||
|
using namespace Libraries::CommonDialog;
|
||||||
|
using namespace Libraries::Np::NpCommerce;
|
||||||
|
|
||||||
|
static constexpr ImVec2 BUTTON_SIZE{160.0f, 30.0f};
|
||||||
|
|
||||||
|
static const char* ModeTitle(CommerceMode m) {
|
||||||
|
switch (m) {
|
||||||
|
case CommerceMode::CATEGORY:
|
||||||
|
return "shadNet Store";
|
||||||
|
case CommerceMode::PRODUCT:
|
||||||
|
return "Product";
|
||||||
|
case CommerceMode::PRODUCT_CODE:
|
||||||
|
return "Redeem Code";
|
||||||
|
case CommerceMode::CHECKOUT:
|
||||||
|
return "Purchase Confirmation";
|
||||||
|
case CommerceMode::DOWNLOADLIST:
|
||||||
|
return "My Downloads";
|
||||||
|
case CommerceMode::PLUS:
|
||||||
|
return "shadNet Plus";
|
||||||
|
default:
|
||||||
|
return "shadNet Store";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* ModeActionLabel(CommerceMode m) {
|
||||||
|
switch (m) {
|
||||||
|
case CommerceMode::CATEGORY:
|
||||||
|
case CommerceMode::PRODUCT:
|
||||||
|
case CommerceMode::CHECKOUT:
|
||||||
|
return "Simulate purchase";
|
||||||
|
case CommerceMode::PRODUCT_CODE:
|
||||||
|
return "Redeem code";
|
||||||
|
case CommerceMode::PLUS:
|
||||||
|
return "Join SN Plus";
|
||||||
|
case CommerceMode::DOWNLOADLIST:
|
||||||
|
default:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* ModeEmptyText(CommerceMode m) {
|
||||||
|
switch (m) {
|
||||||
|
case CommerceMode::CATEGORY:
|
||||||
|
return "Top store category.";
|
||||||
|
case CommerceMode::PRODUCT_CODE:
|
||||||
|
return "Enter a promotion code.";
|
||||||
|
case CommerceMode::DOWNLOADLIST:
|
||||||
|
return "All downloadable content.";
|
||||||
|
case CommerceMode::PLUS:
|
||||||
|
return "Join shadNet Plus.";
|
||||||
|
default:
|
||||||
|
return "No product specified.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* ModeDescription(CommerceMode m) {
|
||||||
|
switch (m) {
|
||||||
|
case CommerceMode::CATEGORY:
|
||||||
|
return "Browsing a store category.";
|
||||||
|
case CommerceMode::PRODUCT:
|
||||||
|
return "Viewing a product.";
|
||||||
|
case CommerceMode::PRODUCT_CODE:
|
||||||
|
return "Redeeming a promotion code.";
|
||||||
|
case CommerceMode::CHECKOUT:
|
||||||
|
return "Confirming a purchase.";
|
||||||
|
case CommerceMode::DOWNLOADLIST:
|
||||||
|
return "Viewing owned downloads.";
|
||||||
|
case CommerceMode::PLUS:
|
||||||
|
return "Joining shadNet Plus.";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string DescribeFeatures(u64 features) {
|
||||||
|
if (features == 0) {
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
std::string out;
|
||||||
|
if (features & 0x1) { // ORBIS_NP_PLUS_FEATURE_REALTIME_MULTIPLAY
|
||||||
|
out += "Realtime Multiplay";
|
||||||
|
}
|
||||||
|
if (out.empty()) {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decomposed CHECKOUT/DOWNLOADLIST element "[serviceLabel:]product[-sku]".
|
||||||
|
struct ParsedTarget {
|
||||||
|
std::string service;
|
||||||
|
std::string product;
|
||||||
|
std::string sku;
|
||||||
|
};
|
||||||
|
|
||||||
|
static ParsedTarget ParseTarget(const std::string& t) {
|
||||||
|
ParsedTarget out;
|
||||||
|
std::string s = t;
|
||||||
|
if (const auto colon = s.find(':'); colon != std::string::npos) {
|
||||||
|
out.service = s.substr(0, colon);
|
||||||
|
s = s.substr(colon + 1);
|
||||||
|
}
|
||||||
|
if (const auto dash = s.find('-'); dash != std::string::npos) {
|
||||||
|
out.product = s.substr(0, dash);
|
||||||
|
out.sku = s.substr(dash + 1);
|
||||||
|
} else {
|
||||||
|
out.product = s;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommerceDialogUi::CommerceDialogUi(CommerceDialogState* state, Status* status, s32* result)
|
||||||
|
: state(state), status(status), result(result) {
|
||||||
|
if (status && *status == Status::RUNNING) {
|
||||||
|
first_render = true;
|
||||||
|
AddLayer(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CommerceDialogUi::~CommerceDialogUi() {
|
||||||
|
Finish(CommerceResult::USER_CANCELED);
|
||||||
|
}
|
||||||
|
|
||||||
|
CommerceDialogUi::CommerceDialogUi(CommerceDialogUi&& other) noexcept
|
||||||
|
: Layer(other), first_render(other.first_render), state(other.state), status(other.status),
|
||||||
|
result(other.result) {
|
||||||
|
other.state = nullptr;
|
||||||
|
other.status = nullptr;
|
||||||
|
other.result = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommerceDialogUi& CommerceDialogUi::operator=(CommerceDialogUi&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
first_render = other.first_render;
|
||||||
|
state = other.state;
|
||||||
|
status = other.status;
|
||||||
|
result = other.result;
|
||||||
|
other.state = nullptr;
|
||||||
|
other.status = nullptr;
|
||||||
|
other.result = nullptr;
|
||||||
|
if (status && *status == Status::RUNNING) {
|
||||||
|
first_render = true;
|
||||||
|
AddLayer(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommerceDialogUi::Finish(CommerceResult r) {
|
||||||
|
if (result) {
|
||||||
|
*result = static_cast<s32>(r);
|
||||||
|
}
|
||||||
|
if (status) {
|
||||||
|
*status = Status::FINISHED;
|
||||||
|
}
|
||||||
|
state = nullptr;
|
||||||
|
status = nullptr;
|
||||||
|
result = nullptr;
|
||||||
|
RemoveLayer(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommerceDialogUi::Draw() {
|
||||||
|
if (status == nullptr || *status != Status::RUNNING || state == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto& io = GetIO();
|
||||||
|
const ImVec2 window_size{std::min(io.DisplaySize.x, 520.0f),
|
||||||
|
std::min(io.DisplaySize.y, 380.0f)};
|
||||||
|
|
||||||
|
CentralizeNextWindow();
|
||||||
|
SetNextWindowSize(window_size);
|
||||||
|
SetNextWindowCollapsed(false);
|
||||||
|
if (first_render || !io.NavActive) {
|
||||||
|
SetNextWindowFocus();
|
||||||
|
}
|
||||||
|
KeepNavHighlight();
|
||||||
|
if (Begin("CommerceDialog##NpCommerceDialog", nullptr,
|
||||||
|
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) {
|
||||||
|
std::string title = ModeTitle(state->mode);
|
||||||
|
if (state->targets.size() == 1 &&
|
||||||
|
(state->mode == CommerceMode::PRODUCT || state->mode == CommerceMode::CATEGORY)) {
|
||||||
|
title += ": " + state->targets.front();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const float tw = CalcTextSize(title.c_str()).x;
|
||||||
|
SetCursorPosX(std::max(10.0f, (GetWindowSize().x - tw) * 0.5f));
|
||||||
|
TextUnformatted(title.c_str());
|
||||||
|
}
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
TextWrapped("%s", ModeDescription(state->mode));
|
||||||
|
Spacing();
|
||||||
|
|
||||||
|
// Calling user.
|
||||||
|
if (!state->username.empty()) {
|
||||||
|
Text("User: %s (id %d)", state->username.c_str(), state->user_id);
|
||||||
|
} else {
|
||||||
|
Text("User id: %d", state->user_id);
|
||||||
|
}
|
||||||
|
// Service label is meaningful for category/product/checkout/downloadlist.
|
||||||
|
if (state->mode != CommerceMode::PRODUCT_CODE && state->mode != CommerceMode::PLUS) {
|
||||||
|
Text("Service label: %u", state->service_label);
|
||||||
|
}
|
||||||
|
// PS Plus feature only applies in PLUS mode.
|
||||||
|
if (state->mode == CommerceMode::PLUS) {
|
||||||
|
Text("PS Plus feature: %s", DescribeFeatures(state->features).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacing();
|
||||||
|
if (state->targets.empty()) {
|
||||||
|
TextWrapped("%s", ModeEmptyText(state->mode));
|
||||||
|
} else {
|
||||||
|
const bool checkout_form =
|
||||||
|
state->mode == CommerceMode::CHECKOUT || state->mode == CommerceMode::DOWNLOADLIST;
|
||||||
|
TextDisabled(state->targets.size() == 1 ? "Target:" : "Targets:");
|
||||||
|
for (const auto& t : state->targets) {
|
||||||
|
if (checkout_form) {
|
||||||
|
const ParsedTarget pt = ParseTarget(t);
|
||||||
|
if (!pt.sku.empty() || !pt.service.empty()) {
|
||||||
|
BulletText("Product %s", pt.product.c_str());
|
||||||
|
if (!pt.sku.empty()) {
|
||||||
|
Indent();
|
||||||
|
Text("SKU: %s", pt.sku.c_str());
|
||||||
|
Unindent();
|
||||||
|
}
|
||||||
|
if (!pt.service.empty()) {
|
||||||
|
Indent();
|
||||||
|
Text("Service: %s", pt.service.c_str());
|
||||||
|
Unindent();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BulletText("%s", t.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacing();
|
||||||
|
|
||||||
|
const char* action = ModeActionLabel(state->mode);
|
||||||
|
const auto ws = GetWindowSize();
|
||||||
|
const float n = action ? 2.0f : 1.0f;
|
||||||
|
SetCursorPos({ws.x / 2.0f - (BUTTON_SIZE.x * n + 10.0f * (n - 1.0f)) / 2.0f,
|
||||||
|
ws.y - 10.0f - BUTTON_SIZE.y});
|
||||||
|
BeginGroup();
|
||||||
|
if (action) {
|
||||||
|
if (Button(action, BUTTON_SIZE)) {
|
||||||
|
LOG_INFO(Lib_NpCommerce, "CommerceDialog: action '{}' mode={} target='{}'", action,
|
||||||
|
static_cast<s32>(state->mode),
|
||||||
|
state->targets.empty() ? "" : state->targets.front());
|
||||||
|
Finish(CommerceResult::PURCHASED);
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
}
|
||||||
|
if (Button("Close", BUTTON_SIZE)) {
|
||||||
|
Finish(CommerceResult::USER_CANCELED);
|
||||||
|
}
|
||||||
|
// Default focus on Close so a stray confirm press dismisses, never buys.
|
||||||
|
if (first_render) {
|
||||||
|
SetItemCurrentNavFocus();
|
||||||
|
}
|
||||||
|
EndGroup();
|
||||||
|
}
|
||||||
|
End();
|
||||||
|
|
||||||
|
first_render = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float LayoutScale(PsStoreIconLayout l) {
|
||||||
|
switch (l) {
|
||||||
|
case PsStoreIconLayout::FIXED_90:
|
||||||
|
return 0.90f;
|
||||||
|
case PsStoreIconLayout::FOLLOW_SAFE_AREA:
|
||||||
|
return 0.95f;
|
||||||
|
case PsStoreIconLayout::DEFAULT:
|
||||||
|
default:
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PsStoreIconLayer& PsStoreIconLayer::Instance() {
|
||||||
|
static PsStoreIconLayer instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsStoreIconLayer::Show(PsStoreIconPos p) {
|
||||||
|
pos.store(p);
|
||||||
|
if (!shown.exchange(true)) {
|
||||||
|
AddLayer(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsStoreIconLayer::Hide() {
|
||||||
|
if (shown.exchange(false)) {
|
||||||
|
RemoveLayer(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsStoreIconLayer::SetLayout(PsStoreIconLayout l) {
|
||||||
|
layout.store(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsStoreIconLayer::Draw() {
|
||||||
|
if (!shown.load()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto& io = GetIO();
|
||||||
|
const ImVec2 ds = io.DisplaySize;
|
||||||
|
const float scale = LayoutScale(layout.load());
|
||||||
|
const float inset_x = ds.x * (1.0f - scale) * 0.5f;
|
||||||
|
const float inset_y = ds.y * (1.0f - scale) * 0.5f;
|
||||||
|
|
||||||
|
constexpr ImVec2 icon{52.0f, 52.0f};
|
||||||
|
constexpr float pad = 16.0f;
|
||||||
|
|
||||||
|
float x;
|
||||||
|
switch (pos.load()) {
|
||||||
|
case PsStoreIconPos::LEFT:
|
||||||
|
x = inset_x + pad;
|
||||||
|
break;
|
||||||
|
case PsStoreIconPos::RIGHT:
|
||||||
|
x = ds.x - inset_x - icon.x - pad;
|
||||||
|
break;
|
||||||
|
case PsStoreIconPos::CENTER:
|
||||||
|
default:
|
||||||
|
x = (ds.x - icon.x) * 0.5f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const float y = ds.y - inset_y - icon.y - pad;
|
||||||
|
|
||||||
|
SetNextWindowPos({x, y});
|
||||||
|
SetNextWindowSize(icon);
|
||||||
|
if (Begin("##PsStoreIcon", nullptr,
|
||||||
|
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings |
|
||||||
|
ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoBackground |
|
||||||
|
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) {
|
||||||
|
auto* dl = GetWindowDrawList();
|
||||||
|
const ImVec2 p0 = GetWindowPos();
|
||||||
|
const ImVec2 p1{p0.x + icon.x, p0.y + icon.y};
|
||||||
|
dl->AddRectFilled(p0, p1, IM_COL32(0, 55, 145, 230), 10.0f);
|
||||||
|
dl->AddRect(p0, p1, IM_COL32(255, 255, 255, 210), 10.0f, 0, 2.0f);
|
||||||
|
const char* label = "SN";
|
||||||
|
const ImVec2 ts = CalcTextSize(label);
|
||||||
|
dl->AddText({p0.x + (icon.x - ts.x) * 0.5f, p0.y + (icon.y - ts.y) * 0.5f},
|
||||||
|
IM_COL32(255, 255, 255, 255), label);
|
||||||
|
}
|
||||||
|
End();
|
||||||
|
}
|
||||||
90
src/core/libraries/np/np_commerce/np_commerce_ui.h
Normal file
90
src/core/libraries/np/np_commerce/np_commerce_ui.h
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "core/libraries/system/commondialog.h"
|
||||||
|
#include "imgui/imgui_layer.h"
|
||||||
|
|
||||||
|
namespace Libraries::Np::NpCommerce {
|
||||||
|
|
||||||
|
// OrbisNpCommerceDialogMode
|
||||||
|
enum class CommerceMode : s32 {
|
||||||
|
CATEGORY = 0,
|
||||||
|
PRODUCT = 1,
|
||||||
|
PRODUCT_CODE = 2,
|
||||||
|
CHECKOUT = 3,
|
||||||
|
DOWNLOADLIST = 4,
|
||||||
|
PLUS = 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
// OrbisNpCommerceDialogResult result values
|
||||||
|
enum class CommerceResult : s32 {
|
||||||
|
OK = 0,
|
||||||
|
USER_CANCELED = 1,
|
||||||
|
PURCHASED = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CommerceDialogState {
|
||||||
|
std::vector<std::string> targets; // product / category labels
|
||||||
|
std::string username; // resolved from userId, may be empty
|
||||||
|
CommerceMode mode{CommerceMode::CATEGORY};
|
||||||
|
s32 user_id{-1};
|
||||||
|
u32 service_label{0};
|
||||||
|
u64 features{0}; // ORBIS_NP_PLUS_FEATURE_* (PLUS mode only)
|
||||||
|
void* user_data{nullptr}; // echoed back into the result struct
|
||||||
|
};
|
||||||
|
|
||||||
|
class CommerceDialogUi final : public ImGui::Layer {
|
||||||
|
bool first_render{false};
|
||||||
|
CommerceDialogState* state{};
|
||||||
|
CommonDialog::Status* status{};
|
||||||
|
s32* result{};
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CommerceDialogUi(CommerceDialogState* state = nullptr,
|
||||||
|
CommonDialog::Status* status = nullptr, s32* result = nullptr);
|
||||||
|
~CommerceDialogUi() override;
|
||||||
|
CommerceDialogUi(const CommerceDialogUi&) = delete;
|
||||||
|
CommerceDialogUi& operator=(const CommerceDialogUi&) = delete;
|
||||||
|
CommerceDialogUi(CommerceDialogUi&& other) noexcept;
|
||||||
|
CommerceDialogUi& operator=(CommerceDialogUi&& other) noexcept;
|
||||||
|
|
||||||
|
void Finish(CommerceResult r);
|
||||||
|
void Draw() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// OrbisNpCommercePsStoreIconPos
|
||||||
|
enum class PsStoreIconPos : s32 {
|
||||||
|
CENTER = 0, // screen lower center
|
||||||
|
LEFT = 1, // screen lower left
|
||||||
|
RIGHT = 2, // screen lower right
|
||||||
|
};
|
||||||
|
|
||||||
|
// OrbisNpCommercePsStoreIconLayout
|
||||||
|
enum class PsStoreIconLayout : s32 {
|
||||||
|
DEFAULT = 0, // full screen (100%)
|
||||||
|
FOLLOW_SAFE_AREA = 1, // follow display safe area (90%-100%)
|
||||||
|
FIXED_90 = 2, // fixed 90% of screen
|
||||||
|
};
|
||||||
|
|
||||||
|
class PsStoreIconLayer final : public ImGui::Layer {
|
||||||
|
std::atomic<bool> shown{false};
|
||||||
|
std::atomic<PsStoreIconPos> pos{PsStoreIconPos::CENTER};
|
||||||
|
std::atomic<PsStoreIconLayout> layout{PsStoreIconLayout::DEFAULT};
|
||||||
|
|
||||||
|
public:
|
||||||
|
static PsStoreIconLayer& Instance();
|
||||||
|
|
||||||
|
void Show(PsStoreIconPos p);
|
||||||
|
void Hide();
|
||||||
|
void SetLayout(PsStoreIconLayout l);
|
||||||
|
|
||||||
|
void Draw() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Libraries::Np::NpCommerce
|
||||||
Loading…
Reference in New Issue
Block a user