From a8d157d3103660bb1bbab0affdcd4219593f954e Mon Sep 17 00:00:00 2001 From: Rambo6Glaz <39063367+EpicUsername12@users.noreply.github.com> Date: Sat, 24 Jun 2023 14:51:41 +0200 Subject: [PATCH] nn_olv: Added community related API (#873) - Initialize - Download communities (self-made / favorites / officials) - Upload communities (create subcommunity) - Upload favorite status (Add/Delete favorite to a subcommunity) Enough for support of Mario Kart 8 tournaments --- src/Cafe/CMakeLists.txt | 10 + src/Cafe/CafeSystem.cpp | 7 + src/Cafe/CafeSystem.h | 1 + src/Cafe/OS/libs/nn_act/nn_act.cpp | 75 ++- src/Cafe/OS/libs/nn_act/nn_act.h | 11 + src/Cafe/OS/libs/nn_olv/nn_olv.cpp | 74 ++- src/Cafe/OS/libs/nn_olv/nn_olv.h | 14 + src/Cafe/OS/libs/nn_olv/nn_olv_Common.cpp | 285 ++++++++++ src/Cafe/OS/libs/nn_olv/nn_olv_Common.h | 179 ++++++ .../nn_olv/nn_olv_DownloadCommunityTypes.cpp | 231 ++++++++ .../nn_olv/nn_olv_DownloadCommunityTypes.h | 530 ++++++++++++++++++ .../OS/libs/nn_olv/nn_olv_InitializeTypes.cpp | 312 +++++++++++ .../OS/libs/nn_olv/nn_olv_InitializeTypes.h | 128 +++++ .../nn_olv/nn_olv_UploadCommunityTypes.cpp | 304 ++++++++++ .../libs/nn_olv/nn_olv_UploadCommunityTypes.h | 432 ++++++++++++++ .../nn_olv/nn_olv_UploadFavoriteTypes.cpp | 169 ++++++ .../libs/nn_olv/nn_olv_UploadFavoriteTypes.h | 342 +++++++++++ src/Cafe/TitleList/ParsedMetaXml.h | 9 + src/Cafe/TitleList/TitleInfo.cpp | 10 + src/Cafe/TitleList/TitleInfo.h | 1 + src/Cemu/napi/napi_helper.cpp | 45 +- src/Cemu/napi/napi_helper.h | 8 + src/config/NetworkSettings.cpp | 1 + src/config/NetworkSettings.h | 3 + 24 files changed, 3125 insertions(+), 56 deletions(-) create mode 100644 src/Cafe/OS/libs/nn_olv/nn_olv_Common.cpp create mode 100644 src/Cafe/OS/libs/nn_olv/nn_olv_Common.h create mode 100644 src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp create mode 100644 src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.h create mode 100644 src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp create mode 100644 src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.h create mode 100644 src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp create mode 100644 src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.h create mode 100644 src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp create mode 100644 src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 8da22176..ea3a728b 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -406,6 +406,16 @@ add_library(CemuCafe OS/libs/nn_nim/nn_nim.h OS/libs/nn_olv/nn_olv.cpp OS/libs/nn_olv/nn_olv.h + OS/libs/nn_olv/nn_olv_Common.cpp + OS/libs/nn_olv/nn_olv_Common.h + OS/libs/nn_olv/nn_olv_InitializeTypes.cpp + OS/libs/nn_olv/nn_olv_InitializeTypes.h + OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp + OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.h + OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp + OS/libs/nn_olv/nn_olv_UploadCommunityTypes.h + OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp + OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h OS/libs/nn_pdm/nn_pdm.cpp OS/libs/nn_pdm/nn_pdm.h OS/libs/nn_save/nn_save.cpp diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index cd6c2b75..8ca86adf 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -723,6 +723,13 @@ namespace CafeSystem return applicationName; } + uint32 GetForegroundTitleOlvAccesskey() + { + if (sLaunchModeIsStandalone) + return -1; + return sGameInfo_ForegroundTitle.GetBase().GetMetaInfo()->GetOlvAccesskey(); + } + std::string GetForegroundTitleArgStr() { if (sLaunchModeIsStandalone) diff --git a/src/Cafe/CafeSystem.h b/src/Cafe/CafeSystem.h index dce0b940..236cf44a 100644 --- a/src/Cafe/CafeSystem.h +++ b/src/Cafe/CafeSystem.h @@ -26,6 +26,7 @@ namespace CafeSystem CafeConsoleRegion GetPlatformRegion(); std::string GetForegroundTitleName(); std::string GetForegroundTitleArgStr(); + uint32 GetForegroundTitleOlvAccesskey(); void ShutdownTitle(); diff --git a/src/Cafe/OS/libs/nn_act/nn_act.cpp b/src/Cafe/OS/libs/nn_act/nn_act.cpp index 5dad1b0b..51cd8ba1 100644 --- a/src/Cafe/OS/libs/nn_act/nn_act.cpp +++ b/src/Cafe/OS/libs/nn_act/nn_act.cpp @@ -95,6 +95,36 @@ namespace act return result; } + uint32 GetTransferableIdEx(uint64* transferableId, uint32 unique, uint8 slot) + { + actPrepareRequest2(); + actRequest->requestCode = IOSU_ARC_TRANSFERABLEID; + actRequest->accountSlot = slot; + actRequest->unique = unique; + + uint32 result = _doCemuActRequest(actRequest); + + *transferableId = _swapEndianU64(actRequest->resultU64.u64); + + return result; + } + + uint32 AcquireIndependentServiceToken(independentServiceToken_t* token, const char* clientId, uint32 cacheDurationInSeconds) + { + memset(token, 0, sizeof(independentServiceToken_t)); + actPrepareRequest(); + actRequest->requestCode = IOSU_ARC_ACQUIREINDEPENDENTTOKEN; + actRequest->titleId = CafeSystem::GetForegroundTitleId(); + actRequest->titleVersion = CafeSystem::GetForegroundTitleVersion(); + actRequest->expiresIn = cacheDurationInSeconds; + strcpy(actRequest->clientId, clientId); + + uint32 resultCode = __depr__IOS_Ioctlv(IOS_DEVICE_ACT, IOSU_ACT_REQUEST_CEMU, 1, 1, actBufferVector); + + memcpy(token, actRequest->resultBinary.binBuffer, sizeof(independentServiceToken_t)); + return getNNReturnCode(resultCode, actRequest); + } + sint32 g_initializeCount = 0; // inc in Initialize and dec in Finalize uint32 Initialize() { @@ -170,21 +200,6 @@ uint32 GetPrincipalIdEx(uint32be* principalId, uint8 slot) return result; } - -uint32 GetTransferableIdEx(uint64* transferableId, uint32 unique, uint8 slot) -{ - actPrepareRequest2(); - actRequest->requestCode = IOSU_ARC_TRANSFERABLEID; - actRequest->accountSlot = slot; - actRequest->unique = unique; - - uint32 result = _doCemuActRequest(actRequest); - - *transferableId = _swapEndianU64(actRequest->resultU64.u64); - - return result; -} - uint32 GetCountryEx(char* country, uint8 slot) { actPrepareRequest2(); @@ -278,7 +293,7 @@ void nnActExport_GetTransferableIdEx(PPCInterpreter_t* hCPU) cemuLog_logDebug(LogType::Force, "nn_act.GetTransferableIdEx(0x{:08x}, 0x{:08x}, {})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5] & 0xFF); - uint32 r = GetTransferableIdEx(transferableId, unique, slot); + uint32 r = nn::act::GetTransferableIdEx(transferableId, unique, slot); osLib_returnFromFunction(hCPU, 0); // ResultSuccess } @@ -589,33 +604,11 @@ void nnActExport_AcquireNexServiceToken(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, getNNReturnCode(resultCode, actRequest)); } -struct independentServiceToken_t -{ - /* +0x000 */ char token[0x201]; -}; -static_assert(sizeof(independentServiceToken_t) == 0x201); // todo - verify size - -uint32 AcquireIndependentServiceToken(independentServiceToken_t* token, const char* clientId, uint32 cacheDurationInSeconds) -{ - memset(token, 0, sizeof(independentServiceToken_t)); - actPrepareRequest(); - actRequest->requestCode = IOSU_ARC_ACQUIREINDEPENDENTTOKEN; - actRequest->titleId = CafeSystem::GetForegroundTitleId(); - actRequest->titleVersion = CafeSystem::GetForegroundTitleVersion(); - actRequest->expiresIn = cacheDurationInSeconds; - strcpy(actRequest->clientId, clientId); - - uint32 resultCode = __depr__IOS_Ioctlv(IOS_DEVICE_ACT, IOSU_ACT_REQUEST_CEMU, 1, 1, actBufferVector); - - memcpy(token, actRequest->resultBinary.binBuffer, sizeof(independentServiceToken_t)); - return getNNReturnCode(resultCode, actRequest); -} - void nnActExport_AcquireIndependentServiceToken(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(token, independentServiceToken_t, 0); ppcDefineParamMEMPTR(serviceToken, const char, 1); - uint32 result = AcquireIndependentServiceToken(token.GetPtr(), serviceToken.GetPtr(), 0); + uint32 result = nn::act::AcquireIndependentServiceToken(token.GetPtr(), serviceToken.GetPtr(), 0); cemuLog_logDebug(LogType::Force, "nn_act.AcquireIndependentServiceToken(0x{}, {}) -> {:x}", (void*)token.GetPtr(), serviceToken.GetPtr(), result); cemuLog_logDebug(LogType::Force, "Token: {}", serviceToken.GetPtr()); osLib_returnFromFunction(hCPU, result); @@ -626,7 +619,7 @@ void nnActExport_AcquireIndependentServiceToken2(PPCInterpreter_t* hCPU) ppcDefineParamStructPtr(token, independentServiceToken_t, 0); ppcDefineParamMEMPTR(clientId, const char, 1); ppcDefineParamU32(cacheDurationInSeconds, 2); - uint32 result = AcquireIndependentServiceToken(token, clientId.GetPtr(), cacheDurationInSeconds); + uint32 result = nn::act::AcquireIndependentServiceToken(token, clientId.GetPtr(), cacheDurationInSeconds); cemuLog_logDebug(LogType::Force, "Called nn_act.AcquireIndependentServiceToken2"); osLib_returnFromFunction(hCPU, result); } @@ -634,7 +627,7 @@ void nnActExport_AcquireIndependentServiceToken2(PPCInterpreter_t* hCPU) void nnActExport_AcquireEcServiceToken(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(pEcServiceToken, independentServiceToken_t, 0); - uint32 result = AcquireIndependentServiceToken(pEcServiceToken.GetPtr(), "71a6f5d6430ea0183e3917787d717c46", 0); + uint32 result = nn::act::AcquireIndependentServiceToken(pEcServiceToken.GetPtr(), "71a6f5d6430ea0183e3917787d717c46", 0); cemuLog_logDebug(LogType::Force, "Called nn_act.AcquireEcServiceToken"); osLib_returnFromFunction(hCPU, result); } diff --git a/src/Cafe/OS/libs/nn_act/nn_act.h b/src/Cafe/OS/libs/nn_act/nn_act.h index 06df4af1..d0610a0f 100644 --- a/src/Cafe/OS/libs/nn_act/nn_act.h +++ b/src/Cafe/OS/libs/nn_act/nn_act.h @@ -1,5 +1,13 @@ #pragma once +#include "Cafe/IOSU/legacy/iosu_act.h" + +struct independentServiceToken_t +{ + /* +0x000 */ char token[0x201]; +}; +static_assert(sizeof(independentServiceToken_t) == 0x201); // todo - verify size + namespace nn { namespace act @@ -9,6 +17,9 @@ namespace act uint32 GetPersistentIdEx(uint8 slot); uint32 GetUuidEx(uint8* uuid, uint8 slot, sint32 name = -2); uint32 GetSimpleAddressIdEx(uint32be* simpleAddressId, uint8 slot); + uint32 GetTransferableIdEx(uint64* transferableId, uint32 unique, uint8 slot); + + uint32 AcquireIndependentServiceToken(independentServiceToken_t* token, const char* clientId, uint32 cacheDurationInSeconds); static uint32 getCountryCodeFromSimpleAddress(uint32 simpleAddressId) { diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv.cpp index 038b555b..97feea8b 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv.cpp @@ -1,7 +1,10 @@ -#include "Cafe/OS/common/OSCommon.h" -#include "Cafe/OS/libs/nn_common.h" #include "nn_olv.h" +#include "nn_olv_InitializeTypes.h" +#include "nn_olv_UploadCommunityTypes.h" +#include "nn_olv_DownloadCommunityTypes.h" +#include "nn_olv_UploadFavoriteTypes.h" + namespace nn { namespace olv @@ -124,14 +127,6 @@ namespace nn osLib_returnFromFunction(hCPU, 0); } - void export_DownloadCommunityDataList(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(communityListSizeOut, uint32be, 1); - - *communityListSizeOut = 0; - osLib_returnFromFunction(hCPU, 0); - } - void exportDownloadPostData_TestFlags(PPCInterpreter_t* hCPU) { ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); @@ -209,8 +204,65 @@ namespace nn return BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_OLV, 0); // undefined error } + // https://github.com/kinnay/NintendoClients/wiki/Wii-U-Error-Codes#act-error-codes + constexpr uint32 GetErrorCodeImpl(uint32 in) + { + uint32_t errorCode = in; + uint32_t errorVersion = (errorCode >> 27) & 3; + uint32_t errorModuleMask = (errorVersion != 3) ? 0x1FF00000 : 0x7F00000; + bool isCodeFailure = errorCode & 0x80000000; + + if (((errorCode & errorModuleMask) >> 20) == NN_RESULT_MODULE_NN_ACT) + { + // BANNED_ACCOUNT_IN_INDEPENDENT_SERVICE or BANNED_ACCOUNT_IN_INDEPENDENT_SERVICE_TEMPORARILY + if (errorCode == OLV_ACT_RESULT_STATUS(2805) || errorCode == OLV_ACT_RESULT_STATUS(2825)) + { + uint32 tmpCode = OLV_RESULT_STATUS(1008); + return GetErrorCodeImpl(tmpCode); + } + // BANNED_DEVICE_IN_INDEPENDENT_SERVICE or BANNED_DEVICE_IN_INDEPENDENT_SERVICE_TEMPORARILY + else if (errorCode == OLV_ACT_RESULT_STATUS(2815) || errorCode == OLV_ACT_RESULT_STATUS(2835)) + { + uint32 tmpCode = OLV_RESULT_STATUS(1009); + return GetErrorCodeImpl(tmpCode); + } + else + { + // Check ACT error code + return 1159999; + } + } + else + { + if (((errorCode & errorModuleMask) >> 20) == NN_RESULT_MODULE_NN_OLV && isCodeFailure) + { + uint32_t errorValueMask = (errorVersion != 3) ? 0xFFFFF : 0x3FF; + return ((errorCode & errorValueMask) >> 7) + 1150000; + } + else + { + return 1159999; + } + } + } + + uint32 GetErrorCode(uint32be* pResult) + { + return GetErrorCodeImpl(pResult->value()); + } + + static_assert(GetErrorCodeImpl(0xa119c600) == 1155004); + void load() { + + loadOliveInitializeTypes(); + loadOliveUploadCommunityTypes(); + loadOliveDownloadCommunityTypes(); + loadOliveUploadFavoriteTypes(); + + cafeExportRegisterFunc(GetErrorCode, "nn_olv", "GetErrorCode__Q2_2nn3olvFRCQ2_2nn6Result", LogType::None); + osLib_addFunction("nn_olv", "DownloadPostDataList__Q2_2nn3olvFPQ3_2nn3olv19DownloadedTopicDataPQ3_2nn3olv18DownloadedPostDataPUiUiPCQ3_2nn3olv25DownloadPostDataListParam", export_DownloadPostDataList); osLib_addFunction("nn_olv", "TestFlags__Q3_2nn3olv18DownloadedDataBaseCFUi", exportDownloadPostData_TestFlags); osLib_addFunction("nn_olv", "GetPostId__Q3_2nn3olv18DownloadedDataBaseCFv", exportDownloadPostData_GetPostId); @@ -218,8 +270,6 @@ namespace nn osLib_addFunction("nn_olv", "GetTopicTag__Q3_2nn3olv18DownloadedDataBaseCFv", exportDownloadPostData_GetTopicTag); osLib_addFunction("nn_olv", "GetBodyText__Q3_2nn3olv18DownloadedDataBaseCFPwUi", exportDownloadPostData_GetBodyText); - osLib_addFunction("nn_olv", "DownloadCommunityDataList__Q2_2nn3olvFPQ3_2nn3olv23DownloadedCommunityDataPUiUiPCQ3_2nn3olv30DownloadCommunityDataListParam", export_DownloadCommunityDataList); - osLib_addFunction("nn_olv", "GetServiceToken__Q4_2nn3olv6hidden14PortalAppParamCFv", exportPortalAppParam_GetServiceToken); cafeExportRegisterFunc(UploadPostDataByPostApp, "nn_olv", "UploadPostDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv28UploadPostDataByPostAppParam", LogType::Placeholder); diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv.h b/src/Cafe/OS/libs/nn_olv/nn_olv.h index e8278136..c608e391 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv.h +++ b/src/Cafe/OS/libs/nn_olv/nn_olv.h @@ -1,9 +1,23 @@ #pragma once +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/nn_common.h" +#include "Cafe/OS/libs/nn_act/nn_act.h" +#include "Cafe/CafeSystem.h" +#include "Cemu/napi/napi.h" + +#include "nn_olv_Common.h" + namespace nn { namespace olv { + + extern ParamPackStorage g_ParamPack; + extern DiscoveryResultStorage g_DiscoveryResults; + + sint32 GetOlvAccessKey(uint32_t* pOutKey); + void load(); } } \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_Common.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_Common.cpp new file mode 100644 index 00000000..839aa412 --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_Common.cpp @@ -0,0 +1,285 @@ +#include "nn_olv_Common.h" +#include + +namespace nn +{ + namespace olv + { + + sint32 olv_copy_wstr(char16_t* dest, const char16_t* src, uint32_t maxSize, uint32_t destSize) + { + size_t len = maxSize + 1; + if (olv_wstrnlen(src, len) > maxSize) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + memset(dest, 0, 2 * destSize); + olv_wstrncpy(dest, src, len); + return OLV_RESULT_SUCCESS; + } + + size_t olv_wstrnlen(const char16_t* str, size_t max_len) + { + size_t len = 0; + while (len < max_len && str[len] != u'\0') + len++; + + return len; + } + + char16_t* olv_wstrncpy(char16_t* dest, const char16_t* src, size_t n) + { + char16_t* ret = dest; + while (n > 0 && *src != u'\0') + { + *dest++ = *src++; + n--; + } + while (n > 0) + { + *dest++ = u'\0'; + n--; + } + return ret; + } + + bool CheckTGA(const uint8* pTgaFile, uint32 pTgaFileLen, TGACheckType checkType) + { + const TGAHeader* header = (const TGAHeader*)pTgaFile; + try + { + if (checkType == TGACheckType::CHECK_PAINTING) + { + if ( + header->idLength || + header->colorMapType || + header->imageType != 2 || // Uncompressed true color + header->first_entry_idx || + header->colormap_length || + header->bpp || + header->x_origin || + header->y_origin || + header->width != 320 || + header->height != 120 || + header->pixel_depth_bpp != 32 || + header->image_desc_bits != 8 + ) + { + throw std::runtime_error("TGACheckType::CHECK_PAINTING - Invalid TGA file!"); + } + } + else if (checkType == TGACheckType::CHECK_COMMUNITY_ICON) + { + if (header->width != 128 || header->height != 128 || header->pixel_depth_bpp != 32) + throw std::runtime_error("TGACheckType::CHECK_COMMUNITY_ICON - Invalid TGA file -> width, height or bpp is wrong"); + } + else if (checkType == TGACheckType::CHECK_100x100_200x200) + { + if (header->pixel_depth_bpp != 32) + throw std::runtime_error("TGACheckType::CHECK_100x100_200x200 - Invalid TGA file -> bpp is wrong"); + + if (header->width == 100) + { + if (header->height != 100) + throw std::runtime_error("TGACheckType::CHECK_100x100_200x200 - Invalid TGA file -> Not 100x100"); + } + else if (header->width != 200 || header->height != 200) + throw std::runtime_error("TGACheckType::CHECK_100x100_200x200 - Invalid TGA file -> Not 100x100 or 200x200"); + } + } + catch (const std::runtime_error& error) + { + // TGA Check Error! illegal format + cemuLog_log(LogType::Force, error.what()); + return false; + } + return true; + } + + sint32 DecodeTGA(uint8* pInBuffer, uint32 inSize, uint8* pOutBuffer, uint32 outSize, TGACheckType checkType) + { + uint32 decompressedSize = outSize; + if (DecompressTGA(pOutBuffer, &decompressedSize, pInBuffer, inSize)) + { + if (CheckTGA(pOutBuffer, decompressedSize, checkType)) + return decompressedSize; + + return -2; + } + else + { + cemuLog_log(LogType::Force, "OLIVE uncompress error.\n"); + return -1; + } + } + + sint32 EncodeTGA(uint8* pInBuffer, uint32 inSize, uint8* pOutBuffer, uint32 outSize, TGACheckType checkType) + { + if (inSize == outSize) + { + if (!CheckTGA(pInBuffer, inSize, checkType)) + return -1; + + uint32 compressedSize = outSize; + if (CompressTGA(pOutBuffer, &compressedSize, pInBuffer, inSize)) + return compressedSize; + else + { + cemuLog_log(LogType::Force, "OLIVE compress error.\n"); + return -1; + } + } + else + { + cemuLog_log(LogType::Force, "compress buffer size check error. uSrcBufSize({}) != uDstBufSize({})\n", inSize, outSize); + return -1; + } + } + + bool DecompressTGA(uint8* pOutBuffer, uint32* pOutSize, uint8* pInBuffer, uint32 inSize) + { + if (pOutBuffer == nullptr || pOutSize == nullptr || pInBuffer == nullptr || inSize == 0) + return false; + + uLongf bufferSize = *pOutSize; + int result = uncompress(pOutBuffer, &bufferSize, pInBuffer, inSize); + + if (result == Z_OK) + { + *pOutSize = static_cast(bufferSize); + return true; + } + else + { + const char* error_msg = (result == Z_MEM_ERROR) ? "Insufficient memory" : "Unknown decompression error"; + cemuLog_log(LogType::Force, "OLIVE ZLIB - ERROR: {}\n", error_msg); + return false; + } + } + + bool CompressTGA(uint8* pOutBuffer, uint32* pOutSize, uint8* pInBuffer, uint32 inSize) + { + if (pOutBuffer == nullptr || pOutSize == nullptr || pInBuffer == nullptr || inSize == 0) + return false; + + uLongf bufferSize = *pOutSize; + int result = compress(pOutBuffer, &bufferSize, pInBuffer, inSize); + + if (result == Z_OK) + { + *pOutSize = static_cast(bufferSize); + return true; + } + else + { + const char* error_msg = (result == Z_MEM_ERROR) ? "Insufficient memory" : "Unknown compression error"; + cemuLog_log(LogType::Force, "OLIVE ZLIB - ERROR: {}\n", error_msg); + return false; + } + } + + constexpr uint32 CreateCommunityCodeById(uint32 communityId) + { + uint32 res = communityId ^ (communityId << 18) ^ (communityId << 24) ^ (communityId << 30); + return res ^ (16 * (res & 0xF0F0F0F)) ^ ((res ^ (16 * (res & 0xF0F0F0F))) >> 17) ^ ((res ^ (16 * (res & 0xF0F0F0F))) >> 23) ^ ((res ^ (16 * (res & 0xF0F0F0F))) >> 29) ^ 0x20121002; + } + + constexpr uint32 CreateCommunityIdByCode(uint32 code) + { + uint32 res = code ^ 0x20121002 ^ ((code ^ 0x20121002u) >> 17) ^ ((code ^ 0x20121002u) >> 23) ^ ((code ^ 0x20121002u) >> 29); + return res ^ (16 * (res & 0xF0F0F0F)) ^ ((res ^ (16 * (res & 0xF0F0F0F))) << 18) ^ ((res ^ (16 * (res & 0xF0F0F0F))) << 24) ^ ((res ^ (16 * (res & 0xF0F0F0F))) << 30); + } + + + constexpr uint32 GetCommunityCodeTopByte(uint32 communityId) + { + uint8 code_byte3 = (uint8_t)(communityId >> 0x18); + uint8 code_byte2 = (uint8_t)(communityId >> 0x10); + uint8 code_byte1 = (uint8_t)(communityId >> 8); + uint8 code_byte0 = (uint8_t)(communityId >> 0); + return code_byte3 ^ code_byte2 ^ code_byte1 ^ code_byte0 ^ 0xff; + } + + constexpr uint64 GetRealCommunityCode(uint32_t communityId) + { + uint64 topByte = GetCommunityCodeTopByte(communityId); + if ((0xe7 < topByte) && ((0xe8 < topByte || (0xd4a50fff < communityId)))) + return ((topByte << 32) | communityId) & 0x7fffffffff; + + return ((topByte << 32) | communityId); + } + + void WriteCommunityCode(char* pOutCode, uint32 communityId) + { + uint32 code = CreateCommunityCodeById(communityId); + uint64 communityCode = GetRealCommunityCode(code); + sprintf(pOutCode, "%012llu", communityCode); + } + + bool EnsureCommunityCode(char* pCode) + { + uint64 code; + if (sscanf(pCode, "%012llu", &code) > 0) + { + uint32 lowerCode = code; + uint64 newCode = GetRealCommunityCode(code); + return code == newCode; + } + return false; + } + + bool FormatCommunityCode(char* pOutCode, uint32* outLen, uint32 communityId) + { + bool result = false; + if (communityId != -1) + { + if (communityId) + { + WriteCommunityCode(pOutCode, communityId); + *outLen = strnlen(pOutCode, 12); + if (EnsureCommunityCode(pOutCode)) + result = 1; + } + } + return result; + } + + static_assert(GetRealCommunityCode(CreateCommunityCodeById(140500)) == 717651734336, "Wrong community code generation code, result must match."); + + uint32 ExtractCommunityIdFromCode(char* pCode) + { + uint32 id = 0; + uint64 code; + if (sscanf(pCode, "%012llu", &code) > 0) + { + uint32 lower_code = code; + id = CreateCommunityIdByCode(lower_code); + } + return id; + } + + bool GetCommunityIdFromCode(uint32* pOutId, const char* pCode) + { + if (!EnsureCommunityCode((char*)pCode)) + return false; + + *pOutId = ExtractCommunityIdFromCode((char*)pCode); + return true; + } + + sint32 olv_curlformcode_to_error(CURLFORMcode code) + { + switch (code) + { + case CURL_FORMADD_OK: + return OLV_RESULT_SUCCESS; + + case CURL_FORMADD_MEMORY: + return OLV_RESULT_FATAL(25); + + case CURL_FORMADD_OPTION_TWICE: + default: + return OLV_RESULT_LVL6(50); + } + } + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_Common.h b/src/Cafe/OS/libs/nn_olv/nn_olv_Common.h new file mode 100644 index 00000000..718c10c3 --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_Common.h @@ -0,0 +1,179 @@ +#pragma once + +#include "Cafe/OS/libs/nn_common.h" +#include "Cafe/OS/common/OSCommon.h" +#include "Cemu/napi/napi_helper.h" +#include "util/helpers/StringHelpers.h" +#include "pugixml.hpp" + +// https://github.com/kinnay/NintendoClients/wiki/Wii-U-Error-Codes#act-error-codes +#define OLV_ACT_RESULT_STATUS(code) (BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_OLV, ((code) << 7))) + +#define OLV_RESULT_STATUS(code) (BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_OLV, ((code) << 7))) +#define OLV_RESULT_LVL6(code) (BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_OLV, ((code) << 7))) +#define OLV_RESULT_FATAL(code) (BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_OLV, ((code) << 7))) +#define OLV_RESULT_SUCCESS (BUILD_NN_RESULT(0, NN_RESULT_MODULE_NN_OLV, 1 << 7)) + +#define OLV_RESULT_INVALID_PARAMETER (OLV_RESULT_LVL6(201)) +#define OLV_RESULT_INVALID_DATA (OLV_RESULT_LVL6(202)) +#define OLV_RESULT_NOT_ENOUGH_SIZE (OLV_RESULT_LVL6(203)) +#define OLV_RESULT_INVALID_PTR (OLV_RESULT_LVL6(204)) +#define OLV_RESULT_NOT_INITIALIZED (OLV_RESULT_LVL6(205)) +#define OLV_RESULT_ALREADY_INITIALIZED (OLV_RESULT_LVL6(206)) +#define OLV_RESULT_OFFLINE_MODE_REQUEST (OLV_RESULT_LVL6(207)) +#define OLV_RESULT_MISSING_DATA (OLV_RESULT_LVL6(208)) +#define OLV_RESULT_INVALID_SIZE (OLV_RESULT_LVL6(209)) + +#define OLV_RESULT_BAD_VERSION (OLV_RESULT_STATUS(2001)) +#define OLV_RESULT_FAILED_REQUEST (OLV_RESULT_STATUS(2003)) +#define OLV_RESULT_INVALID_XML (OLV_RESULT_STATUS(2004)) +#define OLV_RESULT_INVALID_TEXT_FIELD (OLV_RESULT_STATUS(2006)) +#define OLV_RESULT_INVALID_INTEGER_FIELD (OLV_RESULT_STATUS(2007)) + +#define OLV_CLIENT_ID "87cd32617f1985439ea608c2746e4610" + +#define OLV_VERSION_MAJOR 5 +#define OLV_VERSION_MINOR 0 +#define OLV_VERSION_PATCH 3 + +namespace nn +{ + namespace olv + { + struct ParamPackStorage + { + uint64_t titleId; + uint32_t accessKey; + uint32_t platformId; + uint8_t regionId, languageId, countryId, areaId; + uint8_t networkRestriction, friendRestriction; + uint32_t ratingRestriction; + uint8_t ratingOrganization; + uint64_t transferableId; + char tzName[72]; + uint64_t utcOffset; + char encodedParamPack[512]; + }; + + struct DiscoveryResultStorage + { + sint32 has_error; + char serviceToken[512]; + char userAgent[64]; + char apiEndpoint[256]; + char portalEndpoint[256]; + }; + + extern ParamPackStorage g_ParamPack; + extern DiscoveryResultStorage g_DiscoveryResults; + extern uint32_t g_ReportTypes; + extern bool g_IsInitialized; + extern bool g_IsOnlineMode; + + static void InitializeOliveRequest(CurlRequestHelper& req) + { + req.addHeaderField("X-Nintendo-ServiceToken", g_DiscoveryResults.serviceToken); + req.addHeaderField("X-Nintendo-ParamPack", g_ParamPack.encodedParamPack); + curl_easy_setopt(req.getCURL(), CURLOPT_USERAGENT, g_DiscoveryResults.userAgent); + } + + static void appendQueryToURL(char* url, const char* query) + { + size_t urlLength = strlen(url); + size_t queryLength = strlen(query); + + char* delimiter = strchr(url, '?'); + if (delimiter) + snprintf(url + urlLength, queryLength + 2, "&%s", query); + else + snprintf(url + urlLength, queryLength + 2, "?%s", query); + } + + static sint32 CheckOliveResponse(pugi::xml_document& doc) + { + + /* + + 1 + 1 + 400 + 4 + SERVICE_CLOSED + + */ + + pugi::xml_node resultNode = doc.child("result"); + if (!resultNode) + { + cemuLog_log(LogType::Force, "Discovery response doesn't contain ..."); + return OLV_RESULT_INVALID_XML; + } + + std::string_view has_error = resultNode.child_value("has_error"); + std::string_view version = resultNode.child_value("version"); + std::string_view code = resultNode.child_value("code"); + std::string_view error_code = resultNode.child_value("error_code"); + + if (has_error.compare("1") == 0) + { + int codeVal = StringHelpers::ToInt(error_code, -1); + if (codeVal < 0) + { + codeVal = StringHelpers::ToInt(code, -1); + return OLV_RESULT_STATUS(codeVal + 4000); + } + return OLV_RESULT_STATUS(codeVal + 5000); + + } + + if (version.compare("1") != 0) + return OLV_RESULT_BAD_VERSION; // Version mismatch + + return OLV_RESULT_SUCCESS; + } + + sint32 olv_copy_wstr(char16_t* dest, const char16_t* src, uint32_t maxSize, uint32_t destSize); + size_t olv_wstrnlen(const char16_t* str, size_t max_len); + char16_t* olv_wstrncpy(char16_t* dest, const char16_t* src, size_t n); + +#pragma pack(push, 1) + struct TGAHeader + { + uint8 idLength; + uint8 colorMapType; + uint8 imageType; + uint16 first_entry_idx; + uint16 colormap_length; + uint8 bpp; + uint16 x_origin; + uint16 y_origin; + uint16 width; + uint16 height; + uint8 pixel_depth_bpp; + uint8 image_desc_bits; + }; +#pragma pack(pop) + static_assert(sizeof(nn::olv::TGAHeader) == 0x12, "sizeof(nn::olv::TGAHeader != 0x12"); + + enum TGACheckType : uint32 + { + CHECK_PAINTING = 0, + CHECK_COMMUNITY_ICON = 1, + CHECK_100x100_200x200 = 2 + }; + + + bool CheckTGA(const uint8* pTgaFile, uint32 pTgaFileLen, TGACheckType checkType); + sint32 DecodeTGA(uint8* pInBuffer, uint32 inSize, uint8* pOutBuffer, uint32 outSize, TGACheckType checkType); + sint32 EncodeTGA(uint8* pInBuffer, uint32 inSize, uint8* pOutBuffer, uint32 outSize, TGACheckType checkTyp); + + bool CompressTGA(uint8* pOutBuffer, uint32* pOutSize, uint8* pInBuffer, uint32 inSize); + bool DecompressTGA(uint8* pOutBuffer, uint32* pOutSize, uint8* pInBuffer, uint32 inSize); + + + bool GetCommunityIdFromCode(uint32* pOutId, const char* pCode); + bool FormatCommunityCode(char* pOutCode, uint32* outLen, uint32 communityId); + + sint32 olv_curlformcode_to_error(CURLFORMcode code); + } +} diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp new file mode 100644 index 00000000..8df14ce0 --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp @@ -0,0 +1,231 @@ +#include "nn_olv_DownloadCommunityTypes.h" + +namespace nn +{ + namespace olv + { + + sint32 DownloadCommunityDataList_AsyncRequestImpl( + CurlRequestHelper& req, const char* reqUrl, + DownloadedCommunityData* pOutList, uint32* pOutNum, uint32 numMaxList, const DownloadCommunityDataListParam* pParam); + + + sint32 DownloadCommunityDataList_AsyncRequest( + CurlRequestHelper& req, const char* reqUrl, coreinit::OSEvent* requestDoneEvent, + DownloadedCommunityData* pOutList, uint32* pOutNum, uint32 numMaxList, const DownloadCommunityDataListParam* pParam + ) + { + sint32 res = DownloadCommunityDataList_AsyncRequestImpl(req, reqUrl, pOutList, pOutNum, numMaxList, pParam); + coreinit::OSSignalEvent(requestDoneEvent); + return res; + } + + sint32 DownloadCommunityDataList(DownloadedCommunityData* pOutList, uint32* pOutNum, uint32 numMaxList, const DownloadCommunityDataListParam* pParam) + { + if (!g_IsInitialized) + return OLV_RESULT_NOT_INITIALIZED; + + if (!g_IsOnlineMode) + return OLV_RESULT_OFFLINE_MODE_REQUEST; + + if (!pOutList || !pOutNum || !pParam) + return OLV_RESULT_INVALID_PTR; + + if (!numMaxList) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + for (int i = 0; i < numMaxList; i++) + DownloadedCommunityData::Clean(&pOutList[i]); + + char reqUrl[2048]; + sint32 res = pParam->GetRawDataUrl(reqUrl, sizeof(reqUrl)); + if (res < 0) + return res; + + CurlRequestHelper req; + req.initate(reqUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); + InitializeOliveRequest(req); + + StackAllocator requestDoneEvent; + coreinit::OSInitEvent(requestDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + std::future requestRes = std::async(std::launch::async, DownloadCommunityDataList_AsyncRequest, + std::ref(req), reqUrl, requestDoneEvent.GetPointer(), pOutList, pOutNum, numMaxList, pParam); + coreinit::OSWaitEvent(requestDoneEvent); + + return requestRes.get(); + } + + sint32 DownloadCommunityDataList_AsyncRequestImpl( + CurlRequestHelper& req, const char* reqUrl, + DownloadedCommunityData* pOutList, uint32* pOutNum, uint32 numMaxList, const DownloadCommunityDataListParam* pParam + ) + { + + bool reqResult = req.submitRequest(); + long httpCode = 0; + curl_easy_getinfo(req.getCURL(), CURLINFO_RESPONSE_CODE, &httpCode); + + + if (!reqResult) + { + cemuLog_log(LogType::Force, "Failed request: {} ({})", reqUrl, httpCode); + if (!(httpCode >= 400)) + return OLV_RESULT_FAILED_REQUEST; + } + + pugi::xml_document doc; + if (!doc.load_buffer(req.getReceivedData().data(), req.getReceivedData().size())) + { + cemuLog_log(LogType::Force, fmt::format("Invalid XML in community download response")); + return OLV_RESULT_INVALID_XML; + } + + sint32 responseError = CheckOliveResponse(doc); + if (responseError < 0) + return responseError; + + if (httpCode != 200) + return OLV_RESULT_STATUS(httpCode + 4000); + + std::string request_name = doc.select_single_node("//request_name").node().child_value(); + if (request_name.size() == 0) + { + cemuLog_log(LogType::Force, "Community download response doesn't contain "); + return OLV_RESULT_INVALID_XML; + } + + if ((request_name.compare("communities") != 0) && (request_name.compare("specified_communities") != 0)) + { + cemuLog_log(LogType::Force, "Community download response isn't \"communities\" or \"specified_communities\""); + return OLV_RESULT_INVALID_XML; + } + + pugi::xml_node communities = doc.select_single_node("//communities").node(); + if (!communities) + { + cemuLog_log(LogType::Force, "Community download response doesn't contain "); + return OLV_RESULT_INVALID_XML; + } + + int idx = 0; + for (pugi::xml_node communityNode : communities.children("community")) + { + if (idx >= numMaxList) + break; + + DownloadedCommunityData* pOutData = &pOutList[idx]; + + std::string_view app_data = communityNode.child_value("app_data"); + std::string_view community_id = communityNode.child_value("community_id"); + std::string_view name = communityNode.child_value("name"); + std::string_view description = communityNode.child_value("description"); + std::string_view pid = communityNode.child_value("pid"); + std::string_view icon = communityNode.child_value("icon"); + std::string_view mii = communityNode.child_value("mii"); + std::string_view screen_name = communityNode.child_value("screen_name"); + + if (app_data.size() != 0) + { + auto app_data_bin = NCrypto::base64Decode(app_data); + if (app_data_bin.size() != 0) + { + memcpy(pOutData->appData, app_data_bin.data(), std::min(size_t(0x400), app_data_bin.size())); + pOutData->flags |= DownloadedCommunityData::FLAG_HAS_APP_DATA; + pOutData->appDataLen = app_data_bin.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + sint64 community_id_val = StringHelpers::ToInt64(community_id, -1); + if (community_id_val == -1) + return OLV_RESULT_INVALID_INTEGER_FIELD; + + pOutData->communityId = community_id_val; + + if (name.size() != 0) + { + auto name_utf16 = StringHelpers::FromUtf8(name).substr(0, 128); + if (name_utf16.size() != 0) + { + for (int i = 0; i < name_utf16.size(); i++) + pOutData->titleText[i] = name_utf16.at(i).bevalue(); + + pOutData->flags |= DownloadedCommunityData::FLAG_HAS_TITLE_TEXT; + pOutData->titleTextMaxLen = name_utf16.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + if (description.size() != 0) + { + auto description_utf16 = StringHelpers::FromUtf8(description).substr(0, 256); + if (description_utf16.size() != 0) + { + for (int i = 0; i < description_utf16.size(); i++) + pOutData->description[i] = description_utf16.at(i).bevalue(); + + pOutData->flags |= DownloadedCommunityData::FLAG_HAS_DESC_TEXT; + pOutData->descriptionMaxLen = description_utf16.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + sint64 pid_val = StringHelpers::ToInt64(pid, -1); + if (pid_val == -1) + return OLV_RESULT_INVALID_INTEGER_FIELD; + + pOutData->pid = pid_val; + + if (icon.size() != 0) + { + auto icon_bin = NCrypto::base64Decode(icon); + if (icon_bin.size() != 0) + { + memcpy(pOutData->iconData, icon_bin.data(), std::min(size_t(0x1002c), icon_bin.size())); + pOutData->flags |= DownloadedCommunityData::FLAG_HAS_ICON_DATA; + pOutData->iconDataSize = icon_bin.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + if (mii.size() != 0) + { + auto mii_bin = NCrypto::base64Decode(mii); + if (mii_bin.size() != 0) + { + memcpy(pOutData->miiFFLStoreData, mii_bin.data(), std::min(size_t(96), mii_bin.size())); + pOutData->flags |= DownloadedCommunityData::FLAG_HAS_MII_DATA; + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + if (screen_name.size() != 0) + { + auto screen_name_utf16 = StringHelpers::FromUtf8(screen_name).substr(0, 32); + if (screen_name_utf16.size() != 0) + { + for (int i = 0; i < screen_name_utf16.size(); i++) + pOutData->miiDisplayName[i] = screen_name_utf16.at(i).bevalue(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + idx++; + + } + + *pOutNum = _swapEndianU32(idx); + sint32 res = OLV_RESULT_SUCCESS; + if (idx > 0) + res = 0; // nn_olv doesn't do it like that, but it's the same effect. I have no clue why it returns 0 when you have 1+ communities downloaded + + return res; + } + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.h b/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.h new file mode 100644 index 00000000..3f5df35c --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.h @@ -0,0 +1,530 @@ +#pragma once + +#include "Cemu/ncrypto/ncrypto.h" +#include "config/ActiveSettings.h" + +#include "Cafe/OS/libs/nn_olv/nn_olv_Common.h" + +namespace nn +{ + namespace olv + { + + class DownloadedCommunityData + { + public: + static const inline uint32 FLAG_HAS_TITLE_TEXT = (1 << 0); + static const inline uint32 FLAG_HAS_DESC_TEXT = (1 << 1); + static const inline uint32 FLAG_HAS_APP_DATA = (1 << 2); + static const inline uint32 FLAG_HAS_ICON_DATA = (1 << 3); + static const inline uint32 FLAG_HAS_MII_DATA = (1 << 4); + + DownloadedCommunityData() + { + this->titleTextMaxLen = 0; + this->appDataLen = 0; + this->descriptionMaxLen = 0; + this->pid = 0; + this->communityId = 0; + this->flags = 0; + this->iconDataSize = 0; + this->miiDisplayName[0] = 0; + } + static DownloadedCommunityData* __ctor(DownloadedCommunityData* _this) + { + if (!_this) + { + assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN + return nullptr; + } + else + return new (_this) DownloadedCommunityData(); + } + + static DownloadedCommunityData* Clean(DownloadedCommunityData* data) + { + data->flags = 0; + data->communityId = 0; + data->pid = 0; + data->iconData[0] = 0; + data->titleTextMaxLen = 0; + data->appData[0] = 0; + data->appDataLen = 0; + data->description[0] = 0; + data->descriptionMaxLen = 0; + data->iconDataSize = 0; + data->titleText[0] = 0; + data->miiDisplayName[0] = 0; + return data; + } + + bool TestFlags(uint32 flags) const + { + return (this->flags & flags) != 0; + } + static bool __TestFlags(DownloadedCommunityData* _this, uint32 flags) + { + return _this->TestFlags(flags); + } + + uint32 GetCommunityId() const + { + return this->communityId; + } + static uint32 __GetCommunityId(DownloadedCommunityData* _this) + { + return _this->GetCommunityId(); + } + + sint32 GetCommunityCode(char* pBuffer, uint32 bufferSize) const + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (bufferSize <= 12) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + uint32 len = 0; + if (FormatCommunityCode(pBuffer, &len, this->communityId)) + return OLV_RESULT_SUCCESS; + + return OLV_RESULT_INVALID_PARAMETER; + } + static sint32 __GetCommunityCode(DownloadedCommunityData* _this, char* pBuffer, uint32 bufferSize) + { + return _this->GetCommunityCode(pBuffer, bufferSize); + } + + uint32 GetOwnerPid() const + { + return this->pid; + } + static uint32 __GetOwnerPid(DownloadedCommunityData* _this) + { + return _this->GetOwnerPid(); + } + + sint32 GetTitleText(char16_t* pBuffer, uint32 numChars) + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (numChars) + { + if (!this->TestFlags(FLAG_HAS_TITLE_TEXT)) + return OLV_RESULT_MISSING_DATA; + + memset(pBuffer, 0, 2 * numChars); + uint32 readSize = this->titleTextMaxLen; + if (numChars < readSize) + readSize = numChars; + + olv_wstrncpy(pBuffer, this->titleText, readSize); + return OLV_RESULT_SUCCESS; + } + + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + static sint32 __GetTitleText(DownloadedCommunityData* _this, char16_t* pBuffer, uint32 numChars) + { + return _this->GetTitleText(pBuffer, numChars); + } + + sint32 GetDescriptionText(char16_t* pBuffer, uint32 numChars) + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (numChars) + { + if (!this->TestFlags(FLAG_HAS_DESC_TEXT)) + return OLV_RESULT_MISSING_DATA; + + memset(pBuffer, 0, 2 * numChars); + olv_wstrncpy(pBuffer, this->description, numChars); + return OLV_RESULT_SUCCESS; + } + + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + static sint32 __GetDescriptionText(DownloadedCommunityData* _this, char16_t* pBuffer, uint32 numChars) + { + return _this->GetDescriptionText(pBuffer, numChars); + } + + sint32 GetAppData(uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + uint32 appDataSize = bufferSize; + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (bufferSize) + { + if (!this->TestFlags(FLAG_HAS_APP_DATA)) + return OLV_RESULT_MISSING_DATA; + + if (this->appDataLen < appDataSize) + appDataSize = this->appDataLen; + + memcpy(pBuffer, this->appData, appDataSize); + if (pOutSize) + *pOutSize = appDataSize; + + return OLV_RESULT_SUCCESS; + } + + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + static sint32 __GetAppData(DownloadedCommunityData* _this, uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + return _this->GetAppData(pBuffer, pOutSize, bufferSize); + } + + uint32 GetAppDataSize() const + { + if (this->TestFlags(FLAG_HAS_APP_DATA)) + return this->appDataLen; + + return 0; + } + static uint32 __GetAppDataSize(DownloadedCommunityData* _this) + { + return _this->GetAppDataSize(); + } + + sint32 GetIconData(uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (bufferSize < sizeof(this->iconData)) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + if (!this->TestFlags(FLAG_HAS_ICON_DATA)) + return OLV_RESULT_MISSING_DATA; + + sint32 decodeRes = DecodeTGA(this->iconData, this->iconDataSize, pBuffer, bufferSize, TGACheckType::CHECK_COMMUNITY_ICON); + if (decodeRes >= 0) + { + if (pOutSize) + *pOutSize = (uint32)decodeRes; + + return OLV_RESULT_SUCCESS; + } + + if (pOutSize) + *pOutSize = 0; + + if (decodeRes == -1) + cemuLog_log(LogType::Force, "OLIVE - icon uncompress failed.\n"); + else if (decodeRes == -2) + cemuLog_log(LogType::Force, "OLIVE - icon decode error. NOT TGA.\n"); + + return OLV_RESULT_INVALID_TEXT_FIELD; + } + static sint32 __GetIconData(DownloadedCommunityData* _this, uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + return _this->GetIconData(pBuffer, pOutSize, bufferSize); + } + + sint32 GetOwnerMiiData(/* FFLStoreData* */void* pBuffer) const + { + if (!this->TestFlags(FLAG_HAS_MII_DATA)) + return OLV_RESULT_MISSING_DATA; + + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + memcpy(pBuffer, this->miiFFLStoreData, sizeof(this->miiFFLStoreData)); + return OLV_RESULT_SUCCESS; + } + static sint32 __GetOwnerMiiData(DownloadedCommunityData* _this, /* FFLStoreData* */void* pBuffer) + { + return _this->GetOwnerMiiData(pBuffer); + } + + const char16_t* GetOwnerMiiNickname() const + { + if (this->miiDisplayName[0]) + return this->miiDisplayName; + + return nullptr; + } + static const char16_t* __GetOwnerMiiNickname(DownloadedCommunityData* _this) + { + return _this->GetOwnerMiiNickname(); + } + + public: + uint32be flags; + uint32be communityId; + uint32be pid; + char16_t titleText[128]; + uint32be titleTextMaxLen; + char16_t description[256]; + uint32be descriptionMaxLen; + uint8 appData[1024]; + uint32be appDataLen; + uint8 iconData[65580]; + uint32be iconDataSize; + uint8 miiFFLStoreData[96]; + char16_t miiDisplayName[32]; + uint8 unk[6168]; + }; + static_assert(sizeof(nn::olv::DownloadedCommunityData) == 0x12000, "sizeof(nn::olv::DownloadedCommunityData) != 0x12000"); + + class DownloadCommunityDataListParam + { + public: + static const inline uint32 FLAG_FILTER_FAVORITES = (1 << 0); + static const inline uint32 FLAG_FILTER_OFFICIALS = (1 << 1); + static const inline uint32 FLAG_FILTER_OWNED = (1 << 2); + static const inline uint32 FLAG_QUERY_MII_DATA = (1 << 3); + static const inline uint32 FLAG_QUERY_ICON_DATA = (1 << 4); + + DownloadCommunityDataListParam() + { + this->flags = 0; + this->communityDownloadLimit = 0; + this->communityId = 0; + + for (int i = 0; i < 20; i++) + this->additionalCommunityIdList[i] = -2; + } + static DownloadCommunityDataListParam* __ctor(DownloadCommunityDataListParam* _this) + { + if (!_this) + { + assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN + return nullptr; + } + else + return new (_this) DownloadCommunityDataListParam(); + } + + sint32 SetFlags(uint32 flags) + { + this->flags = flags; + return OLV_RESULT_SUCCESS; + } + static sint32 __SetFlags(DownloadCommunityDataListParam* _this, uint32 flags) + { + return _this->SetFlags(flags); + } + + sint32 SetCommunityId(uint32 communityId) + { + if (communityId == -1) + return OLV_RESULT_INVALID_PARAMETER; + + this->communityId = communityId; + if (communityId) + { + if (!this->communityDownloadLimit) + this->communityDownloadLimit = 1; + } + + return OLV_RESULT_SUCCESS; + } + static sint32 __SetCommunityId(DownloadCommunityDataListParam* _this, uint32 communityId) + { + return _this->SetCommunityId(communityId); + } + + sint32 SetCommunityId(uint32 communityId, uint8 idx) + { + if (communityId == -1) + return OLV_RESULT_INVALID_PARAMETER; + + if (idx >= 20) + return OLV_RESULT_INVALID_PARAMETER; + + this->additionalCommunityIdList[idx] = communityId; + int validIdsCount = 0; + for (int i = 0; i < 20; i++ ) + { + if (this->additionalCommunityIdList[i] != -2) + ++validIdsCount; + } + + if (validIdsCount > this->communityDownloadLimit) + this->communityDownloadLimit = validIdsCount; + + return OLV_RESULT_SUCCESS; + } + static sint32 __SetCommunityId(DownloadCommunityDataListParam* _this, uint32 communityId, uint8 idx) + { + return _this->SetCommunityId(communityId, idx); + } + + sint32 SetCommunityDataMaxNum(uint32 num) + { + if (!num) + return OLV_RESULT_INVALID_PARAMETER; + + int validIdsCount = 0; + for (int i = 0; i < 20; ++i) + { + if (this->additionalCommunityIdList[i] != -2) + ++validIdsCount; + } + + if (validIdsCount > num) + return OLV_RESULT_INVALID_PARAMETER; + + this->communityDownloadLimit = num; + return OLV_RESULT_SUCCESS; + } + static sint32 __SetCommunityDataMaxNum(DownloadCommunityDataListParam* _this, uint32 num) + { + return _this->SetCommunityDataMaxNum(num); + } + + sint32 GetRawDataUrl(char* pBuffer, uint32 bufferSize) const + { + if (!g_IsOnlineMode) + return OLV_RESULT_OFFLINE_MODE_REQUEST; + + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (!bufferSize) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + char tmpFormatBuffer[64]; + char urlBuffer[1024]; + memset(urlBuffer, 0, sizeof(urlBuffer)); + + uint32 communityId; + int validIdsCount = 0; + for (int i = 0; i < 20; ++i) + { + if (this->additionalCommunityIdList[i] != -2) + { + communityId = this->additionalCommunityIdList[i]; + ++validIdsCount; + } + } + + if (validIdsCount) + { + if (this->communityId && this->communityId != -2) + return OLV_RESULT_INVALID_PARAMETER; + + uint32 unkFlag = this->flags & 0xFFFFFFE7; + if (unkFlag) + return OLV_RESULT_INVALID_PARAMETER; + + // It's how it's done in the real nn_olv, what even the fuck is this, never seen used yet. + snprintf(urlBuffer, sizeof(urlBuffer), "%s/v1/communities/%u.search", g_DiscoveryResults.apiEndpoint, communityId); + + for (int i = 0; i < 20; ++i) + { + if (this->additionalCommunityIdList[i] != -2) + { + snprintf(tmpFormatBuffer, 64, "community_id=%u", this->additionalCommunityIdList[i].value()); + appendQueryToURL(urlBuffer, tmpFormatBuffer); + ++unkFlag; + } + } + } + else + snprintf(urlBuffer, sizeof(urlBuffer), "%s/v1/communities", g_DiscoveryResults.apiEndpoint); + + if (this->communityId) + { + snprintf(tmpFormatBuffer, 64, "community_id=%u", this->communityId.value()); + appendQueryToURL(urlBuffer, tmpFormatBuffer); + } + else + { + uint32 filterBy_favorite = (this->flags & FLAG_FILTER_FAVORITES) != 0; + uint32 filterBy_official = (this->flags & FLAG_FILTER_OFFICIALS) != 0; + uint32 filterBy_selfmade = (this->flags & FLAG_FILTER_OWNED) != 0; + + if ((filterBy_favorite + filterBy_official + filterBy_selfmade) != 1) + return OLV_RESULT_INVALID_PARAMETER; + + snprintf(tmpFormatBuffer, 64, "limit=%u", this->communityDownloadLimit.value()); + appendQueryToURL(urlBuffer, tmpFormatBuffer); + + if (filterBy_favorite) + { + strncpy(tmpFormatBuffer, "type=favorite", 64); + appendQueryToURL(urlBuffer, tmpFormatBuffer); + } + else if (filterBy_official) + { + strncpy(tmpFormatBuffer, "type=official", 64); + appendQueryToURL(urlBuffer, tmpFormatBuffer); + } + else + { + strncpy(tmpFormatBuffer, "type=my", 64); + appendQueryToURL(urlBuffer, tmpFormatBuffer); + } + } + + if (this->flags & FLAG_QUERY_MII_DATA) + { + strncpy(tmpFormatBuffer, "with_mii=1", 64); + appendQueryToURL(urlBuffer, tmpFormatBuffer); + } + + if (this->flags & FLAG_QUERY_ICON_DATA) + { + strncpy(tmpFormatBuffer, "with_icon=1", 64); + appendQueryToURL(urlBuffer, tmpFormatBuffer); + } + + int res = snprintf(pBuffer, bufferSize, "%s", urlBuffer); + if (res < 0) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + return OLV_RESULT_SUCCESS; + } + static sint32 __GetRawDataUrl(DownloadCommunityDataListParam* _this, char* pBuffer, uint32 bufferSize) + { + return _this->GetRawDataUrl(pBuffer, bufferSize); + } + + public: + uint32be flags; + uint32be communityId; + uint32be communityDownloadLimit; + uint32be additionalCommunityIdList[20]; // Additional community ID filter list + uint8 unk[4004]; // Looks unused lol, probably reserved data + }; + static_assert(sizeof(nn::olv::DownloadCommunityDataListParam) == 0x1000, "sizeof(nn::olv::DownloadCommunityDataListParam) != 0x1000"); + + sint32 DownloadCommunityDataList(DownloadedCommunityData* pOutList, uint32* pOutNum, uint32 numMaxList, const DownloadCommunityDataListParam* pParam); + + static void loadOliveDownloadCommunityTypes() + { + cafeExportRegisterFunc(DownloadedCommunityData::__ctor, "nn_olv", "__ct__Q3_2nn3olv23DownloadedCommunityDataFv", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__TestFlags, "nn_olv", "TestFlags__Q3_2nn3olv23DownloadedCommunityDataCFUi", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv23DownloadedCommunityDataCFv", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__GetCommunityCode, "nn_olv", "GetCommunityCode__Q3_2nn3olv23DownloadedCommunityDataCFPcUi", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__GetOwnerPid, "nn_olv", "GetOwnerPid__Q3_2nn3olv23DownloadedCommunityDataCFv", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__GetTitleText, "nn_olv", "GetTitleText__Q3_2nn3olv23DownloadedCommunityDataCFPwUi", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__GetDescriptionText, "nn_olv", "GetDescriptionText__Q3_2nn3olv23DownloadedCommunityDataCFPwUi", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__GetAppData, "nn_olv", "GetAppData__Q3_2nn3olv23DownloadedCommunityDataCFPUcPUiUi", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv23DownloadedCommunityDataCFv", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__GetIconData, "nn_olv", "GetIconData__Q3_2nn3olv23DownloadedCommunityDataCFPUcPUiUi", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__GetOwnerMiiData, "nn_olv", "GetOwnerMiiData__Q3_2nn3olv23DownloadedCommunityDataCFP12FFLStoreData", LogType::None); + cafeExportRegisterFunc(DownloadedCommunityData::__GetOwnerMiiNickname, "nn_olv", "GetOwnerMiiNickname__Q3_2nn3olv23DownloadedCommunityDataCFv", LogType::None); + + cafeExportRegisterFunc(DownloadCommunityDataListParam::__ctor, "nn_olv", "__ct__Q3_2nn3olv30DownloadCommunityDataListParamFv", LogType::None); + cafeExportRegisterFunc(DownloadCommunityDataListParam::__SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv30DownloadCommunityDataListParamFUi", LogType::None); + cafeExportRegisterFunc(DownloadCommunityDataListParam::__SetCommunityDataMaxNum, "nn_olv", "SetCommunityDataMaxNum__Q3_2nn3olv30DownloadCommunityDataListParamFUi", LogType::None); + cafeExportRegisterFunc(DownloadCommunityDataListParam::__GetRawDataUrl, "nn_olv", "GetRawDataUrl__Q3_2nn3olv30DownloadCommunityDataListParamCFPcUi", LogType::None); + + cafeExportRegisterFunc((sint32 (*)(DownloadCommunityDataListParam*, uint32))DownloadCommunityDataListParam::__SetCommunityId, + "nn_olv", "SetCommunityId__Q3_2nn3olv30DownloadCommunityDataListParamFUi", LogType::None); + cafeExportRegisterFunc((sint32(*)(DownloadCommunityDataListParam*, uint32, uint8))DownloadCommunityDataListParam::__SetCommunityId, + "nn_olv", "SetCommunityId__Q3_2nn3olv30DownloadCommunityDataListParamFUiUc", LogType::None); + + cafeExportRegisterFunc(DownloadCommunityDataList, "nn_olv", "DownloadCommunityDataList__Q2_2nn3olvFPQ3_2nn3olv23DownloadedCommunityDataPUiUiPCQ3_2nn3olv30DownloadCommunityDataListParam", LogType::None); + } + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp new file mode 100644 index 00000000..b71c931a --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp @@ -0,0 +1,312 @@ +#pragma once + +#include "nn_olv_InitializeTypes.h" +#include "CafeSystem.h" +#include "Cafe/OS/libs/nn_act/nn_act.h" +#include + +namespace nn +{ + namespace olv + { + + uint32_t g_ReportTypes = 0; + bool g_IsOnlineMode = false; + bool g_IsInitialized = false; + ParamPackStorage g_ParamPack; + DiscoveryResultStorage g_DiscoveryResults; + + uint64 get_utc_offset() + { + time_t gmt, rawtime = time(NULL); + struct tm* ptm; + +#if !defined(WIN32) + struct tm gbuf; + ptm = gmtime_r(&rawtime, &gbuf); +#else + ptm = gmtime(&rawtime); +#endif + + ptm->tm_isdst = -1; + gmt = mktime(ptm); + + return (uint64)difftime(rawtime, gmt); + } + + sint32 GetOlvAccessKey(uint32_t* pOutKey) + { + *pOutKey = 0; + uint32_t accessKey = CafeSystem::GetForegroundTitleOlvAccesskey(); + if (accessKey == -1) + return OLV_RESULT_STATUS(1102); + + *pOutKey = accessKey; + return OLV_RESULT_SUCCESS; + } + + sint32 CreateParamPack(uint64_t titleId, uint32_t accessKey) + { + g_ParamPack.languageId = uint8(GetConfig().console_language.GetValue()); + + uint32be simpleAddress = 0; + nn::act::GetSimpleAddressIdEx(&simpleAddress, nn::act::ACT_SLOT_CURRENT); + uint32 countryCode = nn::act::getCountryCodeFromSimpleAddress(simpleAddress); + + g_ParamPack.countryId = countryCode; + g_ParamPack.titleId = titleId; + g_ParamPack.platformId = 1; + + g_ParamPack.areaId = (simpleAddress >> 8) & 0xff; + + MCPHANDLE handle = MCP_Open(); + SysProdSettings sysProdSettings; + MCP_GetSysProdSettings(handle, &sysProdSettings); + MCP_Close(handle); + + g_ParamPack.regionId = sysProdSettings.platformRegion; + g_ParamPack.accessKey = accessKey; + + g_ParamPack.networkRestriction = 0; + g_ParamPack.friendRestriction = 0; + g_ParamPack.ratingRestriction = 18; + g_ParamPack.ratingOrganization = 4; // PEGI ? + + uint64 transferrableId; + nn::act::GetTransferableIdEx(&transferrableId, (titleId >> 8) & 0xFFFFF, nn::act::ACT_SLOT_CURRENT); + g_ParamPack.transferableId = transferrableId; + + strcpy(g_ParamPack.tzName, "CEMU/Olive"); // Should be nn::act::GetTimeZoneId + g_ParamPack.utcOffset = get_utc_offset(); + + char paramPackStr[1024]; + snprintf( + paramPackStr, + sizeof(paramPackStr), + "\\%s\\%llu\\%s\\%u\\%s\\%u\\%s\\%d\\%s\\%d\\%s\\%d\\%s\\%d\\%s\\%d\\%s\\%d\\%s\\%u\\%s\\%d\\%s\\%llu\\" + "%s\\%s\\%s\\%lld\\", + "title_id", + g_ParamPack.titleId, + "access_key", + g_ParamPack.accessKey, + "platform_id", + g_ParamPack.platformId, + "region_id", + g_ParamPack.regionId, + "language_id", + g_ParamPack.languageId, + "country_id", + g_ParamPack.countryId, + "area_id", + g_ParamPack.areaId, + "network_restriction", + g_ParamPack.networkRestriction, + "friend_restriction", + g_ParamPack.friendRestriction, + "rating_restriction", + g_ParamPack.ratingRestriction, + "rating_organization", + g_ParamPack.ratingOrganization, + "transferable_id", + g_ParamPack.transferableId, + "tz_name", + g_ParamPack.tzName, + "utc_offset", + g_ParamPack.utcOffset); + std::string encodedParamPack = NCrypto::base64Encode(paramPackStr, strnlen(paramPackStr, 1024)); + memset(&g_ParamPack.encodedParamPack, 0, sizeof(g_ParamPack.encodedParamPack)); + memcpy(&g_ParamPack.encodedParamPack, encodedParamPack.data(), encodedParamPack.size()); + + return OLV_RESULT_SUCCESS; + } + + sint32 MakeDiscoveryRequest_AsyncRequestImpl(CurlRequestHelper& req, const char* reqUrl) + { + bool reqResult = req.submitRequest(); + long httpCode = 0; + curl_easy_getinfo(req.getCURL(), CURLINFO_RESPONSE_CODE, &httpCode); + if (!reqResult) + { + cemuLog_log(LogType::Force, "Failed request: {} ({})", reqUrl, httpCode); + if (!(httpCode >= 400)) + return OLV_RESULT_FAILED_REQUEST; + } + + pugi::xml_document doc; + if (!doc.load_buffer(req.getReceivedData().data(), req.getReceivedData().size())) + { + cemuLog_log(LogType::Force, fmt::format("Invalid XML in discovery service response")); + return OLV_RESULT_INVALID_XML; + } + + sint32 responseError = CheckOliveResponse(doc); + if (responseError < 0) + return responseError; + + if (httpCode != 200) + return OLV_RESULT_STATUS(httpCode + 4000); + + + /* + + 0 + 1 + + api.olv.pretendo.cc + api.olv.pretendo.cc + portal.olv.pretendo.cc + ctr.olv.pretendo.cc + + + */ + + pugi::xml_node resultNode = doc.child("result"); + if (!resultNode) + { + cemuLog_log(LogType::Force, "Discovery response doesn't contain "); + return OLV_RESULT_INVALID_XML; + } + + pugi::xml_node endpointNode = resultNode.child("endpoint"); + if (!endpointNode) + { + cemuLog_log(LogType::Force, "Discovery response doesn't contain "); + return OLV_RESULT_INVALID_XML; + } + + // Yes it only uses and + std::string_view host = endpointNode.child_value("host"); + std::string_view portal_host = endpointNode.child_value("portal_host"); + + snprintf(g_DiscoveryResults.apiEndpoint, sizeof(g_DiscoveryResults.apiEndpoint), "https://%s", host.data()); + snprintf(g_DiscoveryResults.portalEndpoint, sizeof(g_DiscoveryResults.portalEndpoint), "https://%s", portal_host.data()); + + return OLV_RESULT_SUCCESS; + } + + sint32 MakeDiscoveryRequest_AsyncRequest(CurlRequestHelper& req, const char* reqUrl, coreinit::OSEvent* requestDoneEvent) + { + sint32 res = MakeDiscoveryRequest_AsyncRequestImpl(req, reqUrl); + coreinit::OSSignalEvent(requestDoneEvent); + return res; + } + + sint32 MakeDiscoveryRequest() + { + // ============================================================================= + // Discovery request | https://discovery.olv.nintendo.net/v1/endpoint + // ============================================================================= + + CurlRequestHelper req; + std::string requestUrl; + switch (ActiveSettings::GetNetworkService()) + { + case NetworkService::Pretendo: + requestUrl = PretendoURLs::OLVURL; + break; + case NetworkService::Custom: + requestUrl = GetNetworkConfig().urls.OLV.GetValue(); + break; + case NetworkService::Nintendo: + default: + requestUrl = NintendoURLs::OLVURL; + break; + } + + req.initate(requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); + InitializeOliveRequest(req); + + StackAllocator requestDoneEvent; + coreinit::OSInitEvent(requestDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + std::future requestRes = std::async(std::launch::async, MakeDiscoveryRequest_AsyncRequest, std::ref(req), requestUrl.c_str(), requestDoneEvent.GetPointer()); + coreinit::OSWaitEvent(requestDoneEvent); + + return requestRes.get(); + } + + sint32 Initialize(nn::olv::InitializeParam* pParam) + { + if (g_IsInitialized) + return OLV_RESULT_ALREADY_INITIALIZED; + + if (!pParam->m_Work) + { + g_IsInitialized = false; + return OLV_RESULT_INVALID_PTR; + } + + if (pParam->m_WorkSize < 0x10000) + { + g_IsInitialized = false; + return OLV_RESULT_INVALID_SIZE; + } + + uint32_t accessKey; + int32_t olvAccessKeyStatus = GetOlvAccessKey(&accessKey); + if (olvAccessKeyStatus < 0) + { + g_IsInitialized = false; + return olvAccessKeyStatus; + } + + uint64_t tid = CafeSystem::GetForegroundTitleId(); + int32_t createParamPackResult = CreateParamPack(tid, accessKey); + if (createParamPackResult < 0) + { + g_IsInitialized = false; + return createParamPackResult; + } + + g_IsInitialized = true; + + if ((pParam->m_Flags & InitializeParam::FLAG_OFFLINE_MODE) == 0) + { + + g_IsOnlineMode = true; + + independentServiceToken_t token; + sint32 res = (sint32)nn::act::AcquireIndependentServiceToken(&token, OLV_CLIENT_ID, 0); + if (res < 0) + { + g_IsInitialized = false; + return res; + } + + // Assuming we're always a production WiiU (non-dev) + uint32 uniqueId = (CafeSystem::GetForegroundTitleId() >> 8) & 0xFFFFF; + + char versionBuffer[32]; + snprintf(versionBuffer, sizeof(versionBuffer), "%d.%d.%d", OLV_VERSION_MAJOR, OLV_VERSION_MINOR, OLV_VERSION_PATCH); + snprintf(g_DiscoveryResults.userAgent, sizeof(g_DiscoveryResults.userAgent), "%s/%s-%s/%d", "WiiU", "POLV", versionBuffer, uniqueId); + + memcpy(g_DiscoveryResults.serviceToken, token.token, sizeof(g_DiscoveryResults.serviceToken)); + + sint32 discoveryRes = MakeDiscoveryRequest(); + if (discoveryRes < 0) + g_IsInitialized = false; + + return discoveryRes; + } + + return OLV_RESULT_SUCCESS; + } + + namespace Report + { + uint32 GetReportTypes() + { + return g_ReportTypes; + } + + void SetReportTypes(uint32 reportTypes) + { + g_ReportTypes = reportTypes | 0x1000; + } + } + + bool IsInitialized() + { + return g_IsInitialized; + } + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.h b/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.h new file mode 100644 index 00000000..603b167c --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.h @@ -0,0 +1,128 @@ +#pragma once + +#include "Cemu/ncrypto/ncrypto.h" +#include "config/ActiveSettings.h" + +#include "Cafe/OS/libs/nn_olv/nn_olv_Common.h" +#include "Cafe/OS/libs/coreinit/coreinit_MCP.h" + + +namespace nn +{ + namespace olv + { + + class InitializeParam + { + public: + static const inline uint32 FLAG_OFFLINE_MODE = (1 << 0); + + InitializeParam() + { + this->m_Flags = 0; + this->m_ReportTypes = 7039; + this->m_SysArgsSize = 0; + this->m_Work = MEMPTR(nullptr); + this->m_SysArgs = MEMPTR(nullptr); + this->m_WorkSize = 0; + } + static InitializeParam* __ctor(InitializeParam* _this) + { + if (!_this) + { + assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN + return nullptr; + } + else + return new (_this) InitializeParam(); + } + + sint32 SetFlags(uint32 flags) + { + this->m_Flags = flags; + return OLV_RESULT_SUCCESS; + } + static sint32 __SetFlags(InitializeParam* _this, uint32 flags) + { + return _this->SetFlags(flags); + } + + sint32 SetWork(MEMPTR pWorkData, uint32 workDataSize) + { + if (!pWorkData) + return OLV_RESULT_INVALID_PTR; + if (workDataSize < 0x10000) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + this->m_Work = pWorkData; + this->m_WorkSize = workDataSize; + return OLV_RESULT_SUCCESS; + } + static uint32 __SetWork(InitializeParam* _this, MEMPTR pWorkData, uint32 workDataSize) + { + return _this->SetWork(pWorkData, workDataSize); + } + + sint32 SetReportTypes(uint32 reportTypes) + { + this->m_ReportTypes = reportTypes; + return OLV_RESULT_SUCCESS; + } + static sint32 __SetReportTypes(InitializeParam* _this, uint32 reportTypes) + { + return _this->SetReportTypes(reportTypes); + } + + sint32 SetSysArgs(MEMPTR pSysArgs, uint32 sysArgsSize) + { + if (!pSysArgs) + return OLV_RESULT_INVALID_PTR; + + if (!sysArgsSize) + return OLV_RESULT_INVALID_PARAMETER; + + this->m_SysArgs = pSysArgs; + this->m_SysArgsSize = sysArgsSize; + + return OLV_RESULT_SUCCESS; + } + static sint32 __SetSysArgs(InitializeParam* _this, MEMPTR pSysArgs, uint32 sysArgsSize) + { + return _this->SetSysArgs(pSysArgs, sysArgsSize); + } + + uint32be m_Flags; + uint32be m_ReportTypes; + MEMPTR m_Work; + uint32be m_WorkSize; + MEMPTR m_SysArgs; + uint32be m_SysArgsSize; + char unk[0x28]; + }; + static_assert(sizeof(nn::olv::InitializeParam) == 0x40, "sizeof(nn::olv::InitializeParam) != 0x40"); + + + namespace Report + { + uint32 GetReportTypes(); + void SetReportTypes(uint32 reportTypes); + } + + bool IsInitialized(); + sint32 Initialize(nn::olv::InitializeParam* pParam); + + static void loadOliveInitializeTypes() + { + cafeExportRegisterFunc(Initialize, "nn_olv", "Initialize__Q2_2nn3olvFPCQ3_2nn3olv15InitializeParam", LogType::None); + cafeExportRegisterFunc(IsInitialized, "nn_olv", "IsInitialized__Q2_2nn3olvFv", LogType::None); + cafeExportRegisterFunc(Report::GetReportTypes, "nn_olv", "GetReportTypes__Q3_2nn3olv6ReportFv", LogType::None); + cafeExportRegisterFunc(Report::SetReportTypes, "nn_olv", "SetReportTypes__Q3_2nn3olv6ReportFUi", LogType::None); + + cafeExportRegisterFunc(InitializeParam::__ctor, "nn_olv", "__ct__Q3_2nn3olv15InitializeParamFv", LogType::None); + cafeExportRegisterFunc(InitializeParam::__SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv15InitializeParamFUi", LogType::None); + cafeExportRegisterFunc(InitializeParam::__SetWork, "nn_olv", "SetWork__Q3_2nn3olv15InitializeParamFPUcUi", LogType::None); + cafeExportRegisterFunc(InitializeParam::__SetReportTypes, "nn_olv", "SetReportTypes__Q3_2nn3olv15InitializeParamFUi", LogType::None); + cafeExportRegisterFunc(InitializeParam::__SetSysArgs, "nn_olv", "SetSysArgs__Q3_2nn3olv15InitializeParamFPCvUi", LogType::None); + } + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp new file mode 100644 index 00000000..b76e6d63 --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp @@ -0,0 +1,304 @@ +#include "nn_olv_UploadCommunityTypes.h" +#include + +namespace nn +{ + namespace olv + { + + sint32 UploadCommunityData_AsyncRequestImpl(CurlRequestHelper& req, const char* reqUrl, + UploadedCommunityData* pOutData, UploadCommunityDataParam const* pParam); + + sint32 UploadCommunityData_AsyncRequest(CurlRequestHelper& req, const char* reqUrl, coreinit::OSEvent* requestDoneEvent, + UploadedCommunityData* pOutData, UploadCommunityDataParam const* pParam + ) + { + sint32 res = UploadCommunityData_AsyncRequestImpl(req, reqUrl, pOutData, pParam); + coreinit::OSSignalEvent(requestDoneEvent); + return res; + } + + sint32 UploadCommunityData(UploadedCommunityData* pOutData, UploadCommunityDataParam const* pParam) + { + if (!nn::olv::g_IsInitialized) + return OLV_RESULT_NOT_INITIALIZED; + + if (!nn::olv::g_IsOnlineMode) + return OLV_RESULT_OFFLINE_MODE_REQUEST; + + if (!pParam) + return OLV_RESULT_INVALID_PTR; + + if (pOutData) + UploadedCommunityData::Clean(pOutData); + + char requestUrl[512]; + if (pParam->flags & UploadCommunityDataParam::FLAG_DELETION) + { + if (!pParam->communityId) + return OLV_RESULT_INVALID_PARAMETER; + + snprintf(requestUrl, sizeof(requestUrl), "%s/v1/communities/%lu.delete", g_DiscoveryResults.apiEndpoint, pParam->communityId.value()); + } + else + { + if (pParam->communityId) + snprintf(requestUrl, sizeof(requestUrl), "%s/v1/communities/%lu", g_DiscoveryResults.apiEndpoint, pParam->communityId.value()); + else + snprintf(requestUrl, sizeof(requestUrl), "%s/v1/communities", g_DiscoveryResults.apiEndpoint); + } + + + CurlRequestHelper req; + req.initate(requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); + InitializeOliveRequest(req); + + StackAllocator requestDoneEvent; + coreinit::OSInitEvent(requestDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + std::future requestRes = std::async(std::launch::async, UploadCommunityData_AsyncRequest, std::ref(req), requestUrl, requestDoneEvent.GetPointer(), pOutData, pParam); + coreinit::OSWaitEvent(requestDoneEvent); + + return requestRes.get(); + } + + sint32 UploadCommunityData(UploadCommunityDataParam const* pParam) + { + return UploadCommunityData(nullptr, pParam); + } + + sint32 UploadCommunityData_AsyncRequestImpl(CurlRequestHelper& req, const char* reqUrl, + UploadedCommunityData* pOutData, UploadCommunityDataParam const* pParam) + { + sint32 res = OLV_RESULT_SUCCESS; + + std::string base64icon; + std::string form_name; + std::string form_desc; + std::string form_searchKey[5]; + std::string encodedAppData; + uint8* encodedIcon = nullptr; + + struct curl_httppost* post = nullptr; + struct curl_httppost* last = nullptr; + + try + { + if (!pParam->iconData.IsNull()) + { + encodedIcon = new uint8[pParam->iconDataLen]; + if (encodedIcon) + { + sint32 iconEncodeRes = EncodeTGA(pParam->iconData.GetPtr(), pParam->iconDataLen, encodedIcon, pParam->iconDataLen, TGACheckType::CHECK_COMMUNITY_ICON); + if (iconEncodeRes <= 0) + { + delete[] encodedIcon; + return OLV_RESULT_NOT_ENOUGH_SIZE; // ? + } + + base64icon = NCrypto::base64Encode(encodedIcon, iconEncodeRes); + res = olv_curlformcode_to_error( + curl_formadd(&post, &last, + CURLFORM_COPYNAME, "icon", + CURLFORM_PTRCONTENTS, base64icon.data(), + CURLFORM_CONTENTSLENGTH, base64icon.size(), + CURLFORM_END) + ); + + if (res < 0) + throw std::runtime_error("curl_formadd() error! - icon"); + } + } + + if (pParam->titleText[0]) + { + form_name = StringHelpers::ToUtf8((const uint16be*)pParam->titleText, 127); + res = olv_curlformcode_to_error( + curl_formadd(&post, &last, + CURLFORM_COPYNAME, "name", + CURLFORM_PTRCONTENTS, form_name.data(), + CURLFORM_CONTENTSLENGTH, form_name.size(), + CURLFORM_END) + ); + + if (res < 0) + throw std::runtime_error("curl_formadd() error! - name"); + } + + if (pParam->description[0]) + { + form_desc = StringHelpers::ToUtf8((const uint16be*)pParam->description, 255); + res = olv_curlformcode_to_error( + curl_formadd(&post, &last, + CURLFORM_COPYNAME, "description", + CURLFORM_PTRCONTENTS, form_desc.data(), + CURLFORM_CONTENTSLENGTH, form_desc.size(), + CURLFORM_END) + ); + + + if (res < 0) + throw std::runtime_error("curl_formadd() error! - description"); + } + + for (int i = 0; i < 5; i++) + { + if (pParam->searchKeys[i][0]) + { + form_searchKey[i] = StringHelpers::ToUtf8((const uint16be*)pParam->searchKeys[i], 151); + res = olv_curlformcode_to_error( + curl_formadd(&post, &last, + CURLFORM_COPYNAME, "search_key", + CURLFORM_PTRCONTENTS, form_searchKey[i].data(), + CURLFORM_CONTENTSLENGTH, form_searchKey[i].size(), + CURLFORM_END) + ); + + if (res < 0) + throw std::runtime_error("curl_formadd() error! - search_key"); + } + } + + if (!pParam->appData.IsNull()) + { + encodedAppData = NCrypto::base64Encode(pParam->appData.GetPtr(), pParam->appDataLen); + if (encodedAppData.size() < pParam->appDataLen) + res = OLV_RESULT_FATAL(101); + else + { + res = olv_curlformcode_to_error( + curl_formadd(&post, &last, + CURLFORM_COPYNAME, "app_data", + CURLFORM_PTRCONTENTS, encodedAppData.data(), + CURLFORM_CONTENTSLENGTH, encodedAppData.size(), + CURLFORM_END) + ); + + if (res < 0) + throw std::runtime_error("curl_formadd() error! - app_data"); + } + } + } + catch (const std::runtime_error& error) + { + cemuLog_log(LogType::Force, "Error in multipart curl -> {}", error.what()); + curl_formfree(post); + + if (encodedIcon) + delete[] encodedIcon; + + return res; + } + + curl_easy_setopt(req.getCURL(), CURLOPT_HTTPPOST, post); + req.setUseMultipartFormData(true); + + bool reqResult = req.submitRequest(true); + long httpCode = 0; + curl_easy_getinfo(req.getCURL(), CURLINFO_RESPONSE_CODE, &httpCode); + + if (encodedIcon) + delete[] encodedIcon; + + if (!reqResult) + { + cemuLog_log(LogType::Force, "Failed request: {} ({})", reqUrl, httpCode); + if (!(httpCode >= 400)) + return OLV_RESULT_FAILED_REQUEST; + } + + pugi::xml_document doc; + if (!doc.load_buffer(req.getReceivedData().data(), req.getReceivedData().size())) + { + cemuLog_log(LogType::Force, fmt::format("Invalid XML in community upload response")); + return OLV_RESULT_INVALID_XML; + } + + sint32 responseError = CheckOliveResponse(doc); + if (responseError < 0) + return responseError; + + if (httpCode != 200) + return OLV_RESULT_STATUS(httpCode + 4000); + + if (pOutData) + { + + std::string_view app_data = doc.select_single_node("//app_data").node().child_value(); + std::string_view community_id = doc.select_single_node("//community_id").node().child_value(); + std::string_view name = doc.select_single_node("//name").node().child_value(); + std::string_view description = doc.select_single_node("//description").node().child_value(); + std::string_view pid = doc.select_single_node("//pid").node().child_value(); + std::string_view icon = doc.select_single_node("//icon").node().child_value(); + + if (app_data.size() != 0) + { + auto app_data_bin = NCrypto::base64Decode(app_data); + if (app_data_bin.size() != 0) { + memcpy(pOutData->appData, app_data_bin.data(), std::min(size_t(0x400), app_data_bin.size())); + pOutData->flags |= UploadedCommunityData::FLAG_HAS_APP_DATA; + pOutData->appDataLen = app_data_bin.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + sint64 community_id_val = StringHelpers::ToInt64(community_id, -1); + if (community_id_val == -1) + return OLV_RESULT_INVALID_INTEGER_FIELD; + + pOutData->communityId = community_id_val; + + if (name.size() != 0) + { + auto name_utf16 = StringHelpers::FromUtf8(name).substr(0, 128); + if (name_utf16.size() != 0) + { + for (int i = 0; i < name_utf16.size(); i++) + pOutData->titleText[i] = name_utf16.at(i); + + pOutData->flags |= UploadedCommunityData::FLAG_HAS_TITLE_TEXT; + pOutData->titleTextMaxLen = name_utf16.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + if (description.size() != 0) + { + auto description_utf16 = StringHelpers::FromUtf8(description).substr(0, 256); + if (description_utf16.size() != 0) + { + for (int i = 0; i < description_utf16.size(); i++) + pOutData->description[i] = description_utf16.at(i); + + pOutData->flags |= UploadedCommunityData::FLAG_HAS_DESC_TEXT; + pOutData->descriptionMaxLen = description_utf16.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + sint64 pid_val = StringHelpers::ToInt64(pid, -1); + if (pid_val == -1) + return OLV_RESULT_INVALID_INTEGER_FIELD; + + pOutData->pid = pid_val; + + if (icon.size() != 0) + { + auto icon_bin = NCrypto::base64Decode(icon); + if (icon_bin.size() != 0) + { + memcpy(pOutData->iconData, icon_bin.data(), std::min(size_t(0x1002c), icon_bin.size())); + pOutData->flags |= UploadedCommunityData::FLAG_HAS_ICON_DATA; + pOutData->iconDataSize = icon_bin.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + } + + return OLV_RESULT_SUCCESS; + } + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.h b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.h new file mode 100644 index 00000000..4944e314 --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.h @@ -0,0 +1,432 @@ +#pragma once + +#include "Cemu/ncrypto/ncrypto.h" +#include "config/ActiveSettings.h" + +#include "Cafe/OS/libs/nn_olv/nn_olv_Common.h" + +namespace nn +{ + namespace olv + { + class UploadedCommunityData + { + public: + static const inline uint32 FLAG_HAS_TITLE_TEXT = (1 << 0); + static const inline uint32 FLAG_HAS_DESC_TEXT = (1 << 1); + static const inline uint32 FLAG_HAS_APP_DATA = (1 << 2); + static const inline uint32 FLAG_HAS_ICON_DATA = (1 << 3); + + UploadedCommunityData() + { + this->titleTextMaxLen = 0; + this->appDataLen = 0; + this->descriptionMaxLen = 0; + this->pid = 0; + this->communityId = 0; + this->flags = 0; + this->iconDataSize = 0; + } + static UploadedCommunityData* __ctor(UploadedCommunityData* _this) + { + if (!_this) + { + assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN + return nullptr; + } + else + return new (_this) UploadedCommunityData(); + } + + static UploadedCommunityData* Clean(UploadedCommunityData* data) + { + data->appDataLen = 0; + data->pid = 0; + data->titleText[0] = 0; + data->description[0] = 0; + data->appData[0] = 0; + data->titleTextMaxLen = 0; + data->iconData[0] = 0; + data->descriptionMaxLen = 0; + data->communityId = 0; + data->flags = 0; + data->iconDataSize = 0; + return data; + } + + bool TestFlags(uint32 flags) const + { + return (this->flags & flags) != 0; + } + static bool __TestFlags(UploadedCommunityData* _this, uint32 flags) + { + return _this->TestFlags(flags); + } + + uint32 GetCommunityId() const + { + return this->communityId; + } + static uint32 __GetCommunityId(UploadedCommunityData* _this) + { + return _this->GetCommunityId(); + } + + sint32 GetCommunityCode(char* pBuffer, uint32 bufferSize) const + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (bufferSize <= 12) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + uint32 len = 0; + if (FormatCommunityCode(pBuffer, &len, this->communityId)) + return OLV_RESULT_SUCCESS; + + return OLV_RESULT_INVALID_PARAMETER; + } + static sint32 __GetCommunityCode(UploadedCommunityData* _this, char* pBuffer, uint32 bufferSize) + { + return _this->GetCommunityCode(pBuffer, bufferSize); + } + + uint32 GetOwnerPid() const + { + return this->pid; + } + static uint32 __GetOwnerPid(UploadedCommunityData* _this) + { + return _this->GetOwnerPid(); + } + + sint32 GetTitleText(char16_t* pBuffer, uint32 numChars) + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (numChars) + { + if (!this->TestFlags(FLAG_HAS_TITLE_TEXT)) + return OLV_RESULT_MISSING_DATA; + + memset(pBuffer, 0, 2 * numChars); + uint32 readSize = this->titleTextMaxLen; + if (numChars < readSize) + readSize = numChars; + + olv_wstrncpy(pBuffer, this->titleText, readSize); + return OLV_RESULT_SUCCESS; + } + + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + static sint32 __GetTitleText(UploadedCommunityData* _this, char16_t* pBuffer, uint32 numChars) + { + return _this->GetTitleText(pBuffer, numChars); + } + + sint32 GetDescriptionText(char16_t* pBuffer, uint32 numChars) + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (numChars) + { + if (!this->TestFlags(FLAG_HAS_DESC_TEXT)) + return OLV_RESULT_MISSING_DATA; + + memset(pBuffer, 0, 2 * numChars); + olv_wstrncpy(pBuffer, this->description, numChars); + return OLV_RESULT_SUCCESS; + } + + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + static sint32 __GetDescriptionText(UploadedCommunityData* _this, char16_t* pBuffer, uint32 numChars) + { + return _this->GetDescriptionText(pBuffer, numChars); + } + + sint32 GetAppData(uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + uint32 appDataSize = bufferSize; + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (bufferSize) + { + if (!this->TestFlags(FLAG_HAS_APP_DATA)) + return OLV_RESULT_MISSING_DATA; + + if (this->appDataLen < appDataSize) + appDataSize = this->appDataLen; + + memcpy(pBuffer, this->appData, appDataSize); + if (pOutSize) + *pOutSize = appDataSize; + + return OLV_RESULT_SUCCESS; + } + + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + static sint32 __GetAppData(UploadedCommunityData* _this, uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + return _this->GetAppData(pBuffer, pOutSize, bufferSize); + } + + uint32 GetAppDataSize() const + { + if (this->TestFlags(FLAG_HAS_APP_DATA)) + return this->appDataLen; + + return 0; + } + static uint32 __GetAppDataSize(UploadedCommunityData* _this) + { + return _this->GetAppDataSize(); + } + + sint32 GetIconData(uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (bufferSize < sizeof(this->iconData)) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + if (!this->TestFlags(FLAG_HAS_ICON_DATA)) + return OLV_RESULT_MISSING_DATA; + + sint32 decodeRes = DecodeTGA(this->iconData, this->iconDataSize, pBuffer, bufferSize, TGACheckType::CHECK_COMMUNITY_ICON); + if (decodeRes >= 0) + { + if (pOutSize) + *pOutSize = (uint32)decodeRes; + + return OLV_RESULT_SUCCESS; + } + + if (pOutSize) + *pOutSize = 0; + + if (decodeRes == -1) + cemuLog_log(LogType::Force, "OLIVE - icon uncompress failed.\n"); + else if (decodeRes == -2) + cemuLog_log(LogType::Force, "OLIVE - icon decode error. NOT TGA.\n"); + + return OLV_RESULT_INVALID_TEXT_FIELD; + } + static sint32 __GetIconData(UploadedCommunityData* _this, uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + return _this->GetIconData(pBuffer, pOutSize, bufferSize); + } + + public: + uint32be flags; + uint32be communityId; + uint32be pid; + char16_t titleText[128]; + uint32be titleTextMaxLen; + char16_t description[256]; + uint32be descriptionMaxLen; + uint8 appData[1024]; + uint32be appDataLen; + uint8 iconData[65580]; + uint32be iconDataSize; + uint8 unk[6328]; + }; + static_assert(sizeof(nn::olv::UploadedCommunityData) == 0x12000, "sizeof(nn::olv::UploadedCommunityData) != 0x12000"); + + + class UploadCommunityDataParam + { + public: + static const inline uint32 FLAG_DELETION = (1 << 0); + + UploadCommunityDataParam() + { + this->appDataLen = 0; + this->communityId = 0; + this->titleId = 0; + this->iconData = MEMPTR(nullptr); + this->appData = MEMPTR(nullptr); + this->iconDataLen = 0; + this->flags = 0; + memset(this->titleText, 0, sizeof(this->titleText)); + memset(this->description, 0, sizeof(this->description)); + int v2 = 0; + do + memset(this->searchKeys[v2++], 0, sizeof(this->searchKeys[v2++])); + while (v2 < 5); + } + static UploadCommunityDataParam* __ctor(UploadCommunityDataParam* _this) + { + if (!_this) + { + assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN + return nullptr; + } + else + return new (_this) UploadCommunityDataParam(); + } + + sint32 SetFlags(uint32 flags) + { + this->flags = flags; + return OLV_RESULT_SUCCESS; + } + static sint32 __SetFlags(UploadCommunityDataParam* _this, uint32 flags) + { + return _this->SetFlags(flags); + } + + sint32 SetCommunityId(uint32 communityId) + { + if (communityId == -1) + return OLV_RESULT_INVALID_PARAMETER; + + this->communityId = communityId; + return OLV_RESULT_SUCCESS; + } + static sint32 __SetCommunityId(UploadCommunityDataParam* _this, uint32 communityId) + { + return _this->SetCommunityId(communityId); + } + + sint32 SetAppData(MEMPTR pBuffer, uint32 bufferSize) + { + if (!pBuffer.IsNull()) + { + if (bufferSize - 1 >= 0x400) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + this->appData = pBuffer; + this->appDataLen = bufferSize; + } + else + { + this->appData = MEMPTR(nullptr); + this->appDataLen = 0; + } + return OLV_RESULT_SUCCESS; + } + static sint32 __SetAppData(UploadCommunityDataParam* _this, MEMPTR pBuffer, uint32 bufferSize) + { + return _this->SetAppData(pBuffer, bufferSize); + } + + sint32 SetTitleText(char16_t const* pText) + { + if (pText) + return olv_copy_wstr(this->titleText, pText, 127, 128); + + memset(this->titleText, 0, sizeof(this->titleText)); + return OLV_RESULT_SUCCESS; + } + static sint32 __SetTitleText(UploadCommunityDataParam* _this, char16_t const* pText) + { + return _this->SetTitleText(pText); + } + + sint32 SetDescriptionText(char16_t const* pText) + { + if (pText) + return olv_copy_wstr(this->description, pText, 255, 256); + + memset(this->description, 0, sizeof(this->description)); + return OLV_RESULT_SUCCESS; + } + static sint32 __SetDescriptionText(UploadCommunityDataParam* _this, char16_t const* pText) + { + return _this->SetDescriptionText(pText); + } + + sint32 SetIconData(MEMPTR pBuffer, uint32 bufferSize) + { + if (!pBuffer.IsNull()) + { + if (bufferSize) + { + if (bufferSize - 0x10012 < 0x1B) + { + if (CheckTGA(pBuffer.GetPtr(), bufferSize, TGACheckType::CHECK_COMMUNITY_ICON)) + { + this->iconData = pBuffer; + this->iconDataLen = bufferSize; + return OLV_RESULT_SUCCESS; + } + else + { + cemuLog_log(LogType::Force, "OLIVE - SetIconData: TGA Check Failed.\n"); + return OLV_RESULT_INVALID_DATA; + } + } + else + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + else + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + else + { + this->iconData = MEMPTR(nullptr); + this->iconDataLen = 0; + return OLV_RESULT_SUCCESS; + } + } + static sint32 __SetIconData(UploadCommunityDataParam* _this, MEMPTR pBuffer, uint32 bufferSize) + { + return _this->SetIconData(pBuffer, bufferSize); + } + + public: + uint32be flags; + sint32be ___padding_0c; + uint64be titleId; + uint32be communityId; + char16_t titleText[128]; + char16_t description[256]; + char16_t searchKeys[5][152]; + MEMPTR appData; + uint32be appDataLen; + MEMPTR iconData; + uint32be iconDataLen; + char unk3[1772]; + }; + static_assert(sizeof(nn::olv::UploadCommunityDataParam) == 0x1000, "sizeof(nn::olv::UploadCommunityDataParam) != 0x1000"); + + sint32 UploadCommunityData(UploadCommunityDataParam const* pParam); + sint32 UploadCommunityData(UploadedCommunityData* pOutData, UploadCommunityDataParam const* pParam); + + static void loadOliveUploadCommunityTypes() + { + cafeExportRegisterFunc(UploadedCommunityData::__ctor, "nn_olv", "__ct__Q3_2nn3olv21UploadedCommunityDataFv", LogType::None); + cafeExportRegisterFunc(UploadedCommunityData::__TestFlags, "nn_olv", "TestFlags__Q3_2nn3olv21UploadedCommunityDataCFUi", LogType::None); + cafeExportRegisterFunc(UploadedCommunityData::__GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv21UploadedCommunityDataCFv", LogType::None); + cafeExportRegisterFunc(UploadedCommunityData::__GetCommunityCode, "nn_olv", "GetCommunityCode__Q3_2nn3olv21UploadedCommunityDataCFPcUi", LogType::None); + cafeExportRegisterFunc(UploadedCommunityData::__GetOwnerPid, "nn_olv", "GetOwnerPid__Q3_2nn3olv21UploadedCommunityDataCFv", LogType::None); + cafeExportRegisterFunc(UploadedCommunityData::__GetTitleText, "nn_olv", "GetTitleText__Q3_2nn3olv21UploadedCommunityDataCFPwUi", LogType::None); + cafeExportRegisterFunc(UploadedCommunityData::__GetDescriptionText, "nn_olv", "GetDescriptionText__Q3_2nn3olv21UploadedCommunityDataCFPwUi", LogType::None); + cafeExportRegisterFunc(UploadedCommunityData::__GetAppData, "nn_olv", "GetAppData__Q3_2nn3olv21UploadedCommunityDataCFPUcPUiUi", LogType::None); + cafeExportRegisterFunc(UploadedCommunityData::__GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv21UploadedCommunityDataCFv", LogType::None); + cafeExportRegisterFunc(UploadedCommunityData::__GetIconData, "nn_olv", "GetIconData__Q3_2nn3olv21UploadedCommunityDataCFPUcPUiUi", LogType::None); + + cafeExportRegisterFunc(UploadCommunityDataParam::__ctor, "nn_olv", "__ct__Q3_2nn3olv24UploadCommunityDataParamFv", LogType::None); + cafeExportRegisterFunc(UploadCommunityDataParam::__SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv24UploadCommunityDataParamFUi", LogType::None); + cafeExportRegisterFunc(UploadCommunityDataParam::__SetCommunityId, "nn_olv", "SetCommunityId__Q3_2nn3olv24UploadCommunityDataParamFUi", LogType::None); + cafeExportRegisterFunc(UploadCommunityDataParam::__SetAppData, "nn_olv", "SetAppData__Q3_2nn3olv24UploadCommunityDataParamFPCUcUi", LogType::None); + cafeExportRegisterFunc(UploadCommunityDataParam::__SetTitleText, "nn_olv", "SetTitleText__Q3_2nn3olv24UploadCommunityDataParamFPCw", LogType::None); + cafeExportRegisterFunc(UploadCommunityDataParam::__SetDescriptionText, "nn_olv", "SetDescriptionText__Q3_2nn3olv24UploadCommunityDataParamFPCw", LogType::None); + cafeExportRegisterFunc(UploadCommunityDataParam::__SetIconData, "nn_olv", "SetIconData__Q3_2nn3olv24UploadCommunityDataParamFPCUcUi", LogType::None); + + cafeExportRegisterFunc((sint32(*)(UploadCommunityDataParam const*))UploadCommunityData, + "nn_olv", "UploadCommunityData__Q2_2nn3olvFPCQ3_2nn3olv24UploadCommunityDataParam", LogType::None); + + cafeExportRegisterFunc((sint32(*)(UploadedCommunityData *, UploadCommunityDataParam const*))UploadCommunityData, + "nn_olv", "UploadCommunityData__Q2_2nn3olvFPQ3_2nn3olv21UploadedCommunityDataPCQ3_2nn3olv24UploadCommunityDataParam", LogType::None); + } + + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp new file mode 100644 index 00000000..7d9220fc --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp @@ -0,0 +1,169 @@ +#include "nn_olv_UploadFavoriteTypes.h" +#include + +namespace nn +{ + namespace olv + { + + sint32 UploadFavoriteToCommunityData_AsyncRequestImpl(CurlRequestHelper& req, const char* reqUrl, + UploadedFavoriteToCommunityData* pOutData, const UploadFavoriteToCommunityDataParam* pParam + ); + + sint32 UploadFavoriteToCommunityData_AsyncRequest(CurlRequestHelper& req, const char* reqUrl, coreinit::OSEvent* requestDoneEvent, + UploadedFavoriteToCommunityData* pOutData, const UploadFavoriteToCommunityDataParam* pParam + ) + { + sint32 res = UploadFavoriteToCommunityData_AsyncRequestImpl(req, reqUrl, pOutData, pParam); + coreinit::OSSignalEvent(requestDoneEvent); + return res; + } + + sint32 UploadFavoriteToCommunityData(UploadedFavoriteToCommunityData* pOutData, const UploadFavoriteToCommunityDataParam* pParam) + { + if (!nn::olv::g_IsInitialized) + return OLV_RESULT_NOT_INITIALIZED; + + if (!nn::olv::g_IsOnlineMode) + return OLV_RESULT_OFFLINE_MODE_REQUEST; + + if (!pParam) + return OLV_RESULT_INVALID_PTR; + + if (pOutData) + UploadedFavoriteToCommunityData::Clean(pOutData); + + char requestUrl[512]; + if (pParam->flags & UploadFavoriteToCommunityDataParam::FLAG_DELETION) + snprintf(requestUrl, sizeof(requestUrl), "%s/v1/communities/%lu.unfavorite", g_DiscoveryResults.apiEndpoint, pParam->communityId.value()); + else + snprintf(requestUrl, sizeof(requestUrl), "%s/v1/communities/%lu.favorite", g_DiscoveryResults.apiEndpoint, pParam->communityId.value()); + + CurlRequestHelper req; + req.initate(requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); + InitializeOliveRequest(req); + + StackAllocator requestDoneEvent; + coreinit::OSInitEvent(requestDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + std::future requestRes = std::async(std::launch::async, UploadFavoriteToCommunityData_AsyncRequest, std::ref(req), requestUrl, requestDoneEvent.GetPointer(), pOutData, pParam); + coreinit::OSWaitEvent(requestDoneEvent); + + return requestRes.get(); + } + + sint32 UploadFavoriteToCommunityData(const UploadFavoriteToCommunityDataParam* pParam) + { + return UploadFavoriteToCommunityData(nullptr, pParam); + } + + sint32 UploadFavoriteToCommunityData_AsyncRequestImpl(CurlRequestHelper& req, const char* reqUrl, + UploadedFavoriteToCommunityData* pOutData, const UploadFavoriteToCommunityDataParam* pParam + ) + { + bool reqResult = req.submitRequest(true); + long httpCode = 0; + curl_easy_getinfo(req.getCURL(), CURLINFO_RESPONSE_CODE, &httpCode); + + if (!reqResult) + { + cemuLog_log(LogType::Force, "Failed request: {} ({})", reqUrl, httpCode); + if (!(httpCode >= 400)) + return OLV_RESULT_FAILED_REQUEST; + } + + pugi::xml_document doc; + if (!doc.load_buffer(req.getReceivedData().data(), req.getReceivedData().size())) + { + cemuLog_log(LogType::Force, fmt::format("Invalid XML in community favorite upload response")); + return OLV_RESULT_INVALID_XML; + } + + sint32 responseError = CheckOliveResponse(doc); + if (responseError < 0) + return responseError; + + if (httpCode != 200) + return OLV_RESULT_STATUS(httpCode + 4000); + + if (pOutData) + { + std::string_view app_data = doc.select_single_node("//app_data").node().child_value(); + std::string_view community_id = doc.select_single_node("//community_id").node().child_value(); + std::string_view name = doc.select_single_node("//name").node().child_value(); + std::string_view description = doc.select_single_node("//description").node().child_value(); + std::string_view pid = doc.select_single_node("//pid").node().child_value(); + std::string_view icon = doc.select_single_node("//icon").node().child_value(); + + if (app_data.size() != 0) + { + auto app_data_bin = NCrypto::base64Decode(app_data); + if (app_data_bin.size() != 0) + { + memcpy(pOutData->appData, app_data_bin.data(), std::min(size_t(0x400), app_data_bin.size())); + pOutData->flags |= UploadedFavoriteToCommunityData::FLAG_HAS_APP_DATA; + pOutData->appDataLen = app_data_bin.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + sint64 community_id_val = StringHelpers::ToInt64(community_id, -1); + if (community_id_val == -1) + return OLV_RESULT_INVALID_INTEGER_FIELD; + + pOutData->communityId = community_id_val; + + if (name.size() != 0) + { + auto name_utf16 = StringHelpers::FromUtf8(name).substr(0, 128); + if (name_utf16.size() != 0) + { + for (int i = 0; i < name_utf16.size(); i++) + pOutData->titleText[i] = name_utf16.at(i); + + pOutData->flags |= UploadedFavoriteToCommunityData::FLAG_HAS_TITLE_TEXT; + pOutData->titleTextMaxLen = name_utf16.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + if (description.size() != 0) + { + auto description_utf16 = StringHelpers::FromUtf8(description).substr(0, 256); + if (description_utf16.size() != 0) + { + for (int i = 0; i < description_utf16.size(); i++) + pOutData->description[i] = description_utf16.at(i); + + pOutData->flags |= UploadedFavoriteToCommunityData::FLAG_HAS_DESC_TEXT; + pOutData->descriptionMaxLen = description_utf16.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + + sint64 pid_val = StringHelpers::ToInt64(pid, -1); + if (pid_val == -1) + return OLV_RESULT_INVALID_INTEGER_FIELD; + + pOutData->pid = pid_val; + + if (icon.size() != 0) + { + auto icon_bin = NCrypto::base64Decode(icon); + if (icon_bin.size() != 0) + { + memcpy(pOutData->iconData, icon_bin.data(), std::min(size_t(0x1002c), icon_bin.size())); + pOutData->flags |= UploadedFavoriteToCommunityData::FLAG_HAS_ICON_DATA; + pOutData->iconDataSize = icon_bin.size(); + } + else + return OLV_RESULT_INVALID_TEXT_FIELD; + } + } + + return OLV_RESULT_SUCCESS; + } + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h new file mode 100644 index 00000000..05ef1dd4 --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h @@ -0,0 +1,342 @@ +#pragma once + +#include "Cemu/ncrypto/ncrypto.h" +#include "config/ActiveSettings.h" + +#include "Cafe/OS/libs/nn_olv/nn_olv_Common.h" + +namespace nn +{ + namespace olv + { + class UploadedFavoriteToCommunityData + { + public: + static const inline uint32 FLAG_HAS_TITLE_TEXT = (1 << 0); + static const inline uint32 FLAG_HAS_DESC_TEXT = (1 << 1); + static const inline uint32 FLAG_HAS_APP_DATA = (1 << 2); + static const inline uint32 FLAG_HAS_ICON_DATA = (1 << 3); + + UploadedFavoriteToCommunityData() + { + this->titleTextMaxLen = 0; + this->appDataLen = 0; + this->descriptionMaxLen = 0; + this->pid = 0; + this->communityId = 0; + this->flags = 0; + this->iconDataSize = 0; + } + static UploadedFavoriteToCommunityData* __ctor(UploadedFavoriteToCommunityData* _this) + { + if (!_this) + { + assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN + return nullptr; + } + else + return new (_this) UploadedFavoriteToCommunityData(); + } + + static UploadedFavoriteToCommunityData* Clean(UploadedFavoriteToCommunityData* data) + { + data->appDataLen = 0; + data->pid = 0; + data->titleText[0] = 0; + data->description[0] = 0; + data->appData[0] = 0; + data->titleTextMaxLen = 0; + data->iconData[0] = 0; + data->descriptionMaxLen = 0; + data->communityId = 0; + data->flags = 0; + data->iconDataSize = 0; + return data; + } + + bool TestFlags(uint32 flags) const + { + return (this->flags & flags) != 0; + } + static bool __TestFlags(UploadedFavoriteToCommunityData* _this, uint32 flags) + { + return _this->TestFlags(flags); + } + + uint32 GetCommunityId() const + { + return this->communityId; + } + static uint32 __GetCommunityId(UploadedFavoriteToCommunityData* _this) + { + return _this->GetCommunityId(); + } + + sint32 GetCommunityCode(char* pBuffer, uint32 bufferSize) const + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (bufferSize <= 12) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + uint32 len = 0; + if (FormatCommunityCode(pBuffer, &len, this->communityId)) + return OLV_RESULT_SUCCESS; + + return OLV_RESULT_INVALID_PARAMETER; + } + static sint32 __GetCommunityCode(UploadedFavoriteToCommunityData* _this, char* pBuffer, uint32 bufferSize) + { + return _this->GetCommunityCode(pBuffer, bufferSize); + } + + uint32 GetOwnerPid() const + { + return this->pid; + } + static uint32 __GetOwnerPid(UploadedFavoriteToCommunityData* _this) + { + return _this->GetOwnerPid(); + } + + sint32 GetTitleText(char16_t* pBuffer, uint32 numChars) + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (numChars) + { + if (!this->TestFlags(FLAG_HAS_TITLE_TEXT)) + return OLV_RESULT_MISSING_DATA; + + memset(pBuffer, 0, 2 * numChars); + uint32 readSize = this->titleTextMaxLen; + if (numChars < readSize) + readSize = numChars; + + olv_wstrncpy(pBuffer, this->titleText, readSize); + return OLV_RESULT_SUCCESS; + } + + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + static sint32 __GetTitleText(UploadedFavoriteToCommunityData* _this, char16_t* pBuffer, uint32 numChars) + { + return _this->GetTitleText(pBuffer, numChars); + } + + sint32 GetDescriptionText(char16_t* pBuffer, uint32 numChars) + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (numChars) + { + if (!this->TestFlags(FLAG_HAS_DESC_TEXT)) + return OLV_RESULT_MISSING_DATA; + + memset(pBuffer, 0, 2 * numChars); + olv_wstrncpy(pBuffer, this->description, numChars); + return OLV_RESULT_SUCCESS; + } + + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + static sint32 __GetDescriptionText(UploadedFavoriteToCommunityData* _this, char16_t* pBuffer, uint32 numChars) + { + return _this->GetDescriptionText(pBuffer, numChars); + } + + sint32 GetAppData(uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + uint32 appDataSize = bufferSize; + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (bufferSize) + { + if (!this->TestFlags(FLAG_HAS_APP_DATA)) + return OLV_RESULT_MISSING_DATA; + + if (this->appDataLen < appDataSize) + appDataSize = this->appDataLen; + + memcpy(pBuffer, this->appData, appDataSize); + if (pOutSize) + *pOutSize = appDataSize; + + return OLV_RESULT_SUCCESS; + } + + return OLV_RESULT_NOT_ENOUGH_SIZE; + } + static sint32 __GetAppData(UploadedFavoriteToCommunityData* _this, uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + return _this->GetAppData(pBuffer, pOutSize, bufferSize); + } + + uint32 GetAppDataSize() const + { + if (this->TestFlags(FLAG_HAS_APP_DATA)) + return this->appDataLen; + + return 0; + } + static uint32 __GetAppDataSize(UploadedFavoriteToCommunityData* _this) + { + return _this->GetAppDataSize(); + } + + sint32 GetIconData(uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + if (!pBuffer) + return OLV_RESULT_INVALID_PTR; + + if (bufferSize < sizeof(this->iconData)) + return OLV_RESULT_NOT_ENOUGH_SIZE; + + if (!this->TestFlags(FLAG_HAS_ICON_DATA)) + return OLV_RESULT_MISSING_DATA; + + sint32 decodeRes = DecodeTGA(this->iconData, this->iconDataSize, pBuffer, bufferSize, TGACheckType::CHECK_COMMUNITY_ICON); + if (decodeRes >= 0) + { + if (pOutSize) + *pOutSize = (uint32)decodeRes; + + return OLV_RESULT_SUCCESS; + } + + if (pOutSize) + *pOutSize = 0; + + if (decodeRes == -1) + cemuLog_log(LogType::Force, "OLIVE - icon uncompress failed.\n"); + else if (decodeRes == -2) + cemuLog_log(LogType::Force, "OLIVE - icon decode error. NOT TGA.\n"); + + return OLV_RESULT_INVALID_TEXT_FIELD; + } + static sint32 __GetIconData(UploadedFavoriteToCommunityData* _this, uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) + { + return _this->GetIconData(pBuffer, pOutSize, bufferSize); + } + + public: + uint32be flags; + uint32be communityId; + uint32be pid; + char16_t titleText[128]; + uint32be titleTextMaxLen; + char16_t description[256]; + uint32be descriptionMaxLen; + uint8 appData[1024]; + uint32be appDataLen; + uint8 iconData[65580]; + uint32be iconDataSize; + uint8 unk[6328]; + }; + static_assert(sizeof(nn::olv::UploadedFavoriteToCommunityData) == 0x12000, "sizeof(nn::olv::UploadedFavoriteToCommunityData) != 0x12000"); + + class UploadFavoriteToCommunityDataParam + { + + public: + static const inline uint32 FLAG_DELETION = (1 << 0); + + UploadFavoriteToCommunityDataParam() + { + this->communityId = 0; + this->flags = 0; + } + static UploadFavoriteToCommunityDataParam* __ctor(UploadFavoriteToCommunityDataParam* _this) + { + if (!_this) + { + assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN + return nullptr; + } + else + return new (_this) UploadFavoriteToCommunityDataParam(); + } + + sint32 SetFlags(uint32 flags) + { + this->flags = flags; + return OLV_RESULT_SUCCESS; + } + static sint32 __SetFlags(UploadFavoriteToCommunityDataParam* _this, uint32 flags) + { + return _this->SetFlags(flags); + } + + sint32 SetCommunityCode(const char* pBuffer) + { + if (strnlen(pBuffer, 13) != 12) + return OLV_RESULT_INVALID_TEXT_FIELD; + + uint32_t id; + if (GetCommunityIdFromCode(&id, pBuffer)) + { + this->communityId = id; + return OLV_RESULT_SUCCESS; + } + + return OLV_RESULT_STATUS(1901); + } + static sint32 __SetCommunityCode(UploadFavoriteToCommunityDataParam* _this, char* pBuffer) + { + return _this->SetCommunityCode(pBuffer); + } + + sint32 SetCommunityId(uint32 communityId) + { + if (communityId == -1) + return OLV_RESULT_INVALID_PARAMETER; + + this->communityId = communityId; + return OLV_RESULT_SUCCESS; + } + static sint32 __SetCommunityId(UploadFavoriteToCommunityDataParam* _this, uint32 communityId) + { + return _this->SetCommunityId(communityId); + } + + public: + uint32be flags; + uint32be communityId; + uint8 unk[54]; // Unused + }; + static_assert(sizeof(nn::olv::UploadFavoriteToCommunityDataParam) == 64, "sizeof(nn::olv::UploadFavoriteToCommunityDataParam) != 64"); + + sint32 UploadFavoriteToCommunityData(const UploadFavoriteToCommunityDataParam* pParam); + sint32 UploadFavoriteToCommunityData(UploadedFavoriteToCommunityData* pOutData, const UploadFavoriteToCommunityDataParam* pParam); + + static void loadOliveUploadFavoriteTypes() + { + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__ctor, "nn_olv", "__ct__Q3_2nn3olv31UploadedFavoriteToCommunityDataFv", LogType::None); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__TestFlags, "nn_olv", "TestFlags__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFUi", LogType::None); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFv", LogType::None); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetCommunityCode, "nn_olv", "GetCommunityCode__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPcUi", LogType::None); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetOwnerPid, "nn_olv", "GetOwnerPid__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFv", LogType::None); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetTitleText, "nn_olv", "GetTitleText__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPwUi", LogType::None); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetDescriptionText, "nn_olv", "GetDescriptionText__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPwUi", LogType::None); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetAppData, "nn_olv", "GetAppData__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPUcPUiUi", LogType::None); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFv", LogType::None); + cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetIconData, "nn_olv", "GetIconData__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPUcPUiUi", LogType::None); + + cafeExportRegisterFunc(UploadFavoriteToCommunityDataParam::__ctor, "nn_olv", "__ct__Q3_2nn3olv34UploadFavoriteToCommunityDataParamFv", LogType::None); + cafeExportRegisterFunc(UploadFavoriteToCommunityDataParam::__SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv34UploadFavoriteToCommunityDataParamFUi", LogType::None); + cafeExportRegisterFunc(UploadFavoriteToCommunityDataParam::__SetCommunityCode, "nn_olv", "SetCommunityCode__Q3_2nn3olv34UploadFavoriteToCommunityDataParamFPCc", LogType::None); + cafeExportRegisterFunc(UploadFavoriteToCommunityDataParam::__SetCommunityId, "nn_olv", "SetCommunityId__Q3_2nn3olv34UploadFavoriteToCommunityDataParamFUi", LogType::None); + + cafeExportRegisterFunc((sint32(*)(const UploadFavoriteToCommunityDataParam*))UploadFavoriteToCommunityData, + "nn_olv", "UploadFavoriteToCommunityData__Q2_2nn3olvFPCQ3_2nn3olv34UploadFavoriteToCommunityDataParam", LogType::None); + + cafeExportRegisterFunc((sint32(*)(UploadedFavoriteToCommunityData*, const UploadFavoriteToCommunityDataParam*))UploadFavoriteToCommunityData, + "nn_olv", "UploadFavoriteToCommunityData__Q2_2nn3olvFPQ3_2nn3olv31UploadedFavoriteToCommunityDataPCQ3_2nn3olv34UploadFavoriteToCommunityDataParam", LogType::None); + } + + } +} \ No newline at end of file diff --git a/src/Cafe/TitleList/ParsedMetaXml.h b/src/Cafe/TitleList/ParsedMetaXml.h index 2cb2e7a8..7537d603 100644 --- a/src/Cafe/TitleList/ParsedMetaXml.h +++ b/src/Cafe/TitleList/ParsedMetaXml.h @@ -16,6 +16,8 @@ struct ParsedMetaXml std::array m_short_name; std::array m_publisher; + uint32 m_olv_accesskey; + std::string GetShortName(CafeConsoleLanguage languageId) const { return m_short_name[(size_t)languageId].empty() ? m_short_name[(size_t)CafeConsoleLanguage::EN] : m_short_name[(size_t)languageId]; @@ -51,6 +53,11 @@ struct ParsedMetaXml return m_company_code; } + uint32 GetOlvAccesskey() const + { + return m_olv_accesskey; + } + static ParsedMetaXml* Parse(uint8* xmlData, size_t xmlSize) { if (xmlSize == 0) @@ -98,6 +105,8 @@ struct ParsedMetaXml if (index != -1) parsedMetaXml->m_publisher[index] = child.text().as_string(); } + else if (boost::starts_with(name, L"olv_accesskey")) + parsedMetaXml->m_olv_accesskey = child.text().as_uint(-1); } if (parsedMetaXml->m_title_id == 0) { diff --git a/src/Cafe/TitleList/TitleInfo.cpp b/src/Cafe/TitleList/TitleInfo.cpp index 36814ab8..6ffa15c0 100644 --- a/src/Cafe/TitleList/TitleInfo.cpp +++ b/src/Cafe/TitleList/TitleInfo.cpp @@ -615,6 +615,16 @@ CafeConsoleRegion TitleInfo::GetMetaRegion() const return CafeConsoleRegion::JPN; } +uint32 TitleInfo::GetOlvAccesskey() const +{ + cemu_assert_debug(m_isValid); + if (m_parsedMetaXml) + return m_parsedMetaXml->GetOlvAccesskey(); + + cemu_assert_suspicious(); + return -1; +} + std::string TitleInfo::GetArgStr() const { cemu_assert_debug(m_parsedCosXml); diff --git a/src/Cafe/TitleList/TitleInfo.h b/src/Cafe/TitleList/TitleInfo.h index 86f89391..8137ff08 100644 --- a/src/Cafe/TitleList/TitleInfo.h +++ b/src/Cafe/TitleList/TitleInfo.h @@ -122,6 +122,7 @@ public: uint32 GetAppType() const; // from app.xml std::string GetTitleName() const; // from meta.xml CafeConsoleRegion GetMetaRegion() const; // from meta.xml + uint32 GetOlvAccesskey() const; // cos.xml std::string GetArgStr() const; diff --git a/src/Cemu/napi/napi_helper.cpp b/src/Cemu/napi/napi_helper.cpp index b478e1c0..776baf33 100644 --- a/src/Cemu/napi/napi_helper.cpp +++ b/src/Cemu/napi/napi_helper.cpp @@ -67,6 +67,38 @@ CURLcode _sslctx_function_SOAP(CURL* curl, void* sslctx, void* param) return CURLE_OK; } +CURLcode _sslctx_function_OLIVE(CURL* curl, void* sslctx, void* param) +{ + if (iosuCrypto_addCACertificate(sslctx, 105) == false) + { + cemuLog_log(LogType::Force, "Invalid CA certificate (105)"); + cemuLog_log(LogType::Force, "Certificate error"); + } + if (iosuCrypto_addClientCertificate(sslctx, 7) == false) + { + cemuLog_log(LogType::Force, "Olive client certificate error"); + } + + // NSSLAddServerPKIGroups(sslCtx, 3, &x, &y); + { + std::vector certGroups = { + 100, 101, 102, 103, 104, 105, + 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, + 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, + 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, + 1030, 1031, 1032, 1033 + }; + + for (auto& certId : certGroups) + iosuCrypto_addCACertificate(sslctx, certId); + } + + SSL_CTX_set_mode((SSL_CTX*)sslctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_verify_depth((SSL_CTX*)sslctx, 2); + SSL_CTX_set_verify((SSL_CTX*)sslctx, SSL_VERIFY_PEER, nullptr); + return CURLE_OK; +} + CurlRequestHelper::CurlRequestHelper() { m_curl = curl_easy_init(); @@ -122,6 +154,11 @@ void CurlRequestHelper::initate(std::string url, SERVER_SSL_CONTEXT sslContext) curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_SOAP); curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); } + else if (sslContext == SERVER_SSL_CONTEXT::OLIVE) + { + curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_OLIVE); + curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); + } else { cemu_assert(false); @@ -178,9 +215,11 @@ bool CurlRequestHelper::submitRequest(bool isPost) // post if (isPost) { - curl_easy_setopt(m_curl, CURLOPT_POST, 1); - curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, m_postData.data()); - curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, m_postData.size()); + if (!m_isUsingMultipartFormData) { + curl_easy_setopt(m_curl, CURLOPT_POST, 1); + curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, m_postData.data()); + curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, m_postData.size()); + } } else curl_easy_setopt(m_curl, CURLOPT_POST, 0); diff --git a/src/Cemu/napi/napi_helper.h b/src/Cemu/napi/napi_helper.h index 00d57e92..6403f074 100644 --- a/src/Cemu/napi/napi_helper.h +++ b/src/Cemu/napi/napi_helper.h @@ -27,6 +27,7 @@ public: CCS, // ccs. IDBE, // idbe-wup. TAGAYA, // tagaya.wup.shop.nintendo.net + OLIVE, // olv. }; CurlRequestHelper(); @@ -50,6 +51,11 @@ public: return m_receiveBuffer; } + void setUseMultipartFormData(bool isUsingMultipartFormData) + { + m_isUsingMultipartFormData = isUsingMultipartFormData; + } + private: static size_t __curlWriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata); @@ -61,6 +67,8 @@ private: // write callback redirect bool (*m_cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast); void* m_writeCallbackUserData{}; + + bool m_isUsingMultipartFormData = false; }; class CurlSOAPHelper // todo - make this use CurlRequestHelper diff --git a/src/config/NetworkSettings.cpp b/src/config/NetworkSettings.cpp index 20df8251..5cc66a91 100644 --- a/src/config/NetworkSettings.cpp +++ b/src/config/NetworkSettings.cpp @@ -29,6 +29,7 @@ void NetworkConfig::Load(XMLConfigParser& parser) urls.IDBE = u.get("idbe", NintendoURLs::IDBEURL); urls.BOSS = u.get("boss", NintendoURLs::BOSSURL); urls.TAGAYA = u.get("tagaya", NintendoURLs::TAGAYAURL); + urls.OLV = u.get("olv", NintendoURLs::OLVURL); if (static_cast(GetConfig().account.active_service.GetValue()) == NetworkService::Custom) LaunchSettings::ChangeNetworkServiceURL(2); } diff --git a/src/config/NetworkSettings.h b/src/config/NetworkSettings.h index f289e679..26137cdd 100644 --- a/src/config/NetworkSettings.h +++ b/src/config/NetworkSettings.h @@ -30,6 +30,7 @@ struct NetworkConfig { ConfigValue IDBE; ConfigValue BOSS; ConfigValue TAGAYA; + ConfigValue OLV; }urls{}; public: @@ -50,6 +51,7 @@ struct NintendoURLs { inline static std::string IDBEURL = "https://idbe-wup.cdn.nintendo.net/icondata"; inline static std::string BOSSURL = "https://npts.app.nintendo.net/p01/tasksheet"; inline static std::string TAGAYAURL = "https://tagaya.wup.shop.nintendo.net/tagaya/versionlist"; + inline static std::string OLVURL = "https://discovery.olv.nintendo.net/v1/endpoint"; }; struct PretendoURLs { @@ -62,6 +64,7 @@ struct PretendoURLs { inline static std::string IDBEURL = "https://idbe-wup.cdn.pretendo.cc/icondata"; inline static std::string BOSSURL = "https://npts.app.pretendo.cc/p01/tasksheet"; inline static std::string TAGAYAURL = "https://tagaya.wup.shop.pretendo.cc/tagaya/versionlist"; + inline static std::string OLVURL = "https://discovery.olv.pretendo.cc/v1/endpoint"; }; typedef XMLDataConfig XMLNetworkConfig_t;