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_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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -19,5 +19,6 @@ namespace nn
|
||||
sint32 GetOlvAccessKey(uint32_t* pOutKey);
|
||||
|
||||
void load();
|
||||
void unload();
|
||||
}
|
||||
}
|
@ -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<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
|
||||
{
|
||||
|
||||
uint32_t g_ReportTypes = 0;
|
||||
bool g_IsOnlineMode = false;
|
||||
bool g_IsInitialized = false;
|
||||
bool g_IsOfflineDBMode = false;
|
||||
ParamPackStorage g_ParamPack;
|
||||
DiscoveryResultStorage g_DiscoveryResults;
|
||||
|
||||
@ -241,9 +241,15 @@ namespace nn
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
g_IsOnlineMode = true;
|
||||
|
||||
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 "nn_olv_PostTypes.h"
|
||||
#include "nn_olv_OfflineDB.h"
|
||||
#include "Cemu/ncrypto/ncrypto.h" // for base64 decoder
|
||||
#include "util/helpers/helpers.h"
|
||||
#include <pugixml.hpp>
|
||||
@ -9,41 +10,28 @@ namespace nn
|
||||
{
|
||||
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)
|
||||
{
|
||||
// todo:
|
||||
// app_data, body, painting, name
|
||||
// painting with url?
|
||||
|
||||
pugi::xml_node tokenNode;
|
||||
if(tokenNode = xmlNode.child("body"); tokenNode)
|
||||
{
|
||||
//cemu_assert_unimplemented();
|
||||
obj.bodyTextLength = SetStringUC2(obj.bodyText, tokenNode.child_value(), true);
|
||||
if(obj.bodyTextLength > 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<sint8>(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<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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <zlib.h>
|
||||
#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<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;
|
||||
}
|
||||
|
||||
@ -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> 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();
|
||||
}
|
||||
}
|
@ -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<TitleInfo*> sTLList;
|
||||
std::vector<TitleInfo*> sTLListPending;
|
||||
|
@ -485,6 +485,14 @@ bool future_is_ready(std::future<T>& f)
|
||||
#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
|
||||
// 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