diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 59b6aa42..b7656789 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -422,6 +422,8 @@ add_library(CemuCafe OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h OS/libs/nn_olv/nn_olv_PostTypes.cpp OS/libs/nn_olv/nn_olv_PostTypes.h + OS/libs/nn_olv/nn_olv_OfflineDB.cpp + OS/libs/nn_olv/nn_olv_OfflineDB.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/OS/libs/coreinit/coreinit_MCP.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp index a784e593..b348218f 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp @@ -546,6 +546,7 @@ void coreinitExport_UCReadSysConfig(PPCInterpreter_t* hCPU) { // get parental online control for online features // note: This option is account-bound, the p_acct1 prefix indicates that the account in slot 1 is used + // a non-zero value means network access is restricted through parental access. 0 means allowed // account in slot 1 if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // data type is guessed @@ -561,7 +562,7 @@ void coreinitExport_UCReadSysConfig(PPCInterpreter_t* hCPU) { // miiverse restrictions if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) - memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // data type is guessed (0 -> no restrictions, 1 -> read only?, 2 -> no access?) + memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // data type is guessed (0 -> no restrictions, 1 -> read only, 2 -> no access) } else if (_strcmpi(ucParam->settingName, "s_acct01.uuid") == 0) { diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv.cpp index 50036249..25245b5c 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv.cpp @@ -5,6 +5,7 @@ #include "nn_olv_DownloadCommunityTypes.h" #include "nn_olv_UploadFavoriteTypes.h" #include "nn_olv_PostTypes.h" +#include "nn_olv_OfflineDB.h" #include "Cafe/OS/libs/proc_ui/proc_ui.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" @@ -13,179 +14,6 @@ namespace nn { namespace olv { - struct DownloadedPostData_t - { - /* +0x0000 */ uint32be flags; - /* +0x0004 */ uint32be userPrincipalId; - /* +0x0008 */ char postId[0x20]; // size guessed - /* +0x0028 */ uint64 postDate; - /* +0x0030 */ uint8 feeling; - /* +0x0031 */ uint8 padding0031[3]; - /* +0x0034 */ uint32be regionId; - /* +0x0038 */ uint8 platformId; - /* +0x0039 */ uint8 languageId; - /* +0x003A */ uint8 countryId; - /* +0x003B */ uint8 padding003B[1]; - /* +0x003C */ uint16be bodyText[0x100]; // actual size is unknown - /* +0x023C */ uint32be bodyTextLength; - /* +0x0240 */ uint8 compressedMemoBody[0xA000]; // 40KB - /* +0xA240 */ uint32be compressedMemoBodyRelated; // size of compressed data? - /* +0xA244 */ uint16be topicTag[0x98]; - // app data - /* +0xA374 */ uint8 appData[0x400]; - /* +0xA774 */ uint32be appDataLength; - // external binary - /* +0xA778 */ uint8 externalBinaryUrl[0x100]; - /* +0xA878 */ uint32be externalBinaryDataSize; - // external image - /* +0xA87C */ uint8 externalImageDataUrl[0x100]; - /* +0xA97C */ uint32be externalImageDataSize; - // external url ? - /* +0xA980 */ char externalUrl[0x100]; - // mii - /* +0xAA80 */ uint8 miiData[0x60]; - /* +0xAAE0 */ uint16be miiNickname[0x20]; - /* +0xAB20 */ uint8 unusedAB20[0x14E0]; - - // everything above is part of DownloadedDataBase - // everything below is part of DownloadedPostData - /* +0xC000 */ uint8 uknDataC000[8]; // ?? - /* +0xC008 */ uint32be communityId; - /* +0xC00C */ uint32be empathyCount; - /* +0xC010 */ uint32be commentCount; - /* +0xC014 */ uint8 unused[0x1F4]; - }; // size: 0xC208 - - static_assert(sizeof(DownloadedPostData_t) == 0xC208, ""); - static_assert(offsetof(DownloadedPostData_t, postDate) == 0x0028, ""); - static_assert(offsetof(DownloadedPostData_t, platformId) == 0x0038, ""); - static_assert(offsetof(DownloadedPostData_t, bodyText) == 0x003C, ""); - static_assert(offsetof(DownloadedPostData_t, compressedMemoBody) == 0x0240, ""); - static_assert(offsetof(DownloadedPostData_t, topicTag) == 0xA244, ""); - static_assert(offsetof(DownloadedPostData_t, appData) == 0xA374, ""); - static_assert(offsetof(DownloadedPostData_t, externalBinaryUrl) == 0xA778, ""); - static_assert(offsetof(DownloadedPostData_t, externalImageDataUrl) == 0xA87C, ""); - static_assert(offsetof(DownloadedPostData_t, externalUrl) == 0xA980, ""); - static_assert(offsetof(DownloadedPostData_t, miiData) == 0xAA80, ""); - static_assert(offsetof(DownloadedPostData_t, miiNickname) == 0xAAE0, ""); - static_assert(offsetof(DownloadedPostData_t, unusedAB20) == 0xAB20, ""); - static_assert(offsetof(DownloadedPostData_t, communityId) == 0xC008, ""); - static_assert(offsetof(DownloadedPostData_t, empathyCount) == 0xC00C, ""); - static_assert(offsetof(DownloadedPostData_t, commentCount) == 0xC010, ""); - - const int POST_DATA_FLAG_HAS_BODY_TEXT = (0x0001); - const int POST_DATA_FLAG_HAS_BODY_MEMO = (0x0002); - - - void export_DownloadPostDataList(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(downloadedTopicData, void, 0); // DownloadedTopicData - ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 1); // DownloadedPostData - ppcDefineParamTypePtr(downloadedPostDataSize, uint32be, 2); - ppcDefineParamS32(maxCount, 3); - ppcDefineParamTypePtr(listParam, void, 4); // DownloadPostDataListParam - - maxCount = 0; // DISABLED - - // just some test - for (sint32 i = 0; i < maxCount; i++) - { - DownloadedPostData_t* postData = downloadedPostData + i; - memset(postData, 0, sizeof(DownloadedPostData_t)); - postData->userPrincipalId = 0x1000 + i; - // post id - sprintf(postData->postId, "postid-%04x", i+(GetTickCount()%10000)); - postData->bodyTextLength = 12; - postData->bodyText[0] = 'H'; - postData->bodyText[1] = 'e'; - postData->bodyText[2] = 'l'; - postData->bodyText[3] = 'l'; - postData->bodyText[4] = 'o'; - postData->bodyText[5] = ' '; - postData->bodyText[6] = 'w'; - postData->bodyText[7] = 'o'; - postData->bodyText[8] = 'r'; - postData->bodyText[9] = 'l'; - postData->bodyText[10] = 'd'; - postData->bodyText[11] = '!'; - - postData->miiNickname[0] = 'C'; - postData->miiNickname[1] = 'e'; - postData->miiNickname[2] = 'm'; - postData->miiNickname[3] = 'u'; - postData->miiNickname[4] = '-'; - postData->miiNickname[5] = 'M'; - postData->miiNickname[6] = 'i'; - postData->miiNickname[7] = 'i'; - - postData->topicTag[0] = 't'; - postData->topicTag[1] = 'o'; - postData->topicTag[2] = 'p'; - postData->topicTag[3] = 'i'; - postData->topicTag[4] = 'c'; - - postData->flags = POST_DATA_FLAG_HAS_BODY_TEXT; - } - *downloadedPostDataSize = maxCount; - - osLib_returnFromFunction(hCPU, 0); - } - - void exportDownloadPostData_TestFlags(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); - ppcDefineParamU32(testFlags, 1); - - if (((uint32)downloadedPostData->flags) & testFlags) - osLib_returnFromFunction(hCPU, 1); - else - osLib_returnFromFunction(hCPU, 0); - } - - void exportDownloadPostData_GetPostId(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); - osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(downloadedPostData->postId)); - } - - void exportDownloadPostData_GetMiiNickname(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); - if(downloadedPostData->miiNickname[0] == 0 ) - osLib_returnFromFunction(hCPU, MPTR_NULL); - else - osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(downloadedPostData->miiNickname)); - } - - void exportDownloadPostData_GetTopicTag(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); - osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(downloadedPostData->topicTag)); - } - - void exportDownloadPostData_GetBodyText(PPCInterpreter_t* hCPU) - { - ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); - ppcDefineParamWStrBE(strOut, 1); - ppcDefineParamS32(maxLength, 2); - - if (((uint32)downloadedPostData->flags&POST_DATA_FLAG_HAS_BODY_TEXT) == 0) - { - osLib_returnFromFunction(hCPU, 0xC1106800); - return; - } - - memset(strOut, 0, sizeof(uint16be)*maxLength); - sint32 copyLen = std::min(maxLength - 1, (sint32)downloadedPostData->bodyTextLength); - for (sint32 i = 0; i < copyLen; i++) - { - strOut[i] = downloadedPostData->bodyText[i]; - } - strOut[copyLen] = '\0'; - - osLib_returnFromFunction(hCPU, 0); - } - struct PortalAppParam_t { /* +0x1A663B */ char serviceToken[32]; // size is unknown @@ -284,6 +112,10 @@ namespace nn void load() { + g_ReportTypes = 0; + g_IsOnlineMode = false; + g_IsInitialized = false; + g_IsOfflineDBMode = false; loadOliveInitializeTypes(); loadOliveUploadCommunityTypes(); @@ -293,13 +125,6 @@ namespace nn 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); -// osLib_addFunction("nn_olv", "GetMiiNickname__Q3_2nn3olv18DownloadedDataBaseCFv", exportDownloadPostData_GetMiiNickname); -// osLib_addFunction("nn_olv", "GetTopicTag__Q3_2nn3olv18DownloadedDataBaseCFv", exportDownloadPostData_GetTopicTag); -// osLib_addFunction("nn_olv", "GetBodyText__Q3_2nn3olv18DownloadedDataBaseCFPwUi", exportDownloadPostData_GetBodyText); - osLib_addFunction("nn_olv", "GetServiceToken__Q4_2nn3olv6hidden14PortalAppParamCFv", exportPortalAppParam_GetServiceToken); cafeExportRegisterFunc(StubPostApp, "nn_olv", "UploadPostDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv28UploadPostDataByPostAppParam", LogType::Force); @@ -314,5 +139,10 @@ namespace nn cafeExportRegisterFunc(UploadedPostData_GetPostId, "nn_olv", "GetPostId__Q3_2nn3olv16UploadedPostDataCFv", LogType::Force); } + void unload() // not called yet + { + OfflineDB_Shutdown(); + } + } } \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv.h b/src/Cafe/OS/libs/nn_olv/nn_olv.h index c608e391..52474b49 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv.h +++ b/src/Cafe/OS/libs/nn_olv/nn_olv.h @@ -19,5 +19,6 @@ namespace nn sint32 GetOlvAccessKey(uint32_t* pOutKey); void load(); + void unload(); } } \ 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 index 718c10c3..c598e7ba 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_Common.h +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_Common.h @@ -69,6 +69,7 @@ namespace nn extern uint32_t g_ReportTypes; extern bool g_IsInitialized; extern bool g_IsOnlineMode; + extern bool g_IsOfflineDBMode; // use offline cache for posts static void InitializeOliveRequest(CurlRequestHelper& req) { @@ -175,5 +176,39 @@ namespace nn bool FormatCommunityCode(char* pOutCode, uint32* outLen, uint32 communityId); sint32 olv_curlformcode_to_error(CURLFORMcode code); + + // convert and copy utf8 string into UC2 big-endian array + template + uint32 SetStringUC2(uint16be(&str)[TLength], std::string_view sv, bool unescape = false) + { + if(unescape) + { + // todo + } + std::wstring ws = boost::nowide::widen(sv); + size_t copyLen = std::min(TLength-1, ws.size()); + for(size_t i=0; i + uint32 SetStringUC2(uint16be(&str)[TLength], const uint16be* strIn) + { + size_t copyLen = TLength-1; + for(size_t i=0; im_Flags & InitializeParam::FLAG_OFFLINE_MODE) == 0) { - g_IsOnlineMode = true; independentServiceToken_t token; diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp new file mode 100644 index 00000000..241630aa --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp @@ -0,0 +1,215 @@ +#include "nn_olv_Common.h" +#include "nn_olv_PostTypes.h" +#include "nn_olv_OfflineDB.h" +#include "Cemu/ncrypto/ncrypto.h" // for base64 encoder/decoder +#include "util/helpers/helpers.h" +#include "Config/ActiveSettings.h" +#include "Cafe/CafeSystem.h" +#include +#include +#include + +namespace nn +{ + namespace olv + { + std::mutex g_offlineDBMutex; + bool g_offlineDBInitialized = false; + ZArchiveReader* g_offlineDBArchive{nullptr}; + + void OfflineDB_LazyInit() + { + std::scoped_lock _l(g_offlineDBMutex); + if(g_offlineDBInitialized) + return; + // open archive + g_offlineDBArchive = ZArchiveReader::OpenFromFile(ActiveSettings::GetUserDataPath("resources/miiverse/OfflineDB.zar")); + if(!g_offlineDBArchive) + cemuLog_log(LogType::Force, "Failed to open resources/miiverse/OfflineDB.zar. Miiverse posts will not be available"); + g_offlineDBInitialized = true; + } + + void OfflineDB_Shutdown() + { + std::scoped_lock _l(g_offlineDBMutex); + if(!g_offlineDBInitialized) + return; + delete g_offlineDBArchive; + g_offlineDBInitialized = false; + } + + bool CheckForOfflineDBFile(const char* filePath, uint32* fileSize) + { + if(!g_offlineDBArchive) + return false; + ZArchiveNodeHandle fileHandle = g_offlineDBArchive->LookUp(filePath); + if (!g_offlineDBArchive->IsFile(fileHandle)) + return false; + if(fileSize) + *fileSize = g_offlineDBArchive->GetFileSize(fileHandle); + return true; + } + + bool LoadOfflineDBFile(const char* filePath, std::vector& fileData) + { + fileData.clear(); + if(!g_offlineDBArchive) + return false; + ZArchiveNodeHandle fileHandle = g_offlineDBArchive->LookUp(filePath); + if (!g_offlineDBArchive->IsFile(fileHandle)) + return false; + fileData.resize(g_offlineDBArchive->GetFileSize(fileHandle)); + g_offlineDBArchive->ReadFromFile(fileHandle, 0, fileData.size(), fileData.data()); + return true; + } + + void TryLoadCompressedMemoImage(DownloadedPostData& downloadedPostData) + { + const unsigned char tgaHeader_320x120_32BPP[] = {0x0,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40,0x1,0x78,0x0,0x20,0x8}; + std::string memoImageFilename = fmt::format("memo/{}", (char*)downloadedPostData.downloadedDataBase.postId); + std::vector bitmaskCompressedImg; + if (!LoadOfflineDBFile(memoImageFilename.c_str(), bitmaskCompressedImg)) + return; + if (bitmaskCompressedImg.size() != (320*120)/8) + return; + std::vector decompressedImage; + decompressedImage.resize(sizeof(tgaHeader_320x120_32BPP) + 320 * 120 * 4); + memcpy(decompressedImage.data(), tgaHeader_320x120_32BPP, sizeof(tgaHeader_320x120_32BPP)); + uint8* pOut = decompressedImage.data() + sizeof(tgaHeader_320x120_32BPP); + for(int i=0; i<320*120; i++) + { + bool isWhite = (bitmaskCompressedImg[i/8] & (1 << (i%8))) != 0; + if(isWhite) + { + pOut[0] = pOut[1] = pOut[2] = pOut[3] = 0xFF; + } + else + { + pOut[0] = pOut[1] = pOut[2] = 0; + pOut[3] = 0xFF; + } + pOut += 4; + } + // store compressed image + uLongf compressedDestLen = 40960; + int r = compress((uint8*)downloadedPostData.downloadedDataBase.compressedMemoBody, &compressedDestLen, decompressedImage.data(), decompressedImage.size()); + if( r != Z_OK) + return; + downloadedPostData.downloadedDataBase.compressedMemoBodySize = compressedDestLen; + downloadedPostData.downloadedDataBase.SetFlag(DownloadedDataBase::FLAGS::HAS_BODY_MEMO); + } + + void CheckForExternalImage(DownloadedPostData& downloadedPostData) + { + std::string externalImageFilename = fmt::format("image/{}.jpg", (char*)downloadedPostData.downloadedDataBase.postId); + uint32 fileSize; + if (!CheckForOfflineDBFile(externalImageFilename.c_str(), &fileSize)) + return; + strcpy((char*)downloadedPostData.downloadedDataBase.externalImageDataUrl, externalImageFilename.c_str()); + downloadedPostData.downloadedDataBase.SetFlag(DownloadedDataBase::FLAGS::HAS_EXTERNAL_IMAGE); + downloadedPostData.downloadedDataBase.externalImageDataSize = fileSize; + } + + nnResult _Async_OfflineDB_DownloadPostDataListParam_DownloadPostDataList(coreinit::OSEvent* event, DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param) + { + scope_exit _se([&](){coreinit::OSSignalEvent(event);}); + + uint64 titleId = CafeSystem::GetForegroundTitleId(); + + memset(downloadedTopicData, 0, sizeof(DownloadedTopicData)); + memset(downloadedPostData, 0, sizeof(DownloadedPostData) * maxCount); + *postCountOut = 0; + + const char* postXmlFilename = nullptr; + if(titleId == 0x0005000010143400 || titleId == 0x0005000010143500 || titleId == 0x0005000010143600) + postXmlFilename = "PostList_WindWakerHD.xml"; + + if (!postXmlFilename) + return OLV_RESULT_SUCCESS; + + // load post XML + std::vector xmlData; + if (!LoadOfflineDBFile(postXmlFilename, xmlData)) + return OLV_RESULT_SUCCESS; + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_buffer(xmlData.data(), xmlData.size()); + if (!result) + return OLV_RESULT_SUCCESS; + // collect list of all post xml nodes + std::vector postXmlNodes; + for (pugi::xml_node postNode = doc.child("posts").child("post"); postNode; postNode = postNode.next_sibling("post")) + postXmlNodes.push_back(postNode); + + // randomly select up to maxCount posts + srand(GetTickCount()); + uint32 postCount = 0; + while(!postXmlNodes.empty() && postCount < maxCount) + { + uint32 index = rand() % postXmlNodes.size(); + pugi::xml_node& postNode = postXmlNodes[index]; + + auto& addedPost = downloadedPostData[postCount]; + memset(&addedPost, 0, sizeof(DownloadedPostData)); + if (!ParseXML_DownloadedPostData(addedPost, postNode) ) + continue; + TryLoadCompressedMemoImage(addedPost); + CheckForExternalImage(addedPost); + postCount++; + // remove from post list + postXmlNodes[index] = postXmlNodes.back(); + postXmlNodes.pop_back(); + } + *postCountOut = postCount; + return OLV_RESULT_SUCCESS; + } + + nnResult OfflineDB_DownloadPostDataListParam_DownloadPostDataList(DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param) + { + OfflineDB_LazyInit(); + + memset(downloadedTopicData, 0, sizeof(DownloadedTopicData)); + downloadedTopicData->communityId = param->communityId; + *postCountOut = 0; + + if(param->_HasFlag(DownloadPostDataListParam::FLAGS::SELF_ONLY)) + return OLV_RESULT_SUCCESS; // the offlineDB doesn't contain any self posts + + StackAllocator doneEvent; + coreinit::OSInitEvent(doneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + auto asyncTask = std::async(std::launch::async, _Async_OfflineDB_DownloadPostDataListParam_DownloadPostDataList, doneEvent.GetPointer(), downloadedTopicData, downloadedPostData, postCountOut, maxCount, param); + coreinit::OSWaitEvent(doneEvent); + nnResult r = asyncTask.get(); + return r; + } + + nnResult _Async_OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(coreinit::OSEvent* event, DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize) + { + scope_exit _se([&](){coreinit::OSSignalEvent(event);}); + + if (!_this->TestFlags(_this, DownloadedDataBase::FLAGS::HAS_EXTERNAL_IMAGE)) + return OLV_RESULT_MISSING_DATA; + + // not all games may use JPEG files? + std::string externalImageFilename = fmt::format("image/{}.jpg", (char*)_this->postId); + std::vector jpegData; + if (!LoadOfflineDBFile(externalImageFilename.c_str(), jpegData)) + return OLV_RESULT_FAILED_REQUEST; + + memcpy(imageDataOut, jpegData.data(), jpegData.size()); + *imageSizeOut = jpegData.size(); + + return OLV_RESULT_SUCCESS; + } + + nnResult OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize) + { + StackAllocator doneEvent; + coreinit::OSInitEvent(doneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + auto asyncTask = std::async(std::launch::async, _Async_OfflineDB_DownloadPostDataListParam_DownloadExternalImageData, doneEvent.GetPointer(), _this, imageDataOut, imageSizeOut, maxSize); + coreinit::OSWaitEvent(doneEvent); + nnResult r = asyncTask.get(); + return r; + } + + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.h b/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.h new file mode 100644 index 00000000..ed790479 --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.h @@ -0,0 +1,16 @@ +#pragma once +#include "Cafe/OS/libs/nn_common.h" +#include "nn_olv_Common.h" + +namespace nn +{ + namespace olv + { + void OfflineDB_Init(); + void OfflineDB_Shutdown(); + + nnResult OfflineDB_DownloadPostDataListParam_DownloadPostDataList(DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param); + nnResult OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize); + + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp index 5056f2fe..722e5584 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp @@ -1,5 +1,6 @@ #include "Cafe/OS/libs/nn_olv/nn_olv_Common.h" #include "nn_olv_PostTypes.h" +#include "nn_olv_OfflineDB.h" #include "Cemu/ncrypto/ncrypto.h" // for base64 decoder #include "util/helpers/helpers.h" #include @@ -9,41 +10,28 @@ namespace nn { namespace olv { - - template - uint32 SetStringUC2(uint16be(&str)[TLength], std::string_view sv, bool unescape = false) - { - if(unescape) - { - // todo - } - std::wstring ws = boost::nowide::widen(sv); - size_t copyLen = std::min(TLength-1, ws.size()); - for(size_t i=0; i 0) obj.SetFlag(DownloadedDataBase::FLAGS::HAS_BODY_TEXT); } + if(tokenNode = xmlNode.child("topic_tag"); tokenNode) + { + SetStringUC2(obj.topicTag, tokenNode.child_value(), true); + } if(tokenNode = xmlNode.child("feeling_id"); tokenNode) { obj.feeling = ConvertString(tokenNode.child_value()); if(obj.feeling < 0 || obj.feeling >= 5) { - cemuLog_log(LogType::Force, "DownloadedDataBase::ParseXml: feeling_id out of range"); + cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase::ParseXml: feeling_id out of range"); return false; } } @@ -52,7 +40,7 @@ namespace nn std::string_view id_sv = tokenNode.child_value(); if(id_sv.size() > 22) { - cemuLog_log(LogType::Force, "DownloadedDataBase::ParseXml: id too long"); + cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase::ParseXml: id too long"); return false; } memcpy(obj.postId, id_sv.data(), id_sv.size()); @@ -67,7 +55,7 @@ namespace nn obj.SetFlag(DownloadedDataBase::FLAGS::IS_NOT_AUTOPOST); else { - cemuLog_log(LogType::Force, "DownloadedDataBase::ParseXml: is_autopost has invalid value"); + cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase::ParseXml: is_autopost has invalid value"); return false; } } @@ -116,6 +104,36 @@ namespace nn { obj.countryId = ConvertString(tokenNode.child_value()); } + if(tokenNode = xmlNode.child("painting"); tokenNode) + { + if(pugi::xml_node subNode = tokenNode.child("content"); subNode) + { + std::vector paintingData = NCrypto::base64Decode(subNode.child_value()); + if (paintingData.size() > 0xA000) + { + cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase painting content is too large"); + return false; + } + memcpy(obj.compressedMemoBody, paintingData.data(), paintingData.size()); + obj.SetFlag(DownloadedDataBase::FLAGS::HAS_BODY_MEMO); + } + if(pugi::xml_node subNode = tokenNode.child("size"); subNode) + { + obj.compressedMemoBodySize = ConvertString(subNode.child_value()); + } + } + if(tokenNode = xmlNode.child("app_data"); tokenNode) + { + std::vector appData = NCrypto::base64Decode(tokenNode.child_value()); + if (appData.size() > 0x400) + { + cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase AppData is too large"); + return false; + } + memcpy(obj.appData, appData.data(), appData.size()); + obj.appDataLength = appData.size(); + obj.SetFlag(DownloadedDataBase::FLAGS::HAS_APP_DATA); + } return true; } @@ -256,6 +274,121 @@ namespace nn return 0; } + nnResult DownloadedDataBase::DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize) + { + if(g_IsOfflineDBMode) + return OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(_this, imageDataOut, imageSizeOut, maxSize); + + if(!g_IsOnlineMode) + return OLV_RESULT_OFFLINE_MODE_REQUEST; + if (!TestFlags(_this, FLAGS::HAS_EXTERNAL_IMAGE)) + return OLV_RESULT_MISSING_DATA; + + cemuLog_logDebug(LogType::Force, "DownloadedDataBase::DownloadExternalImageData not implemented"); + return OLV_RESULT_FAILED_REQUEST; // placeholder error + } + + nnResult DownloadPostDataListParam::GetRawDataUrl(DownloadPostDataListParam* _this, char* urlOut, uint32 urlMaxSize) + { + if(!g_IsOnlineMode) + return OLV_RESULT_OFFLINE_MODE_REQUEST; + //if(_this->communityId == 0) + // cemuLog_log(LogType::Force, "DownloadPostDataListParam::GetRawDataUrl called with invalid communityId"); + + // get base url + std::string baseUrl; + baseUrl.append(g_DiscoveryResults.apiEndpoint); + //baseUrl.append(fmt::format("/v1/communities/{}/posts", (uint32)_this->communityId)); + cemu_assert_debug(_this->communityId == 0); + baseUrl.append(fmt::format("/v1/posts.search", (uint32)_this->communityId)); + + // "v1/posts.search" + + // build parameter string + std::string params; + + // this function behaves differently for the Wii U menu? Where it can lookup posts by titleId? + if(_this->titleId != 0) + { + cemu_assert_unimplemented(); // Wii U menu mode + } + + // todo: Generic parameters. Which includes: language_id, limit, type=text/memo + + // handle postIds + for(size_t i=0; i<_this->MAX_NUM_POST_ID; i++) + { + if(_this->searchPostId[i].str[0] == '\0') + continue; + cemu_assert_unimplemented(); // todo + // todo - postId parameter + // handle filters + if(_this->_HasFlag(DownloadPostDataListParam::FLAGS::WITH_MII)) + params.append("&with_mii=1"); + if(_this->_HasFlag(DownloadPostDataListParam::FLAGS::WITH_EMPATHY)) + params.append("&with_empathy_added=1"); + if(_this->bodyTextMaxLength != 0) + params.append(fmt::format("&max_body_length={}", _this->bodyTextMaxLength)); + } + + if(_this->titleId != 0) + params.append(fmt::format("&title_id={}", (uint64)_this->titleId)); + + if (_this->_HasFlag(DownloadPostDataListParam::FLAGS::FRIENDS_ONLY)) + params.append("&by=friend"); + if (_this->_HasFlag(DownloadPostDataListParam::FLAGS::FOLLOWERS_ONLY)) + params.append("&by=followings"); + if (_this->_HasFlag(DownloadPostDataListParam::FLAGS::SELF_ONLY)) + params.append("&by=self"); + + if(!params.empty()) + params[0] = '?'; // replace the leading ampersand + + baseUrl.append(params); + if(baseUrl.size()+1 > urlMaxSize) + return OLV_RESULT_NOT_ENOUGH_SIZE; + strncpy(urlOut, baseUrl.c_str(), urlMaxSize); + return OLV_RESULT_SUCCESS; + } + + nnResult DownloadPostDataList(DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param) + { + if(g_IsOfflineDBMode) + return OfflineDB_DownloadPostDataListParam_DownloadPostDataList(downloadedTopicData, downloadedPostData, postCountOut, maxCount, param); + memset(downloadedTopicData, 0, sizeof(DownloadedTopicData)); + downloadedTopicData->communityId = param->communityId; + *postCountOut = 0; + + char urlBuffer[2048]; + if (NN_RESULT_IS_FAILURE(DownloadPostDataListParam::GetRawDataUrl(param, urlBuffer, sizeof(urlBuffer)))) + return OLV_RESULT_INVALID_PARAMETER; + + /* + CurlRequestHelper req; + req.initate(urlBuffer, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); + InitializeOliveRequest(req); + bool reqResult = req.submitRequest(); + if (!reqResult) + { + long httpCode = 0; + curl_easy_getinfo(req.getCURL(), CURLINFO_RESPONSE_CODE, &httpCode); + cemuLog_log(LogType::Force, "Failed request: {} ({})", urlBuffer, 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; + } + */ + + *postCountOut = 0; + + return OLV_RESULT_SUCCESS; + } + void loadOlivePostAndTopicTypes() { cafeExportRegisterFunc(GetSystemTopicDataListFromRawData, "nn_olv", "GetSystemTopicDataListFromRawData__Q3_2nn3olv6hiddenFPQ4_2nn3olv6hidden29DownloadedSystemTopicDataListPQ4_2nn3olv6hidden24DownloadedSystemPostDataPUiUiPCUcT4", LogType::None); @@ -279,6 +412,8 @@ namespace nn cafeExportRegisterFunc(DownloadedDataBase::GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); cafeExportRegisterFunc(DownloadedDataBase::GetPostId, "nn_olv", "GetPostId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); cafeExportRegisterFunc(DownloadedDataBase::GetMiiData2, "nn_olv", "GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::DownloadExternalImageData, "nn_olv", "DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi", LogType::None); + cafeExportRegisterFunc(DownloadedDataBase::GetExternalImageDataSize, "nn_olv", "GetExternalImageDataSize__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None); // DownloadedPostData getters cafeExportRegisterFunc(DownloadedPostData::GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv18DownloadedPostDataCFv", LogType::None); @@ -305,6 +440,23 @@ namespace nn cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemTopicData, "nn_olv", "GetDownloadedSystemTopicData__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFi", LogType::None); cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemPostData, "nn_olv", "GetDownloadedSystemPostData__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFiT1", LogType::None); + // DownloadPostDataListParam constructor and getters + cafeExportRegisterFunc(DownloadPostDataListParam::Construct, "nn_olv", "__ct__Q3_2nn3olv25DownloadPostDataListParamFv", LogType::None); + cafeExportRegisterFunc(DownloadPostDataListParam::SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::None); + cafeExportRegisterFunc(DownloadPostDataListParam::SetLanguageId, "nn_olv", "SetLanguageId__Q3_2nn3olv25DownloadPostDataListParamFUc", LogType::None); + cafeExportRegisterFunc(DownloadPostDataListParam::SetCommunityId, "nn_olv", "SetCommunityId__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::None); + cafeExportRegisterFunc(DownloadPostDataListParam::SetSearchKey, "nn_olv", "SetSearchKey__Q3_2nn3olv25DownloadPostDataListParamFPCwUc", LogType::None); + cafeExportRegisterFunc(DownloadPostDataListParam::SetSearchKeySingle, "nn_olv", "SetSearchKey__Q3_2nn3olv25DownloadPostDataListParamFPCw", LogType::None); + cafeExportRegisterFunc(DownloadPostDataListParam::SetSearchPid, "nn_olv", "SetSearchPid__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::None); + cafeExportRegisterFunc(DownloadPostDataListParam::SetPostId, "nn_olv", "SetPostId__Q3_2nn3olv25DownloadPostDataListParamFPCcUi", LogType::None); + cafeExportRegisterFunc(DownloadPostDataListParam::SetPostDate, "nn_olv", "SetPostDate__Q3_2nn3olv25DownloadPostDataListParamFL", LogType::None); + cafeExportRegisterFunc(DownloadPostDataListParam::SetPostDataMaxNum, "nn_olv", "SetPostDataMaxNum__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::None); + cafeExportRegisterFunc(DownloadPostDataListParam::SetBodyTextMaxLength, "nn_olv", "SetBodyTextMaxLength__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::None); + + // URL and downloading functions + cafeExportRegisterFunc(DownloadPostDataListParam::GetRawDataUrl, "nn_olv", "GetRawDataUrl__Q3_2nn3olv25DownloadPostDataListParamCFPcUi", LogType::None); + cafeExportRegisterFunc(DownloadPostDataList, "nn_olv", "DownloadPostDataList__Q2_2nn3olvFPQ3_2nn3olv19DownloadedTopicDataPQ3_2nn3olv18DownloadedPostDataPUiUiPCQ3_2nn3olv25DownloadPostDataListParam", LogType::None); + } } diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h index 3ca4f87e..e6078a7a 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h @@ -1,5 +1,6 @@ #pragma once #include +#include "nn_olv_Common.h" namespace nn { @@ -154,8 +155,11 @@ namespace nn return OLV_RESULT_INVALID_PTR; if (maxLength == 0) return OLV_RESULT_NOT_ENOUGH_SIZE; + if (!TestFlags(_this, FLAGS::HAS_BODY_TEXT)) + return OLV_RESULT_MISSING_DATA; + memset(bodyTextOut, 0, maxLength * sizeof(uint16)); uint32 outputLength = std::min(_this->bodyTextLength, maxLength); - olv_wstrncpy((char16_t*)bodyTextOut, (char16_t*)_this->bodyText, _this->bodyTextLength); + olv_wstrncpy((char16_t*)bodyTextOut, (char16_t*)_this->bodyText, outputLength); return OLV_RESULT_SUCCESS; } @@ -213,9 +217,19 @@ namespace nn return _this->postId; } - // todo: // DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi + static nnResult DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize); + // GetExternalImageDataSize__Q3_2nn3olv18DownloadedDataBaseCFv + static uint32 GetExternalImageDataSize(DownloadedDataBase* _this) + { + if (!TestFlags(_this, FLAGS::HAS_EXTERNAL_IMAGE)) + return 0; + return _this->externalImageDataSize; + } + + // todo: + // DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi (implement downloading) // DownloadExternalBinaryData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi // GetExternalBinaryDataSize__Q3_2nn3olv18DownloadedDataBaseCFv }; @@ -425,6 +439,174 @@ namespace nn static_assert(sizeof(DownloadedSystemTopicDataList) == 0xC1000); } + + struct DownloadPostDataListParam + { + static constexpr size_t MAX_NUM_SEARCH_PID = 12; + static constexpr size_t MAX_NUM_SEARCH_KEY = 5; + static constexpr size_t MAX_NUM_POST_ID = 20; + + enum class FLAGS + { + FRIENDS_ONLY = 0x01, // friends only + FOLLOWERS_ONLY = 0x02, // followers only + SELF_ONLY = 0x04, // self only + ONLY_TYPE_TEXT = 0x08, + ONLY_TYPE_MEMO = 0x10, + UKN_20 = 0x20, + WITH_MII = 0x40, // with mii + WITH_EMPATHY = 0x80, // with yeahs added + UKN_100 = 0x100, + UKN_200 = 0x200, // "is_delay" parameter + UKN_400 = 0x400, // "is_hot" parameter + + + }; + + struct SearchKey + { + uint16be str[152]; + }; + + struct PostId + { + char str[32]; + }; + + betype flags; + uint32be communityId; + uint32be searchPid[MAX_NUM_SEARCH_PID]; + uint8 languageId; + uint8 hasLanguageId_039; + uint8 padding03A[2]; + uint32be postDataMaxNum; + SearchKey searchKeyArray[MAX_NUM_SEARCH_KEY]; + PostId searchPostId[MAX_NUM_POST_ID]; + uint64be postDate; // OSTime? + uint64be titleId; // only used by System posts? + uint32be bodyTextMaxLength; + uint8 padding8C4[1852]; + + bool _HasFlag(FLAGS flag) + { + return ((uint32)flags.value() & (uint32)flag) != 0; + } + + void _SetFlags(FLAGS flag) + { + flags = (FLAGS)((uint32)flags.value() | (uint32)flag); + } + + // constructor and getters + // __ct__Q3_2nn3olv25DownloadPostDataListParamFv + static DownloadPostDataListParam* Construct(DownloadPostDataListParam* _this) + { + memset(_this, 0, sizeof(DownloadPostDataListParam)); + return _this; + } + + // SetFlags__Q3_2nn3olv25DownloadPostDataListParamFUi + static nnResult SetFlags(DownloadPostDataListParam* _this, FLAGS flags) + { + // todo - verify flag combos + _this->flags = flags; + return OLV_RESULT_SUCCESS; + } + + // SetLanguageId__Q3_2nn3olv25DownloadPostDataListParamFUc + static nnResult SetLanguageId(DownloadPostDataListParam* _this, uint8 languageId) + { + _this->languageId = languageId; + _this->hasLanguageId_039 = 1; + return OLV_RESULT_SUCCESS; + } + + // SetCommunityId__Q3_2nn3olv25DownloadPostDataListParamFUi + static nnResult SetCommunityId(DownloadPostDataListParam* _this, uint32 communityId) + { + _this->communityId = communityId; + return OLV_RESULT_SUCCESS; + } + + // SetSearchKey__Q3_2nn3olv25DownloadPostDataListParamFPCwUc + static nnResult SetSearchKey(DownloadPostDataListParam* _this, const uint16be* searchKey, uint8 searchKeyIndex) + { + if (searchKeyIndex >= MAX_NUM_SEARCH_KEY) + return OLV_RESULT_INVALID_PARAMETER; + memset(&_this->searchKeyArray[searchKeyIndex], 0, sizeof(SearchKey)); + if(olv_wstrnlen((const char16_t*)searchKey, 152) > 50) + { + cemuLog_log(LogType::Force, "DownloadPostDataListParam::SetSearchKey: searchKey is too long\n"); + return OLV_RESULT_INVALID_PARAMETER; + } + SetStringUC2(_this->searchKeyArray[searchKeyIndex].str, searchKey); + return OLV_RESULT_SUCCESS; + } + + // SetSearchKey__Q3_2nn3olv25DownloadPostDataListParamFPCw + static nnResult SetSearchKeySingle(DownloadPostDataListParam* _this, const uint16be* searchKey) + { + return SetSearchKey(_this, searchKey, 0); + } + + // SetSearchPid__Q3_2nn3olv25DownloadPostDataListParamFUi + static nnResult SetSearchPid(DownloadPostDataListParam* _this, uint32 searchPid) + { + if(_this->_HasFlag(FLAGS::FRIENDS_ONLY) || _this->_HasFlag(FLAGS::FOLLOWERS_ONLY) || _this->_HasFlag(FLAGS::SELF_ONLY)) + return OLV_RESULT_INVALID_PARAMETER; + _this->searchPid[0] = searchPid; + return OLV_RESULT_SUCCESS; + } + + // SetPostId__Q3_2nn3olv25DownloadPostDataListParamFPCcUi + static nnResult SetPostId(DownloadPostDataListParam* _this, const char* postId, uint32 postIdIndex) + { + if (postIdIndex >= MAX_NUM_POST_ID) + return OLV_RESULT_INVALID_PARAMETER; + memset(&_this->searchPostId[postIdIndex], 0, sizeof(PostId)); + if (strlen(postId) > 22) + { + cemuLog_log(LogType::Force, "DownloadPostDataListParam::SetPostId: postId is too long\n"); + return OLV_RESULT_INVALID_PARAMETER; + } + strcpy(_this->searchPostId[postIdIndex].str, postId); + return OLV_RESULT_SUCCESS; + } + + // SetPostDate__Q3_2nn3olv25DownloadPostDataListParamFL + static nnResult SetPostDate(DownloadPostDataListParam* _this, uint64 postDate) + { + _this->postDate = postDate; + return OLV_RESULT_SUCCESS; + } + + // SetPostDataMaxNum__Q3_2nn3olv25DownloadPostDataListParamFUi + static nnResult SetPostDataMaxNum(DownloadPostDataListParam* _this, uint32 postDataMaxNum) + { + if(postDataMaxNum == 0) + return OLV_RESULT_INVALID_PARAMETER; + _this->postDataMaxNum = postDataMaxNum; + return OLV_RESULT_SUCCESS; + } + + // SetBodyTextMaxLength__Q3_2nn3olv25DownloadPostDataListParamFUi + static nnResult SetBodyTextMaxLength(DownloadPostDataListParam* _this, uint32 bodyTextMaxLength) + { + if(bodyTextMaxLength >= 256) + return OLV_RESULT_INVALID_PARAMETER; + _this->bodyTextMaxLength = bodyTextMaxLength; + return OLV_RESULT_SUCCESS; + } + + // GetRawDataUrl__Q3_2nn3olv25DownloadPostDataListParamCFPcUi + static nnResult GetRawDataUrl(DownloadPostDataListParam* _this, char* urlOut, uint32 urlMaxSize); + }; + + static_assert(sizeof(DownloadPostDataListParam) == 0x1000); + + // parsing functions + bool ParseXML_DownloadedPostData(DownloadedPostData& obj, pugi::xml_node& xmlNode); + void loadOlivePostAndTopicTypes(); } } \ No newline at end of file diff --git a/src/Cafe/TitleList/TitleList.cpp b/src/Cafe/TitleList/TitleList.cpp index 7f42d17c..2e50cbf9 100644 --- a/src/Cafe/TitleList/TitleList.cpp +++ b/src/Cafe/TitleList/TitleList.cpp @@ -9,7 +9,7 @@ bool sTLInitialized{ false }; fs::path sTLCacheFilePath; // lists for tracking known titles -// note: The list may only contain titles with valid meta data. Entries loaded from the cache may not have been parsed yet, but they will use a cached value for titleId and titleVersion +// note: The list may only contain titles with valid meta data (except for certain system titles). Entries loaded from the cache may not have been parsed yet, but they will use a cached value for titleId and titleVersion std::mutex sTLMutex; std::vector sTLList; std::vector sTLListPending; diff --git a/src/Common/precompiled.h b/src/Common/precompiled.h index 56f31a03..7152f2c1 100644 --- a/src/Common/precompiled.h +++ b/src/Common/precompiled.h @@ -485,6 +485,14 @@ bool future_is_ready(std::future& f) #endif } +// replace with std::scope_exit once available +struct scope_exit +{ + std::function f_; + explicit scope_exit(std::function f) noexcept : f_(std::move(f)) {} + ~scope_exit() { if (f_) f_(); } +}; + // helper function to cast raw pointers to std::atomic // this is technically not legal but works on most platforms as long as alignment restrictions are met and the implementation of atomic doesnt come with additional members