mirror of
https://github.com/cemu-project/Cemu.git
synced 2024-11-25 10:36:55 +01:00
nn_olv: More work on post API
This commit is contained in:
parent
67819a68d9
commit
0d96255bae
@ -422,6 +422,8 @@ add_library(CemuCafe
|
|||||||
OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h
|
OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h
|
||||||
OS/libs/nn_olv/nn_olv_PostTypes.cpp
|
OS/libs/nn_olv/nn_olv_PostTypes.cpp
|
||||||
OS/libs/nn_olv/nn_olv_PostTypes.h
|
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.cpp
|
||||||
OS/libs/nn_pdm/nn_pdm.h
|
OS/libs/nn_pdm/nn_pdm.h
|
||||||
OS/libs/nn_save/nn_save.cpp
|
OS/libs/nn_save/nn_save.cpp
|
||||||
|
@ -546,6 +546,7 @@ void coreinitExport_UCReadSysConfig(PPCInterpreter_t* hCPU)
|
|||||||
{
|
{
|
||||||
// get parental online control for online features
|
// 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
|
// 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
|
// account in slot 1
|
||||||
if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL))
|
if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL))
|
||||||
memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // data type is guessed
|
memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // data type is guessed
|
||||||
@ -561,7 +562,7 @@ void coreinitExport_UCReadSysConfig(PPCInterpreter_t* hCPU)
|
|||||||
{
|
{
|
||||||
// miiverse restrictions
|
// miiverse restrictions
|
||||||
if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL))
|
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)
|
else if (_strcmpi(ucParam->settingName, "s_acct01.uuid") == 0)
|
||||||
{
|
{
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include "nn_olv_DownloadCommunityTypes.h"
|
#include "nn_olv_DownloadCommunityTypes.h"
|
||||||
#include "nn_olv_UploadFavoriteTypes.h"
|
#include "nn_olv_UploadFavoriteTypes.h"
|
||||||
#include "nn_olv_PostTypes.h"
|
#include "nn_olv_PostTypes.h"
|
||||||
|
#include "nn_olv_OfflineDB.h"
|
||||||
|
|
||||||
#include "Cafe/OS/libs/proc_ui/proc_ui.h"
|
#include "Cafe/OS/libs/proc_ui/proc_ui.h"
|
||||||
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
|
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
|
||||||
@ -13,179 +14,6 @@ namespace nn
|
|||||||
{
|
{
|
||||||
namespace olv
|
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
|
struct PortalAppParam_t
|
||||||
{
|
{
|
||||||
/* +0x1A663B */ char serviceToken[32]; // size is unknown
|
/* +0x1A663B */ char serviceToken[32]; // size is unknown
|
||||||
@ -284,6 +112,10 @@ namespace nn
|
|||||||
|
|
||||||
void load()
|
void load()
|
||||||
{
|
{
|
||||||
|
g_ReportTypes = 0;
|
||||||
|
g_IsOnlineMode = false;
|
||||||
|
g_IsInitialized = false;
|
||||||
|
g_IsOfflineDBMode = false;
|
||||||
|
|
||||||
loadOliveInitializeTypes();
|
loadOliveInitializeTypes();
|
||||||
loadOliveUploadCommunityTypes();
|
loadOliveUploadCommunityTypes();
|
||||||
@ -293,13 +125,6 @@ namespace nn
|
|||||||
|
|
||||||
cafeExportRegisterFunc(GetErrorCode, "nn_olv", "GetErrorCode__Q2_2nn3olvFRCQ2_2nn6Result", LogType::None);
|
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);
|
osLib_addFunction("nn_olv", "GetServiceToken__Q4_2nn3olv6hidden14PortalAppParamCFv", exportPortalAppParam_GetServiceToken);
|
||||||
|
|
||||||
cafeExportRegisterFunc(StubPostApp, "nn_olv", "UploadPostDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv28UploadPostDataByPostAppParam", LogType::Force);
|
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);
|
cafeExportRegisterFunc(UploadedPostData_GetPostId, "nn_olv", "GetPostId__Q3_2nn3olv16UploadedPostDataCFv", LogType::Force);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void unload() // not called yet
|
||||||
|
{
|
||||||
|
OfflineDB_Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -19,5 +19,6 @@ namespace nn
|
|||||||
sint32 GetOlvAccessKey(uint32_t* pOutKey);
|
sint32 GetOlvAccessKey(uint32_t* pOutKey);
|
||||||
|
|
||||||
void load();
|
void load();
|
||||||
|
void unload();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -69,6 +69,7 @@ namespace nn
|
|||||||
extern uint32_t g_ReportTypes;
|
extern uint32_t g_ReportTypes;
|
||||||
extern bool g_IsInitialized;
|
extern bool g_IsInitialized;
|
||||||
extern bool g_IsOnlineMode;
|
extern bool g_IsOnlineMode;
|
||||||
|
extern bool g_IsOfflineDBMode; // use offline cache for posts
|
||||||
|
|
||||||
static void InitializeOliveRequest(CurlRequestHelper& req)
|
static void InitializeOliveRequest(CurlRequestHelper& req)
|
||||||
{
|
{
|
||||||
@ -175,5 +176,39 @@ namespace nn
|
|||||||
bool FormatCommunityCode(char* pOutCode, uint32* outLen, uint32 communityId);
|
bool FormatCommunityCode(char* pOutCode, uint32* outLen, uint32 communityId);
|
||||||
|
|
||||||
sint32 olv_curlformcode_to_error(CURLFORMcode code);
|
sint32 olv_curlformcode_to_error(CURLFORMcode code);
|
||||||
|
|
||||||
|
// convert and copy utf8 string into UC2 big-endian array
|
||||||
|
template<size_t TLength>
|
||||||
|
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<size_t>(TLength-1, ws.size());
|
||||||
|
for(size_t i=0; i<copyLen; i++)
|
||||||
|
str[i] = ws[i];
|
||||||
|
str[copyLen] = '\0';
|
||||||
|
return copyLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// safely copy null-terminated UC2 big-endian string into UC2 big-endian array
|
||||||
|
template<size_t TLength>
|
||||||
|
uint32 SetStringUC2(uint16be(&str)[TLength], const uint16be* strIn)
|
||||||
|
{
|
||||||
|
size_t copyLen = TLength-1;
|
||||||
|
for(size_t i=0; i<copyLen; i++)
|
||||||
|
{
|
||||||
|
if(strIn[i] == 0)
|
||||||
|
{
|
||||||
|
str[i] = 0;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
str[i] = strIn[i];
|
||||||
|
}
|
||||||
|
str[copyLen] = '\0';
|
||||||
|
return copyLen;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,10 @@ namespace nn
|
|||||||
{
|
{
|
||||||
namespace olv
|
namespace olv
|
||||||
{
|
{
|
||||||
|
|
||||||
uint32_t g_ReportTypes = 0;
|
uint32_t g_ReportTypes = 0;
|
||||||
bool g_IsOnlineMode = false;
|
bool g_IsOnlineMode = false;
|
||||||
bool g_IsInitialized = false;
|
bool g_IsInitialized = false;
|
||||||
|
bool g_IsOfflineDBMode = false;
|
||||||
ParamPackStorage g_ParamPack;
|
ParamPackStorage g_ParamPack;
|
||||||
DiscoveryResultStorage g_DiscoveryResults;
|
DiscoveryResultStorage g_DiscoveryResults;
|
||||||
|
|
||||||
@ -241,9 +241,15 @@ namespace nn
|
|||||||
|
|
||||||
g_IsInitialized = true;
|
g_IsInitialized = true;
|
||||||
|
|
||||||
|
if(ActiveSettings::GetNetworkService() == NetworkService::Nintendo)
|
||||||
|
{
|
||||||
|
// since the official Miiverse was shut down, use local post archive instead
|
||||||
|
g_IsOnlineMode = true;
|
||||||
|
g_IsOfflineDBMode = true;
|
||||||
|
return OLV_RESULT_SUCCESS;
|
||||||
|
}
|
||||||
if ((pParam->m_Flags & InitializeParam::FLAG_OFFLINE_MODE) == 0)
|
if ((pParam->m_Flags & InitializeParam::FLAG_OFFLINE_MODE) == 0)
|
||||||
{
|
{
|
||||||
|
|
||||||
g_IsOnlineMode = true;
|
g_IsOnlineMode = true;
|
||||||
|
|
||||||
independentServiceToken_t token;
|
independentServiceToken_t token;
|
||||||
|
215
src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp
Normal file
215
src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp
Normal file
@ -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 <pugixml.hpp>
|
||||||
|
#include <zlib.h>
|
||||||
|
#include <zarchive/zarchivereader.h>
|
||||||
|
|
||||||
|
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<uint8>& 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<uint8> bitmaskCompressedImg;
|
||||||
|
if (!LoadOfflineDBFile(memoImageFilename.c_str(), bitmaskCompressedImg))
|
||||||
|
return;
|
||||||
|
if (bitmaskCompressedImg.size() != (320*120)/8)
|
||||||
|
return;
|
||||||
|
std::vector<uint8> 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<uint8> 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<pugi::xml_node> 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<coreinit::OSEvent> 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<uint8> 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<coreinit::OSEvent> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
16
src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.h
Normal file
16
src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.h
Normal file
@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
#include "Cafe/OS/libs/nn_olv/nn_olv_Common.h"
|
#include "Cafe/OS/libs/nn_olv/nn_olv_Common.h"
|
||||||
#include "nn_olv_PostTypes.h"
|
#include "nn_olv_PostTypes.h"
|
||||||
|
#include "nn_olv_OfflineDB.h"
|
||||||
#include "Cemu/ncrypto/ncrypto.h" // for base64 decoder
|
#include "Cemu/ncrypto/ncrypto.h" // for base64 decoder
|
||||||
#include "util/helpers/helpers.h"
|
#include "util/helpers/helpers.h"
|
||||||
#include <pugixml.hpp>
|
#include <pugixml.hpp>
|
||||||
@ -9,41 +10,28 @@ namespace nn
|
|||||||
{
|
{
|
||||||
namespace olv
|
namespace olv
|
||||||
{
|
{
|
||||||
|
|
||||||
template<size_t TLength>
|
|
||||||
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<size_t>(TLength-1, ws.size());
|
|
||||||
for(size_t i=0; i<copyLen; i++)
|
|
||||||
str[i] = ws[i];
|
|
||||||
str[copyLen] = '\0';
|
|
||||||
return copyLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ParseXml_DownloadedDataBase(DownloadedDataBase& obj, pugi::xml_node& xmlNode)
|
bool ParseXml_DownloadedDataBase(DownloadedDataBase& obj, pugi::xml_node& xmlNode)
|
||||||
{
|
{
|
||||||
// todo:
|
// todo:
|
||||||
// app_data, body, painting, name
|
// painting with url?
|
||||||
|
|
||||||
pugi::xml_node tokenNode;
|
pugi::xml_node tokenNode;
|
||||||
if(tokenNode = xmlNode.child("body"); tokenNode)
|
if(tokenNode = xmlNode.child("body"); tokenNode)
|
||||||
{
|
{
|
||||||
//cemu_assert_unimplemented();
|
|
||||||
obj.bodyTextLength = SetStringUC2(obj.bodyText, tokenNode.child_value(), true);
|
obj.bodyTextLength = SetStringUC2(obj.bodyText, tokenNode.child_value(), true);
|
||||||
if(obj.bodyTextLength > 0)
|
if(obj.bodyTextLength > 0)
|
||||||
obj.SetFlag(DownloadedDataBase::FLAGS::HAS_BODY_TEXT);
|
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)
|
if(tokenNode = xmlNode.child("feeling_id"); tokenNode)
|
||||||
{
|
{
|
||||||
obj.feeling = ConvertString<sint8>(tokenNode.child_value());
|
obj.feeling = ConvertString<sint8>(tokenNode.child_value());
|
||||||
if(obj.feeling < 0 || obj.feeling >= 5)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,7 +40,7 @@ namespace nn
|
|||||||
std::string_view id_sv = tokenNode.child_value();
|
std::string_view id_sv = tokenNode.child_value();
|
||||||
if(id_sv.size() > 22)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
memcpy(obj.postId, id_sv.data(), id_sv.size());
|
memcpy(obj.postId, id_sv.data(), id_sv.size());
|
||||||
@ -67,7 +55,7 @@ namespace nn
|
|||||||
obj.SetFlag(DownloadedDataBase::FLAGS::IS_NOT_AUTOPOST);
|
obj.SetFlag(DownloadedDataBase::FLAGS::IS_NOT_AUTOPOST);
|
||||||
else
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,6 +104,36 @@ namespace nn
|
|||||||
{
|
{
|
||||||
obj.countryId = ConvertString<uint8>(tokenNode.child_value());
|
obj.countryId = ConvertString<uint8>(tokenNode.child_value());
|
||||||
}
|
}
|
||||||
|
if(tokenNode = xmlNode.child("painting"); tokenNode)
|
||||||
|
{
|
||||||
|
if(pugi::xml_node subNode = tokenNode.child("content"); subNode)
|
||||||
|
{
|
||||||
|
std::vector<uint8> 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<uint32>(subNode.child_value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(tokenNode = xmlNode.child("app_data"); tokenNode)
|
||||||
|
{
|
||||||
|
std::vector<uint8> 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,6 +274,121 @@ namespace nn
|
|||||||
return 0;
|
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()
|
void loadOlivePostAndTopicTypes()
|
||||||
{
|
{
|
||||||
cafeExportRegisterFunc(GetSystemTopicDataListFromRawData, "nn_olv", "GetSystemTopicDataListFromRawData__Q3_2nn3olv6hiddenFPQ4_2nn3olv6hidden29DownloadedSystemTopicDataListPQ4_2nn3olv6hidden24DownloadedSystemPostDataPUiUiPCUcT4", LogType::None);
|
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::GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::None);
|
||||||
cafeExportRegisterFunc(DownloadedDataBase::GetPostId, "nn_olv", "GetPostId__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::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
|
// DownloadedPostData getters
|
||||||
cafeExportRegisterFunc(DownloadedPostData::GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv18DownloadedPostDataCFv", LogType::None);
|
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::GetDownloadedSystemTopicData, "nn_olv", "GetDownloadedSystemTopicData__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFi", LogType::None);
|
||||||
cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemPostData, "nn_olv", "GetDownloadedSystemPostData__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFiT1", 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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
|
#include "nn_olv_Common.h"
|
||||||
|
|
||||||
namespace nn
|
namespace nn
|
||||||
{
|
{
|
||||||
@ -154,8 +155,11 @@ namespace nn
|
|||||||
return OLV_RESULT_INVALID_PTR;
|
return OLV_RESULT_INVALID_PTR;
|
||||||
if (maxLength == 0)
|
if (maxLength == 0)
|
||||||
return OLV_RESULT_NOT_ENOUGH_SIZE;
|
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<uint32>(_this->bodyTextLength, maxLength);
|
uint32 outputLength = std::min<uint32>(_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;
|
return OLV_RESULT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,9 +217,19 @@ namespace nn
|
|||||||
return _this->postId;
|
return _this->postId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo:
|
|
||||||
// DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi
|
// DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi
|
||||||
|
static nnResult DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize);
|
||||||
|
|
||||||
// GetExternalImageDataSize__Q3_2nn3olv18DownloadedDataBaseCFv
|
// 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
|
// DownloadExternalBinaryData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi
|
||||||
// GetExternalBinaryDataSize__Q3_2nn3olv18DownloadedDataBaseCFv
|
// GetExternalBinaryDataSize__Q3_2nn3olv18DownloadedDataBaseCFv
|
||||||
};
|
};
|
||||||
@ -425,6 +439,174 @@ namespace nn
|
|||||||
static_assert(sizeof(DownloadedSystemTopicDataList) == 0xC1000);
|
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> 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();
|
void loadOlivePostAndTopicTypes();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,7 +9,7 @@ bool sTLInitialized{ false };
|
|||||||
fs::path sTLCacheFilePath;
|
fs::path sTLCacheFilePath;
|
||||||
|
|
||||||
// lists for tracking known titles
|
// 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::mutex sTLMutex;
|
||||||
std::vector<TitleInfo*> sTLList;
|
std::vector<TitleInfo*> sTLList;
|
||||||
std::vector<TitleInfo*> sTLListPending;
|
std::vector<TitleInfo*> sTLListPending;
|
||||||
|
@ -485,6 +485,14 @@ bool future_is_ready(std::future<T>& f)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// replace with std::scope_exit once available
|
||||||
|
struct scope_exit
|
||||||
|
{
|
||||||
|
std::function<void()> f_;
|
||||||
|
explicit scope_exit(std::function<void()> f) noexcept : f_(std::move(f)) {}
|
||||||
|
~scope_exit() { if (f_) f_(); }
|
||||||
|
};
|
||||||
|
|
||||||
// helper function to cast raw pointers to std::atomic
|
// 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
|
// 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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user