This repository has been archived on 2022-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
Luma3DS-3GX/sysmodules/rosalina/source/plgloader.c
2018-11-15 13:38:19 +01:00

600 lines
16 KiB
C

#include <3ds.h>
#include "3gx.h"
#include "ifile.h"
#include "utils.h" // for makeARMBranch
#include "plgloader.h"
#include "fmt.h"
#include "menu.h"
#include "menus.h"
#include "memory.h"
#include "sleep.h"
#define MEMPERM_RW (MEMPERM_READ | MEMPERM_WRITE)
#define MemBlockSize (5*1024*1024) /* 5 MiB */
typedef struct
{
Result code;
const char * message;
} Error;
typedef struct
{
bool isEnabled;
bool noFlash;
u32 titleid;
char path[256];
u32 config[32];
} PluginLoadParameters;
#define HeaderMagic (0x24584733) /* "3GX$" */
typedef struct
{
u32 magic;
u32 version;
u32 heapVA;
u32 heapSize;
u32 pluginSize;
const char* pluginPathPA;
u32 isDefaultPlugin;
u32 reserved[25];
u32 config[32];
} PluginHeader;
static bool g_isEnabled;
static u8 * g_memBlock;
static char g_path[256];
static Handle g_process = 0;
static u32 g_codeSize;
static u32 g_heapSize;
static Error g_error;
static PluginLoadParameters g_userDefinedLoadParameters;
static MyThread g_pluginLoaderThread;
static u8 ALIGN(8) g_pluginLoaderThreadStack[0x4000];
// pluginLoader.s
void gamePatchFunc(void);
void PluginLoader__ThreadMain(void);
MyThread * PluginLoader__CreateThread(void)
{
s64 out;
svcGetSystemInfo(&out, 0x10000, 0x102);
g_isEnabled = out & 1;
g_userDefinedLoadParameters.isEnabled = false;
if(R_FAILED(MyThread_Create(&g_pluginLoaderThread, PluginLoader__ThreadMain, g_pluginLoaderThreadStack, 0x4000, 20, CORE_SYSTEM)))
svcBreak(USERBREAK_PANIC);
return &g_pluginLoaderThread;
}
bool PluginLoader__IsEnabled(void)
{
return g_isEnabled;
}
void PluginLoader__MenuCallback(void)
{
g_isEnabled = !g_isEnabled;
SaveSettings();
PluginLoader__UpdateMenu();
}
void PluginLoader__UpdateMenu(void)
{
static const char *status[2] =
{
"Plugin Loader: [Disabled]",
"Plugin Loader: [Enabled]"
};
rosalinaMenu.items[isN3DS + 1].title = status[g_isEnabled];
}
static Result MapPluginInProcess(Handle proc, u32 size)
{
u32 heapsize = MemBlockSize - size;
Result res;
PluginHeader *header = (PluginHeader *)g_memBlock;
header->heapVA = 0x06000000;
g_heapSize = header->heapSize = heapsize;
g_codeSize = size;
// From now on, all memory page mapped to the process should be rwx
svcControlProcess(proc, PROCESSOP_SET_MMU_TO_RWX, 0, 0);
// Plugin
if (R_FAILED((res = svcMapProcessMemoryEx(proc, 0x07000000, CUR_PROCESS_HANDLE, (u32)g_memBlock, size))))
{
g_error.message = "Couldn't map plugin memory block";
g_error.code = res;
return res;
}
// Heap (to be used by the plugin)
if (R_FAILED((res = svcMapProcessMemoryEx(proc, 0x06000000, CUR_PROCESS_HANDLE, (u32)g_memBlock + size, heapsize))))
{
g_error.message = "Couldn't map heap memory block";
g_error.code = res;
goto exit;
}
// Clear heap section
memset32(g_memBlock + size, 0, heapsize);
exit:
return res;
}
static u32 strlen16(const u16 *str)
{
u32 size = 0;
while (str && *str++) ++size;
return size;
}
static Result FindPluginFile(u64 tid)
{
char filename[256];
u32 entriesNb = 0;
bool found = false;
Handle dir = 0;
Result res;
FS_Archive sdmcArchive = 0;
FS_DirectoryEntry entries[10];
sprintf(g_path, "/luma/plugins/%016llX", tid);
if (R_FAILED((res =FSUSER_OpenArchive(&sdmcArchive, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, "")))))
goto exit;
if (R_FAILED((res = FSUSER_OpenDirectory(&dir, sdmcArchive, fsMakePath(PATH_ASCII, g_path)))))
goto exit;
strcat(g_path, "/");
while (!found && R_SUCCEEDED(FSDIR_Read(dir, &entriesNb, 10, entries)))
{
if (entriesNb == 0)
break;
static const u16 * validExtension = u"3gx";
for (u32 i = 0; i < entriesNb; ++i)
{
FS_DirectoryEntry *entry = &entries[i];
// If entry is a folder, skip it
if (entry->attributes & 1)
continue;
// Check extension
u32 size = strlen16(entry->name);
if (size <= 5)
continue;
u16 *fileExt = entry->name + size - 3;
if (memcmp(fileExt, validExtension, 3 * sizeof(u16)))
continue;
// Convert name from utf16 to utf8
int units = utf16_to_utf8((u8 *)filename, entry->name, 100);
if (units == -1)
continue;
filename[units] = 0;
found = true;
break;
}
}
if (!found)
res = MAKERESULT(28, 4, 0, 1018);
else
{
u32 len = strlen(g_path);
filename[256 - len] = 0;
strcat(g_path, filename);
}
((PluginHeader *)g_memBlock)->pluginPathPA = PA_FROM_VA_PTR(g_path);
exit:
FSDIR_Close(dir);
FSUSER_CloseArchive(sdmcArchive);
return res;
}
static Result OpenFile(IFile *file, const char *path)
{
return IFile_Open(file, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, path), FS_OPEN_READ);
}
static Result CheckPluginCompatibility(_3gx_Header *header, u32 processTitle)
{
static char errorBuf[0x100];
if (header->targets.count == 0)
return 0;
for (u32 i = 0; i < header->targets.count; ++i)
{
if (header->targets.titles[i] == processTitle)
return 0;
}
sprintf(errorBuf, "The plugin - %s -\nis not compatible with this game.\n" \
"Contact \"%s\" for more infos.", header->infos.titleMsg, header->infos.authorMsg);
g_error.message = errorBuf;
return -1;
}
static bool TryToLoadPlugin(Handle process)
{
u64 fileSize;
u64 tid;
u32 procStart = 0x00100000;
IFile plugin;
PluginHeader *hdr = (PluginHeader *)g_memBlock;
_3gx_Header *header;
Result res;
// Clear the memblock
memset32(g_memBlock, 0, MemBlockSize);
hdr->magic = HeaderMagic;
// Get title id
svcGetProcessInfo((s64 *)&tid, process, 0x10001);
if (R_FAILED((res = svcMapProcessMemoryEx(CUR_PROCESS_HANDLE, procStart, process, procStart, 0x1000))))
{
g_error.message = "Couldn't map process";
g_error.code = res;
return false;
}
// Try to open plugin file
if (g_userDefinedLoadParameters.isEnabled && (u32)tid == g_userDefinedLoadParameters.titleid)
{
g_userDefinedLoadParameters.isEnabled = false;
if (OpenFile(&plugin, g_userDefinedLoadParameters.path))
goto exitFail;
hdr->pluginPathPA = PA_FROM_VA_PTR(g_userDefinedLoadParameters.path);
memcpy(hdr->config, g_userDefinedLoadParameters.config, 32 * sizeof(u32));
}
else
{
if (R_FAILED(FindPluginFile(tid)) || OpenFile(&plugin, g_path))
{
// Try to open default plugin
const char *defaultPath = "/luma/plugins/default.3gx";
if (OpenFile(&plugin, defaultPath))
goto exitFail;
hdr->isDefaultPlugin = 1;
hdr->pluginPathPA = PA_FROM_VA_PTR(defaultPath);
}
}
if (R_FAILED((res = IFile_GetSize(&plugin, &fileSize))))
g_error.message = "Couldn't get file size";
// Plugins will rarely exceed 1MB so this is fine
header = (_3gx_Header *)(g_memBlock + MemBlockSize - (u32)fileSize);
// Read header
if (!res && R_FAILED((res = Read_3gx_Header(&plugin, header))))
g_error.message = "Couldn't read file";
// Check titles compatibility
if (!res) res = CheckPluginCompatibility(header, (u32)tid);
// Read code
if (!res && R_FAILED(res = Read_3gx_Code(&plugin, header, g_memBlock + sizeof(PluginHeader))))
g_error.message = "Couldn't read plugin's code";
// Close file
IFile_Close(&plugin);
if (R_FAILED(res))
{
g_error.code = res;
goto exitFail;
}
hdr->version = header->version;
// Code size must be page aligned
fileSize = (header->codeSize + 0x1100) & ~0xFFF;
if (MapPluginInProcess(process, fileSize) == 0)
// Install hook
{
extern u32 g_savedGameInstr[2];
u32 *game = (u32 *)procStart;
g_savedGameInstr[0] = game[0];
g_savedGameInstr[1] = game[1];
game[0] = 0xE51FF004; // ldr pc, [pc, #-4]
game[1] = (u32)PA_FROM_VA_PTR(gamePatchFunc);
}
else
goto exitFail;
svcUnmapProcessMemoryEx(CUR_PROCESS_HANDLE, procStart, 0x1000);
return true;
exitFail:
svcUnmapProcessMemoryEx(CUR_PROCESS_HANDLE, procStart, 0x1000);
return false;
}
static void SetKernelConfigurationMemoryFlag(bool loaded)
{
u32 *flag = (u32 *)PA_FROM_VA_PTR(0x1FF800F0);
*flag = loaded;
}
static void PluginLoader_HandleCommands(void)
{
u32 *cmdbuf = getThreadCommandBuffer();
switch (cmdbuf[0] >> 16)
{
case 1: // Load plugin
{
if (cmdbuf[0] != IPC_MakeHeader(1, 0, 2))
{
error(cmdbuf, 0xD9001830);
break;
}
Handle process = cmdbuf[2];
if (g_isEnabled && TryToLoadPlugin(process))
{
if (!g_userDefinedLoadParameters.isEnabled && g_userDefinedLoadParameters.noFlash)
g_userDefinedLoadParameters.noFlash = false;
else
{
for (u32 i = 0; i < 64; i++)
{
REG32(0x10202204) = 0x01FF9933;
svcSleepThread(5000000);
}
REG32(0x10202204) = 0;
}
g_process = process;
}
else
svcCloseHandle(process);
cmdbuf[0] = IPC_MakeHeader(1, 1, 0);
cmdbuf[1] = 0;
break;
}
case 2: // Check if plugin loader is enabled
{
if (cmdbuf[0] != IPC_MakeHeader(2, 0, 0))
{
error(cmdbuf, 0xD9001830);
break;
}
cmdbuf[0] = IPC_MakeHeader(2, 2, 0);
cmdbuf[1] = 0;
cmdbuf[2] = (u32)g_isEnabled;
break;
}
case 3: // Enable / Disable plugin loader
{
if (cmdbuf[0] != IPC_MakeHeader(3, 1, 0))
{
error(cmdbuf, 0xD9001830);
break;
}
if (cmdbuf[1] != g_isEnabled)
{
g_isEnabled = cmdbuf[1];
SaveSettings();
}
cmdbuf[0] = IPC_MakeHeader(3, 1, 0);
cmdbuf[1] = 0;
break;
}
case 4: // Define next plugin load settings
{
if (cmdbuf[0] != IPC_MakeHeader(4, 2, 4))
{
error(cmdbuf, 0xD9001830);
break;
}
g_userDefinedLoadParameters.isEnabled = true;
g_userDefinedLoadParameters.noFlash = cmdbuf[1];
g_userDefinedLoadParameters.titleid = cmdbuf[2];
strncpy(g_userDefinedLoadParameters.path, (const char *)cmdbuf[4], 255);
memcpy(g_userDefinedLoadParameters.config, (void *)cmdbuf[6], 32 * sizeof(u32));
cmdbuf[0] = IPC_MakeHeader(4, 1, 0);
cmdbuf[1] = 0;
break;
}
case 5: // Display menu
{
if (cmdbuf[0] != IPC_MakeHeader(5, 1, 8))
{
error(cmdbuf, 0xD9001830);
break;
}
u32 nbItems = cmdbuf[1];
u32 states = cmdbuf[3];
DisplayPluginMenu(cmdbuf);
cmdbuf[0] = IPC_MakeHeader(5, 1, 2);
cmdbuf[1] = 0;
cmdbuf[2] = IPC_Desc_Buffer(nbItems, IPC_BUFFER_RW);
cmdbuf[3] = states;
break;
}
case 6: // Display message
{
if (cmdbuf[0] != IPC_MakeHeader(6, 0, 4))
{
error(cmdbuf, 0xD9001830);
break;
}
const char *title = (const char *)cmdbuf[2];
const char *body = (const char *)cmdbuf[4];
DispMessage(title, body);
cmdbuf[0] = IPC_MakeHeader(6, 1, 0);
cmdbuf[1] = 0;
break;
}
case 7: // Display error message
{
if (cmdbuf[0] != IPC_MakeHeader(7, 1, 4))
{
error(cmdbuf, 0xD9001830);
break;
}
const char *title = (const char *)cmdbuf[3];
const char *body = (const char *)cmdbuf[5];
DispErrMessage(title, body, cmdbuf[1]);
cmdbuf[0] = IPC_MakeHeader(7, 1, 0);
cmdbuf[1] = 0;
break;
}
}
}
void PluginLoader__ThreadMain(void)
{
const char *title = "Plugin loader";
Result res = 0;
MemOp memRegion = isN3DS ? MEMOP_REGION_BASE : MEMOP_REGION_SYSTEM;
Handle handles[3];
Handle serverHandle, clientHandle, sessionHandle = 0;
u32 *cmdbuf = getThreadCommandBuffer();
u32 replyTarget = 0;
u32 nbHandle;
s32 index;
// Wait for the system to be completely started
{
bool isAcuRegistered = false;
while (true)
{
if (R_SUCCEEDED(srvIsServiceRegistered(&isAcuRegistered, "ac:u"))
&& isAcuRegistered)
break;
svcSleepThread(100000);
}
}
// Init memory block to hold the plugin
{
u32 free = (u32)osGetMemRegionFree(memRegion);
u32 temp = 0;
svcControlMemoryEx(&temp, 0x00100000, 0, free - MemBlockSize, memRegion | MEMOP_ALLOC, MEMPERM_RW, true);
if (R_FAILED((res = svcControlMemoryEx((u32 *)&g_memBlock, 0x07000000, 0, MemBlockSize, memRegion | MEMOP_ALLOC, MEMPERM_RW, true))))
{
svcSleepThread(5000000000ULL); ///< Wait until the system started to display the error
DispErrMessage(title, "Couldn't allocate memblock", res);
return;
}
svcControlMemoryEx(&temp, (u32)temp, 0, MemBlockSize, memRegion | MEMOP_FREE, 0, true);
}
assertSuccess(svcCreatePort(&serverHandle, &clientHandle, "plg:ldr", 1));
do
{
g_error.message = NULL;
g_error.code = 0;
handles[0] = serverHandle;
handles[1] = sessionHandle == 0 ? g_process : sessionHandle;
handles[2] = g_process;
if(replyTarget == 0) // k11
cmdbuf[0] = 0xFFFF0000;
nbHandle = 1 + (sessionHandle != 0) + (g_process != 0);
res = svcReplyAndReceive(&index, handles, nbHandle, replyTarget);
if(R_FAILED(res))
{
if((u32)res == 0xC920181A) // session closed by remote
{
svcCloseHandle(sessionHandle);
sessionHandle = 0;
replyTarget = 0;
}
else
svcBreak(USERBREAK_PANIC);
}
else
{
if(index == 0)
{
Handle session;
assertSuccess(svcAcceptSession(&session, serverHandle));
if(sessionHandle == 0)
sessionHandle = session;
else
svcCloseHandle(session);
}
else if (index == 1 && handles[1] == sessionHandle)
{
PluginLoader_HandleCommands();
replyTarget = sessionHandle;
}
else ///< The process in which we injected the plugin is terminating
{
// Unmap plugin's memory before closing the process
svcUnmapProcessMemoryEx(g_process, 0x07000000, g_codeSize);
svcUnmapProcessMemoryEx(g_process, 0x06000000, g_heapSize);
svcCloseHandle(g_process);
g_process = 0;
}
}
if (g_error.message != NULL)
DispErrMessage(title, g_error.message, g_error.code);
SetKernelConfigurationMemoryFlag(g_process != 0);
} while(!terminationRequest);
svcCloseHandle(sessionHandle);
svcCloseHandle(clientHandle);
svcCloseHandle(serverHandle);
svcControlMemoryEx((u32 *)&g_memBlock, (u32)g_memBlock, 0, MemBlockSize, memRegion | MEMOP_FREE, 0, true);
}