olv/hle: Functioning Miiverse applet (#1747)

This commit is contained in:
Arian K. 2025-12-19 14:53:09 -05:00 committed by GitHub
parent 42da9712a9
commit 7dd0b90f53
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 138 additions and 19 deletions

View file

@ -524,6 +524,10 @@ void Account::ParseFile(class FileStream* file)
m_country = ConvertString<uint32>(value, 16);
else if (key == "SimpleAddressId")
m_simple_address_id = ConvertString<uint32>(value, 16);
else if (key == "TimeZoneId")
m_timezone_id = value;
else if (key == "UtcOffset")
m_utc_offset = ConvertString<uint64>(value, 16);
else if (key == "PrincipalId")
m_principal_id = ConvertString<uint32>(value, 16);
else if (key == "IsPasswordCacheEnabled")

View file

@ -77,6 +77,8 @@ public:
[[nodiscard]] std::string_view GetEmail() const { return m_email; }
[[nodiscard]] uint32 GetCountry() const { return m_country; }
[[nodiscard]] uint32 GetSimpleAddressId() const { return m_simple_address_id; }
[[nodiscard]] std::string_view GetTimeZoneId() const { return m_timezone_id; }
[[nodiscard]] sint64 GetUtcOffset() const { return m_utc_offset; }
[[nodiscard]] uint32 GetPrincipalId() const { return m_principal_id; }
[[nodiscard]] bool IsPasswordCacheEnabled() const { return m_password_cache_enabled != 0; }
[[nodiscard]] const std::array<uint8, 32>& GetAccountPasswordCache() const { return m_account_password_cache; }
@ -90,6 +92,8 @@ public:
void SetGender(uint8 gender) { m_gender = gender; }
void SetEmail(std::string_view email) { m_email = email; }
void SetCountry(uint32 country) { m_country = country; }
void SetTimeZoneId(std::string_view timezone_id) { m_timezone_id = timezone_id; }
void SetUtcOffset(sint64 utc_offset) { m_utc_offset = utc_offset; }
// this will always return at least one account (default one)
static const std::vector<Account>& RefreshAccounts();
@ -123,6 +127,8 @@ private:
std::string m_email;
uint32 m_country = 0;
uint32 m_simple_address_id = 0;
std::string m_timezone_id;
sint64 m_utc_offset;
uint32 m_principal_id = 0;
uint8 m_password_cache_enabled = 0;
std::array<uint8, 32> m_account_password_cache{};

View file

@ -2,6 +2,7 @@
#include "Cafe/OS/RPL/rpl.h"
#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h"
#include "CafeSystem.h"
#include "config/ActiveSettings.h" // Selectively add some patches based on network settings.
void hleExport_breathOfTheWild_busyLoop(PPCInterpreter_t* hCPU)
{
@ -268,6 +269,16 @@ static_assert(sizeof(bayo2_audioQueueFixSignature) == sizeof(bayo2_audioQueueFix
uint8 cars3_avro_schema_incref[] = { 0x2C,0x03,0x00,0x00,0x94,0x21,0xFF,0xE8,0x41,0x82,0x00,0x40,0x39,0x03,0x00,0x08,0x39,0x41,0x00,0x08,0x91,0x01,0x00,0x08,0x7D,0x80,0x50,0x28,0x2C,0x0C,0xFF,0xFF,0x41,0x82,0x00,0x28,0x39,0x21,0x00,0x0C,0x38,0x0C,0x00,0x01,0x38,0xE0,0x00,0x01,0x91,0x01,0x00,0x0C,0x7C,0x00,0x49,0x2D };
// For USA titles: 000500301001610a / 000500301001410a
uint8 miiverse_eshop_url_match_whitelist_func[] = {
// For both titles, the code fully matches with the relative
// branch targets, even if the absolute targets are different.
0x89,0x45,0x00,0x00, // lbz r10, 0x0(r5)
0x2c,0x0a,0x00,0x2e, // cmpwi r10, 0x2e
0x40,0x82,0x00,0x08, // bne LAB_020ff3a8
0x4b,0xff,0xff,0x5c // b FUN_020ff300
};
uint8 wave_libopenssl_ssl_verify_cert_chain[] = { 0x94,0x21,0xff,0x58,0x93,0xc1,0x00,0xa0,0x93,0xe1,0x00,0xa4,0x7c,0x9f,0x23,0x79,0x7c,0x7e,0x1b,0x78,0x90,0x01,0x00,0xac,0x41,0x82,0x00,0x14,0x7f,0xe3,0xfb,0x78 }; // rpl function for the above applets
sint32 hleIndex_h000000001 = -1;
sint32 hleIndex_h000000002 = -1;
@ -461,6 +472,36 @@ void GamePatch_scan()
memory_writeU32(hleAddr + 0x64, 0x60000000);
}
// Patch function in Miiverse/eShop wave.rpx that matches
// a domain against another to validate its whitelist.
// This allows those browsers to load any domain.
const NetworkService service = ActiveSettings::GetNetworkService();
if (service != NetworkService::Nintendo // Only patch for custom services.
&& CafeSystem::GetForegroundTitleArgStr().ends_with("wave.rpx")
&& (hleAddr = hle_locate(miiverse_eshop_url_match_whitelist_func,
nullptr, sizeof(miiverse_eshop_url_match_whitelist_func))))
{
cemuLog_log(LogType::Force, "Patching Miiverse/eShop whitelist check at: 0x{:08x}", hleAddr);
// Always return 1. (Note that the matched pattern is not at the beginning but still works)
memory_writeU32(hleAddr, 0x38600001);
memory_writeU32(hleAddr + 0x4, 0x4e800020);
// Note that the same applies to Account Settings, but
// its version of this function differs a lot.
// Search: 88 0c ff ff 2c 00 00 2e (lbz r0,-0x1(r12); cmpwi r0,0x2e)
}
// Additionally patch out SSL checks for libopenssl.rpl, used by the browser.
if (IsNetworkServiceSSLDisabled(service)
&& RPLLoader_GetHandleByModuleName("libopenssl.rpl") != RPL_INVALID_HANDLE
&& (hleAddr = hle_locate(wave_libopenssl_ssl_verify_cert_chain,
nullptr, sizeof(wave_libopenssl_ssl_verify_cert_chain))))
{
cemuLog_log(LogType::Force, "Patching OpenSSL ssl_verify_cert_chain at: 0x{:08x}", hleAddr);
// Reference: https://github.com/PretendoNetwork/Meowth/blob/meowth/src/patcher/patches/webkit_applets.cpp
memory_writeU32(hleAddr + 0x28, 0x60000000);
memory_writeU32(hleAddr + 0x40, 0x38600001);
}
uint32 hleInstallEnd = GetTickCount();
cemuLog_log(LogType::Force, "HLE scan time: {}ms", hleInstallEnd-hleInstallStart);
}

View file

@ -49,6 +49,8 @@ struct actAccountData_t
// country & language
uint32 countryIndex;
char country[8];
char timeZoneId[16];
sint64 utcOffset;
// Mii
FFLData_t miiData;
uint16le miiNickname[ACT_NICKNAME_LENGTH];
@ -84,6 +86,8 @@ void FillAccountData(const Account& account, const bool online_enabled, int inde
// country & language
data.countryIndex = account.GetCountry();
strcpy(data.country, NCrypto::GetCountryAsString(data.countryIndex));
std::copy(account.GetTimeZoneId().cbegin(), account.GetTimeZoneId().cend(), data.timeZoneId);
data.utcOffset = account.GetUtcOffset() / 1'000'000;
// Mii
std::copy(account.GetMiiData().begin(), account.GetMiiData().end(), (uint8*)&data.miiData);
std::copy(account.GetMiiName().begin(), account.GetMiiName().end(), data.miiNickname);
@ -808,6 +812,13 @@ int iosuAct_thread()
strcpy(actCemuRequest->resultString.strBuffer, _actAccountData[accountIndex].country);
actCemuRequest->setACTReturnCode(0);
}
else if (actCemuRequest->requestCode == IOSU_ARC_TIMEZONEID)
{
accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot);
_cancelIfAccountDoesNotExist();
strcpy(actCemuRequest->resultString.strBuffer, _actAccountData[accountIndex].timeZoneId);
actCemuRequest->setACTReturnCode(0);
}
else if (actCemuRequest->requestCode == IOSU_ARC_ISNETWORKACCOUNT)
{
accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot);

View file

@ -117,6 +117,7 @@ struct iosuActCemuRequest_t
#define IOSU_ARC_MIIDATA 0x0A
#define IOSU_ARC_ACQUIREINDEPENDENTTOKEN 0x0B
#define IOSU_ARC_ACQUIREPIDBYNNID 0x0C
#define IOSU_ARC_TIMEZONEID 0x0D
uint32 iosuAct_getAccountIdOfCurrentAccount();

View file

@ -15,22 +15,6 @@ namespace nn
{
namespace olv
{
struct PortalAppParam_t
{
/* +0x1A663B */ char serviceToken[32]; // size is unknown
};
void exportPortalAppParam_GetServiceToken(PPCInterpreter_t* hCPU)
{
// r3 = PortalAppParam
ppcDefineParamTypePtr(portalAppParam, PortalAppParam_t, 0);
strcpy(portalAppParam->serviceToken, "servicetoken");
// this token is probably just the act IndependentServiceToken for the Miiverse title?
osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(&portalAppParam->serviceToken));
}
static SysAllocator<OSThread_t> s_OlvReleaseBgThread;
SysAllocator<uint8, 1024> s_OlvReleaseBgThreadStack;
SysAllocator<char, 32> s_OlvReleaseBgThreadName;
@ -126,8 +110,6 @@ namespace nn
cafeExportRegisterFunc(GetErrorCode, "nn_olv", "GetErrorCode__Q2_2nn3olvFRCQ2_2nn6Result", LogType::NN_OLV);
osLib_addFunction("nn_olv", "GetServiceToken__Q4_2nn3olv6hidden14PortalAppParamCFv", exportPortalAppParam_GetServiceToken);
cafeExportRegisterFunc(StubPostApp, "nn_olv", "UploadPostDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv28UploadPostDataByPostAppParam", LogType::NN_OLV);
cafeExportRegisterFunc(StubPostApp, "nn_olv", "UploadCommentDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv31UploadCommentDataByPostAppParam", LogType::NN_OLV);
cafeExportRegisterFunc(StubPostApp, "nn_olv", "UploadDirectMessageDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv37UploadDirectMessageDataByPostAppParam", LogType::NN_OLV);

View file

@ -277,6 +277,21 @@ namespace nn
return OLV_RESULT_SUCCESS;
}
sint32 InitializePortalApp(nn::olv::PortalAppParam* pPortalAppParam, nn::olv::InitializeParam* pInitializeParam)
{
sint32 result = Initialize(pInitializeParam);
if (result != OLV_RESULT_SUCCESS)
return result;
memcpy(pPortalAppParam->m_ParamPack, g_ParamPack.encodedParamPack, sizeof(g_ParamPack.encodedParamPack));
memcpy(pPortalAppParam->m_ServiceToken, g_DiscoveryResults.serviceToken, sizeof(g_DiscoveryResults.serviceToken));
snprintf(reinterpret_cast<char*>(pPortalAppParam->m_StartUrl), sizeof(pPortalAppParam->m_StartUrl),
"%s/titles/show?src=menu", g_DiscoveryResults.portalEndpoint);
return OLV_RESULT_SUCCESS;
}
namespace Report
{
uint32 GetReportTypes()
@ -295,4 +310,4 @@ namespace nn
return g_IsInitialized;
}
}
}
}

View file

@ -101,6 +101,58 @@ namespace nn
};
static_assert(sizeof(nn::olv::InitializeParam) == 0x40, "sizeof(nn::olv::InitializeParam) != 0x40");
class PortalAppParam
{
public:
PortalAppParam()
{
m_ParamPack[0] = 0;
m_ServiceToken[0] = 0;
m_StartUrl[0] = 0;
}
static PortalAppParam* __ctor(PortalAppParam* _this)
{
if (!_this)
{
assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN
return nullptr;
}
else
return new (_this) PortalAppParam();
}
uint8be* GetParamPack()
{
return m_ParamPack;
}
static uint8be* __GetParamPack(PortalAppParam* _this)
{
return _this->GetParamPack();
}
uint8be* GetServiceToken()
{
return m_ServiceToken;
}
static uint8be* __GetServiceToken(PortalAppParam* _this)
{
return _this->GetServiceToken();
}
uint8be* GetStartUrl()
{
return m_StartUrl;
}
static uint8be* __GetStartUrl(PortalAppParam* _this)
{
return _this->GetStartUrl();
}
public:
/* +0x1A5C3C */ uint8be m_ParamPack[0x200];
/* +0x1A663B */ uint8be m_ServiceToken[0x201]; // IndependentServiceToken for Miiverse title
/* +0x1A5E3C */ uint8be m_StartUrl[0x7ff]; // https://portal-us.olv.nintendo.net/titles/show?src=menu
};
namespace Report
{
@ -110,6 +162,7 @@ namespace nn
bool IsInitialized();
sint32 Initialize(nn::olv::InitializeParam* pParam);
sint32 InitializePortalApp(nn::olv::PortalAppParam* pPortalAppParam, nn::olv::InitializeParam* pInitializeParam);
static void loadOliveInitializeTypes()
{
@ -123,6 +176,12 @@ namespace nn
cafeExportRegisterFunc(InitializeParam::__SetWork, "nn_olv", "SetWork__Q3_2nn3olv15InitializeParamFPUcUi", LogType::NN_OLV);
cafeExportRegisterFunc(InitializeParam::__SetReportTypes, "nn_olv", "SetReportTypes__Q3_2nn3olv15InitializeParamFUi", LogType::NN_OLV);
cafeExportRegisterFunc(InitializeParam::__SetSysArgs, "nn_olv", "SetSysArgs__Q3_2nn3olv15InitializeParamFPCvUi", LogType::NN_OLV);
cafeExportRegisterFunc(InitializePortalApp, "nn_olv", "InitializePortalApp__Q3_2nn3olv6hiddenFPQ4_2nn3olv6hidden14PortalAppParamPCQ3_2nn3olv15InitializeParam", LogType::NN_OLV);
cafeExportRegisterFunc(PortalAppParam::__ctor, "nn_olv", "__ct__Q4_2nn3olv6hidden14PortalAppParamFv", LogType::NN_OLV);
cafeExportRegisterFunc(PortalAppParam::__GetParamPack, "nn_olv", "GetParamPack__Q4_2nn3olv6hidden14PortalAppParamCFv", LogType::NN_OLV);
cafeExportRegisterFunc(PortalAppParam::__GetServiceToken, "nn_olv", "GetServiceToken__Q4_2nn3olv6hidden14PortalAppParamCFv", LogType::NN_OLV);
cafeExportRegisterFunc(PortalAppParam::__GetStartUrl, "nn_olv", "GetStartUrl__Q4_2nn3olv6hidden14PortalAppParamCFv", LogType::NN_OLV);
}
}
}