/*
Copyright (C) 2020-2021 Reiko Asakura. All Rights Reserved.
Moonshine
*/
#include <kernel/libkernel.h>
#include <kernel/modulemgr.h>
#include <libdbg.h>
#include <taihen.h>
#include "config.h"
#include "opcode.h"
#include "patch.h"
#define SYSTEM_TITLE_ID_PREFIX "NPXS"
#define RETAIL_TITLE_ID_PREFIX "PCS"
#define TITLE_INFO_PREFIX "{\"npTitleId\":\""
#define TITLE_INFO_PREFIX_LEN (sizeof(TITLE_INFO_PREFIX) - 1)
#define TITLE_INFO_LEN_BOUND (TITLE_INFO_PREFIX_LEN + 4)
#define STARTSWITH(s, t) (sceClibMemcmp(s, t, sizeof(t) - 1) == 0)
static SceUID SceShell_uid;
static uint32_t sceNpWebApiSendRequest_ofs;
static SceUID set_presence_hook_id = -1;
static tai_hook_ref_t set_presence_hook_ref;
static SceUID sceNpWebApiSendRequest_hook_id = -1;
static tai_hook_ref_t sceNpWebApiSendRequest_hook_ref;
static SceUID post_status_hook_id = -1;
static tai_hook_ref_t post_status_hook_ref;
static MoonshineConfig config;
static int32_t sceNpWebApiSendRequest_hook(int64_t requestId, const void *pData, size_t dataSize) {
if (dataSize >= TITLE_INFO_LEN_BOUND && STARTSWITH(pData, TITLE_INFO_PREFIX)) {
const char *title_id = ((const char *)pData) + TITLE_INFO_PREFIX_LEN;
config_read(&config);
if (config.block_presence == MOONSHINE_CONFIG_BLOCK_NONE
|| (config.block_presence == MOONSHINE_CONFIG_BLOCK_HOMEBREW
&& (STARTSWITH(title_id, SYSTEM_TITLE_ID_PREFIX)
|| STARTSWITH(title_id, RETAIL_TITLE_ID_PREFIX))))
{
SCE_DBG_LOG_INFO("Request permitted: %.*s", dataSize, pData);
} else {
SCE_DBG_LOG_INFO("Request blocked: %.*s", dataSize, pData);
return -1;
}
}
return HOOK_NEXT(sceNpWebApiSendRequest, requestId, pData, dataSize);
}
static int set_presence_hook(int r0, int r1) {
if (sceNpWebApiSendRequest_hook_id < 0) {
HOOK_OFFSET(SceShell_uid, 0, sceNpWebApiSendRequest_ofs, 0, sceNpWebApiSendRequest);
}
int ret = HOOK_NEXT(set_presence, r0, r1);
UNHOOK(set_presence);
return ret;
}
static int post_status_hook(char **r0) {
char *title_id = r0[21];
config_read(&config);
if (config.block_status == MOONSHINE_CONFIG_BLOCK_NONE
|| (config.block_status == MOONSHINE_CONFIG_BLOCK_HOMEBREW
&& (STARTSWITH(title_id, SYSTEM_TITLE_ID_PREFIX)
|| STARTSWITH(title_id, RETAIL_TITLE_ID_PREFIX))))
{
SCE_DBG_LOG_INFO("Post status permitted: %s", title_id);
} else {
SCE_DBG_LOG_INFO("Post status blocked: %s", title_id);
return -1;
}
return HOOK_NEXT(post_status, r0);
}
static void cleanup(void) {
UNHOOK(set_presence);
UNHOOK(sceNpWebApiSendRequest);
UNHOOK(post_status);
}
int module_start() {
int ret;
tai_module_info_t minfo;
SceKernelModuleInfo sce_minfo;
uint32_t SceShell_dbg_fingerprint;
uint16_t *SceShell_seg0;
uint32_t set_presence_ofs;
uint32_t post_status_ofs;
uint16_t *sceNpWebApiSendRequest_addr;
config_init(&config);
sceClibMemset(&minfo, 0, sizeof(minfo));
minfo.size = sizeof(minfo);
ret = taiGetModuleInfo("SceShell", &minfo);
if (ret < 0) {
SCE_DBG_LOG_ERROR("Failed to get SceShell taiHEN module info %08X", ret);
goto fail;
}
SceShell_uid = minfo.modid;
SceShell_dbg_fingerprint = minfo.module_nid;
sceClibMemset(&sce_minfo, 0, sizeof(sce_minfo));
sce_minfo.size = sizeof(sce_minfo);
ret = sceKernelGetModuleInfo(SceShell_uid, &sce_minfo);
if (ret < 0) {
SCE_DBG_LOG_ERROR("Failed to get SceShell module info %08X", ret);
goto fail;
}
SceShell_seg0 = (uint16_t *)sce_minfo.segments[0].vaddr;
switch(SceShell_dbg_fingerprint) {
case 0x0552F692: // 3.60 retail
case 0x532155E5: // 3.61 retail
set_presence_ofs = 0x3B9EEE;
post_status_ofs = 0x1BDE54;
break;
case 0xBB4B0A3E: // 3.63 retail
set_presence_ofs = 0x3B9F8E;
post_status_ofs = 0x1BDF1C;
break;
case 0x5549BF1F: // 3.65 retail
case 0x34B4D82E: // 3.67 retail
case 0x12DAC0F3: // 3.68 retail
set_presence_ofs = 0x3BA336;
post_status_ofs = 0x1BDF1C;
break;
case 0x0703C828: // 3.69 retail
case 0x2053B5A5: // 3.70 retail
case 0xF476E785: // 3.71 retail
case 0x939FFBE9: // 3.72 retail
case 0x734D476A: // 3.73 retail
set_presence_ofs = 0x3BA346;
post_status_ofs = 0x1BDF1C;
break;
case 0xEAB89D5C: // 3.60 testkit
set_presence_ofs = 0x3B066E;
post_status_ofs = 0x1B6288;
break;
case 0x587F9CED: // 3.65 testkit
set_presence_ofs = 0x3B0AAE;
post_status_ofs = 0x1B6350;
break;
default:
SCE_DBG_LOG_ERROR("Unsupported SceShell version");
goto fail;
}
SCE_DBG_LOG_INFO("set_presence at offset %p", set_presence_ofs);
SCE_DBG_LOG_INFO("post_status at offset %p", post_status_ofs);
if (get_addr_blx(SceShell_seg0 + set_presence_ofs/2 + 0x254/2, &sceNpWebApiSendRequest_addr) < 0) {
goto fail;
}
sceNpWebApiSendRequest_ofs = (uint32_t)sceNpWebApiSendRequest_addr - (uint32_t)SceShell_seg0;
SCE_DBG_LOG_INFO("sceNpWebApiSendRequest at offset %p", sceNpWebApiSendRequest_ofs);
if (HOOK_OFFSET(SceShell_uid, 0, set_presence_ofs, 1, set_presence) < 0) {
goto fail;
}
if (HOOK_OFFSET(SceShell_uid, 0, post_status_ofs, 1, post_status) < 0) {
goto fail;
}
return SCE_KERNEL_START_SUCCESS;
fail:
cleanup();
return SCE_KERNEL_START_FAILED;
}
int module_stop() {
cleanup();
return SCE_KERNEL_STOP_SUCCESS;
}