From 107d5f750a0487acb279b8d9fbc3ae1e538c6c1d Mon Sep 17 00:00:00 2001 From: NeySlim <4509984+NeySlim@users.noreply.github.com> Date: Thu, 8 Oct 2026 17:31:47 +0200 Subject: [PATCH] libNet: finish NpCommerce dialogs at open (#1166) * libNet: finish NpCommerce dialogs at open * libNet: complete NpCommerce dialog cancellation --------- Co-authored-by: nmzik --- src/libs/libNet.cpp | 76 ++++++++++++++++++++++++++-- tests/KernelFileSystemTests.cpp | 90 +++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+), 3 deletions(-) diff --git a/src/libs/libNet.cpp b/src/libs/libNet.cpp index d540a4d0f..f1aff5fc3 100644 --- a/src/libs/libNet.cpp +++ b/src/libs/libNet.cpp @@ -1497,13 +1497,43 @@ namespace LibNpCommerce { LIB_VERSION("NpCommerce", 1, "NpCommerce", 1, 1); -constexpr int COMMERCE_STATUS_NONE = 0; -constexpr int COMMERCE_STATUS_INITIALIZED = 1; +constexpr int COMMERCE_STATUS_NONE = 0; +constexpr int COMMERCE_STATUS_INITIALIZED = 1; +constexpr int COMMERCE_STATUS_FINISHED = 3; +constexpr int COMMERCE_RESULT_USER_CANCELED = 1; constexpr int COMMERCE_ERROR_NOT_INITIALIZED = static_cast(0x80B80003u); constexpr int COMMERCE_ERROR_ALREADY_INITIALIZED = static_cast(0x80B80004u); +constexpr int COMMERCE_ERROR_NOT_FINISHED = static_cast(0x80B80005u); +constexpr int COMMERCE_ERROR_PARAM_INVALID = static_cast(0x80B8000Au); +constexpr int COMMERCE_ERROR_ARG_NULL = static_cast(0x80B8000Du); -static int g_commerce_status = COMMERCE_STATUS_NONE; +struct CommerceDialogParam { + uint8_t base_param[48]; + int32_t size; + int32_t user_id; + int32_t mode; + uint32_t service_label; + const char* const* targets; + uint32_t num_targets; + uint32_t padding; + uint64_t features; + void* user_data; + uint8_t reserved[32]; +}; + +struct CommerceDialogResult { + int32_t result; + bool authorized; + void* user_data; + uint8_t reserved[32]; +}; + +static_assert(sizeof(CommerceDialogParam) == 128 && offsetof(CommerceDialogParam, user_data) == 88); +static_assert(sizeof(CommerceDialogResult) == 48 && offsetof(CommerceDialogResult, user_data) == 8); + +static int g_commerce_status = COMMERCE_STATUS_NONE; +static CommerceDialogResult g_commerce_result {}; static int KYTY_SYSV_ABI NpCommerceDialogInitialize() { PRINT_NAME(); @@ -1520,9 +1550,47 @@ static int KYTY_SYSV_ABI NpCommerceDialogTerminate() { return COMMERCE_ERROR_NOT_INITIALIZED; } g_commerce_status = COMMERCE_STATUS_NONE; + g_commerce_result = {}; return OK; } +static int KYTY_SYSV_ABI NpCommerceDialogOpen(const CommerceDialogParam* param) { + PRINT_NAME(); + if (g_commerce_status == COMMERCE_STATUS_NONE) { + return COMMERCE_ERROR_NOT_INITIALIZED; + } + if (param == nullptr) { + return COMMERCE_ERROR_ARG_NULL; + } + g_commerce_result = {}; + if (param->size == sizeof(CommerceDialogParam)) { + g_commerce_result.user_data = param->user_data; + } + g_commerce_status = COMMERCE_STATUS_FINISHED; + if (param->size != sizeof(CommerceDialogParam) || param->mode < 0 || param->mode > 5 || + std::ranges::any_of(param->reserved, [](uint8_t value) { return value != 0; })) { + g_commerce_result.result = COMMERCE_ERROR_PARAM_INVALID; + return COMMERCE_ERROR_PARAM_INVALID; + } + g_commerce_result.result = COMMERCE_RESULT_USER_CANCELED; + return OK; +} + +static int KYTY_SYSV_ABI NpCommerceDialogGetResult(CommerceDialogResult* result) { + PRINT_NAME(); + if (g_commerce_status == COMMERCE_STATUS_NONE) { + return COMMERCE_ERROR_NOT_INITIALIZED; + } + if (result == nullptr) { + return COMMERCE_ERROR_ARG_NULL; + } + if (g_commerce_status != COMMERCE_STATUS_FINISHED) { + return COMMERCE_ERROR_NOT_FINISHED; + } + *result = g_commerce_result; + return result->result; +} + static int KYTY_SYSV_ABI NpCommerceDialogUpdateStatus() { PRINT_NAME(); @@ -1532,6 +1600,8 @@ static int KYTY_SYSV_ABI NpCommerceDialogUpdateStatus() { LIB_DEFINE(InitNet_1_NpCommerce) { LIB_FUNC("0aR2aWmQal4", NpCommerceDialogInitialize); LIB_FUNC("m-I92Ab50W8", NpCommerceDialogTerminate); + LIB_FUNC("DfSCDRA3EjY", NpCommerceDialogOpen); + LIB_FUNC("r42bWcQbtZY", NpCommerceDialogGetResult); LIB_FUNC("LR5cwFMMCVE", NpCommerceDialogUpdateStatus); } diff --git a/tests/KernelFileSystemTests.cpp b/tests/KernelFileSystemTests.cpp index 9c2ee6504..9e93d58d4 100644 --- a/tests/KernelFileSystemTests.cpp +++ b/tests/KernelFileSystemTests.cpp @@ -62,6 +62,10 @@ namespace Libs::LibNpWebApi2 { void InitNet_1_NpWebApi2(Loader::SymbolDatabase *symbols); } +namespace Libs::LibNpCommerce { +void InitNet_1_NpCommerce(Loader::SymbolDatabase *symbols); +} + namespace { namespace FileSystem = Libs::LibKernel::FileSystem; @@ -385,6 +389,91 @@ void TestAioBatches() { Check(FileSystem::KernelClose(fd) == OK, "close AIO read fixture"); } +void TestNpCommerceDialog() { + Loader::SymbolDatabase symbols; + Libs::LibNpCommerce::InitNet_1_NpCommerce(&symbols); + const auto find = [&](const char *nid) { + const auto *symbol = symbols.FindByNid(nid, Loader::SymbolType::Func); + Check(symbol != nullptr, "NpCommerce dialog exports resolve"); + return symbol->vaddr; + }; + using Call = int (KYTY_SYSV_ABI *)(); + using Open = int (KYTY_SYSV_ABI *)(const void *); + using GetResult = int (KYTY_SYSV_ABI *)(void *); + const auto initialize = reinterpret_cast(find("0aR2aWmQal4")); + const auto terminate = reinterpret_cast(find("m-I92Ab50W8")); + const auto update = reinterpret_cast(find("LR5cwFMMCVE")); + const auto open = reinterpret_cast(find("DfSCDRA3EjY")); + const auto get_result = reinterpret_cast(find("r42bWcQbtZY")); + struct BaseParam { + uint64_t size; + uint8_t reserved[36]; + uint32_t magic; + }; + struct Param { + BaseParam base; + int32_t size, user_id, mode; + uint32_t service_label; + const char *const *targets; + uint32_t num_targets, padding; + uint64_t features; + void *user_data; + uint8_t reserved[32]; + } param {}; + struct Result { + int32_t result; + bool authorized; + void *user_data; + uint8_t reserved[32]; + } result {}; + static_assert(sizeof(Param) == 128 && offsetof(Param, user_data) == 88); + static_assert(sizeof(Result) == 48 && offsetof(Result, user_data) == 8); + param.base.size = sizeof(BaseParam); + param.base.magic = static_cast(0xC0D1A109u + reinterpret_cast(¶m.base)); + param.size = sizeof(param); + param.user_id = 1; + param.user_data = &symbols; + Check(open(¶m) == static_cast(0x80B80003u) && + get_result(&result) == static_cast(0x80B80003u), + "NpCommerce dialog calls require initialization"); + Check(initialize() == OK && update() == 1 && + get_result(&result) == static_cast(0x80B80005u), + "NpCommerce dialog result requires completion"); + Check(open(nullptr) == static_cast(0x80B8000Du) && update() == 1 && + get_result(nullptr) == static_cast(0x80B8000Du), + "NpCommerce dialog rejects null arguments"); + Check(open(¶m) == OK && update() == 3, "NpCommerce dialog finishes without a store"); + param.user_data = nullptr; + result.authorized = true; + Check(get_result(&result) == 1 && result.result == 1 && !result.authorized && + result.user_data == &symbols, + "NpCommerce cancellation returns the original user data"); + param.mode = 5; + Check(open(¶m) == OK && get_result(&result) == 1 && !result.authorized && + result.user_data == nullptr, + "NpCommerce dialog can reopen with fresh result data"); + const auto invalid_param = [&] { + Check(open(¶m) == static_cast(0x80B8000Au) && update() == 3 && + get_result(&result) == static_cast(0x80B8000Au) && + result.result == static_cast(0x80B8000Au) && !result.authorized, + "NpCommerce dialog reports invalid parameters in the completion result"); + }; + param.mode = 99; + invalid_param(); + param.mode = -1; + invalid_param(); + param.mode = 0; + param.size = 64; + invalid_param(); + param.size = sizeof(param); + param.reserved[0] = 1; + invalid_param(); + param.reserved[0] = 0; + Check(open(¶m) == OK && get_result(&result) == 1 && terminate() == OK && update() == 0 && + get_result(&result) == static_cast(0x80B80003u), + "NpCommerce dialog recovers from invalid parameters and terminates"); +} + void TestNpWebApi2Memory() { Loader::SymbolDatabase symbols; Libs::LibNpWebApi2::InitNet_1_NpWebApi2(&symbols); @@ -1644,6 +1733,7 @@ int main(int, char**) { CheckSocketWakeup(); CheckEtherAddressFormatting(); TestNpWebApi2Memory(); + TestNpCommerceDialog(); graphics.reset(); subsystems.Destroy();