
This commit adds all the changes made to the 3GX plugin loader fork of Luma3DS. The most important features are: - Add 3GX plugin loader support. New service added to rosalina: plg:ldr - Add svcControlProcess, svcControlMemoryUnsafe and improve svcMapProcessMemoryEx (breaking change) - Allow applications to override certain configurations depending on their needs: - Disable core2 thread redirection - Disable game patching for the next app - Force New 3DS speedup - Force next application in a specific memory mode - Block the opening of the Rosalina menu - Add GDB commands to list all process handles and catch all SVC (latter is for IDA Pro as gdb client supports it) - Other changes necessary for plugins to work properly. Please check changed files in this PR for more details. --------- Co-authored-by: PabloMK7 <hackyglitch@gmail.com> Co-authored-by: Nanquitas <nath.doidi@gmail.com> Co-authored-by: TuxSH <1922548+TuxSH@users.noreply.github.com>
604 lines
19 KiB
C
604 lines
19 KiB
C
#include <3ds.h>
|
|
#include "memory.h"
|
|
#include "patcher.h"
|
|
#include "paslr.h"
|
|
#include "ifile.h"
|
|
#include "util.h"
|
|
#include "hbldr.h"
|
|
|
|
#define SYSMODULE_CXI_COOKIE_MASK 0xEEEE000000000000ull
|
|
|
|
// Used by the custom loader command 0x101 (ControlApplicationMemoryModeOverride)
|
|
typedef struct ControlApplicationMemoryModeOverrideConfig {
|
|
u32 query : 1; //< Only query the current configuration, do not update it.
|
|
u32 enable_o3ds : 1; //< Enable o3ds memory mode override
|
|
u32 enable_n3ds : 1; //< Enable n3ds memory mode override
|
|
u32 o3ds_mode : 3; //< O3ds memory mode
|
|
u32 n3ds_mode : 3; //< N3ds memory mode
|
|
} ControlApplicationMemoryModeOverrideConfig;
|
|
|
|
static ControlApplicationMemoryModeOverrideConfig g_memoryOverrideConfig = { 0 };
|
|
|
|
extern u32 config, multiConfig, bootConfig;
|
|
extern bool isN3DS, isSdMode, nextGamePatchDisabled;
|
|
|
|
static u64 g_cached_programHandle; // for exheader info only
|
|
static ExHeader_Info g_exheaderInfo;
|
|
|
|
static IFile g_cached_sysmoduleCxiFile;
|
|
static u64 g_cached_sysmoduleCxiCookie;
|
|
static Ncch g_cached_sysmoduleCxiNcch;
|
|
|
|
typedef struct ContentPath {
|
|
u32 contentType;
|
|
char fileName[8]; // for exefs
|
|
} ContentPath;
|
|
|
|
static const ContentPath codeContentPath = {
|
|
.contentType = 1, // ExeFS (with code)
|
|
.fileName = ".code", // last 3 bytes have to be 0, but this is guaranteed here.
|
|
};
|
|
|
|
typedef struct prog_addrs_t
|
|
{
|
|
u32 text_addr;
|
|
u32 text_size;
|
|
u32 ro_addr;
|
|
u32 ro_size;
|
|
u32 data_addr;
|
|
u32 data_size;
|
|
u32 total_size;
|
|
} prog_addrs_t;
|
|
|
|
static int lzss_decompress(u8 *end)
|
|
{
|
|
unsigned int v1; // r1@2
|
|
u8 *v2; // r2@2
|
|
u8 *v3; // r3@2
|
|
u8 *v4; // r1@2
|
|
char v5; // r5@4
|
|
char v6; // t1@4
|
|
signed int v7; // r6@4
|
|
int v9; // t1@7
|
|
u8 *v11; // r3@8
|
|
int v12; // r12@8
|
|
int v13; // t1@8
|
|
int v14; // t1@8
|
|
unsigned int v15; // r7@8
|
|
int v16; // r12@8
|
|
int ret;
|
|
|
|
ret = 0;
|
|
if ( end )
|
|
{
|
|
v1 = *((u32 *)end - 2);
|
|
v2 = &end[*((u32 *)end - 1)];
|
|
v3 = &end[-(v1 >> 24)];
|
|
v4 = &end[-(v1 & 0xFFFFFF)];
|
|
while ( v3 > v4 )
|
|
{
|
|
v6 = *(v3-- - 1);
|
|
v5 = v6;
|
|
v7 = 8;
|
|
while ( 1 )
|
|
{
|
|
if ( (v7-- < 1) )
|
|
break;
|
|
if ( v5 & 0x80 )
|
|
{
|
|
v13 = *(v3 - 1);
|
|
v11 = v3 - 1;
|
|
v12 = v13;
|
|
v14 = *(v11 - 1);
|
|
v3 = v11 - 1;
|
|
v15 = ((v14 | (v12 << 8)) & 0xFFFF0FFF) + 2;
|
|
v16 = v12 + 32;
|
|
do
|
|
{
|
|
ret = v2[v15];
|
|
*(v2-- - 1) = ret;
|
|
v16 -= 16;
|
|
}
|
|
while ( !(v16 < 0) );
|
|
}
|
|
else
|
|
{
|
|
v9 = *(v3-- - 1);
|
|
ret = v9;
|
|
*(v2-- - 1) = v9;
|
|
}
|
|
v5 *= 2;
|
|
if ( v3 <= v4 )
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static inline bool IsSysmoduleId(u64 tid)
|
|
{
|
|
return (tid >> 32) == 0x00040130;
|
|
}
|
|
|
|
static inline bool IsApplicationId(u64 tid)
|
|
{
|
|
return (tid >> 32) == 0x00040000;
|
|
}
|
|
|
|
static inline bool IsSysmoduleCxiCookie(u64 programHandle)
|
|
{
|
|
return (programHandle >> 32) == (SYSMODULE_CXI_COOKIE_MASK >> 32);
|
|
}
|
|
|
|
static void InvalidateCachedCxiFile(void)
|
|
{
|
|
if (g_cached_sysmoduleCxiFile.handle != 0)
|
|
IFile_Close(&g_cached_sysmoduleCxiFile);
|
|
memset(&g_cached_sysmoduleCxiFile, 0, sizeof(IFile));
|
|
|
|
g_cached_sysmoduleCxiCookie = 0;
|
|
}
|
|
|
|
static Result allocateProgramMemoryWrapper(prog_addrs_t *mapped, const ExHeader_Info *exhi, const prog_addrs_t *vaddr)
|
|
{
|
|
memcpy(mapped, vaddr, sizeof(prog_addrs_t));
|
|
mapped->text_addr = 0x10000000;
|
|
mapped->ro_addr = mapped->text_addr + (mapped->text_size << 12);
|
|
mapped->data_addr = mapped->ro_addr + (mapped->ro_size << 12);
|
|
return allocateProgramMemory(exhi, mapped->text_addr, mapped->total_size << 12);
|
|
}
|
|
|
|
static Result loadCode(const ExHeader_Info *exhi, u64 programHandle, const prog_addrs_t *mapped)
|
|
{
|
|
IFile file;
|
|
FS_Path archivePath;
|
|
FS_Path filePath;
|
|
u64 size;
|
|
u64 total;
|
|
|
|
u64 titleId = exhi->aci.local_caps.title_id;
|
|
const ExHeader_CodeSetInfo *csi = &exhi->sci.codeset_info;
|
|
bool isCompressed = csi->flags.compress_exefs_code;
|
|
|
|
// Load from CXI, and skip patches, if we were opened from it
|
|
if (IsSysmoduleCxiCookie(programHandle))
|
|
{
|
|
u32 sz_ = 0;
|
|
bool ok = readSysmoduleCxiCode((u8 *)mapped->text_addr, &sz_, (u64)mapped->total_size << 12, &g_cached_sysmoduleCxiFile, &g_cached_sysmoduleCxiNcch);
|
|
size = sz_;
|
|
|
|
if (!ok)
|
|
return (Result)-2;
|
|
|
|
// Decompress
|
|
if (isCompressed)
|
|
lzss_decompress((u8 *)mapped->text_addr + size);
|
|
|
|
// No need to keep the file open at this point
|
|
InvalidateCachedCxiFile();
|
|
return 0;
|
|
}
|
|
|
|
bool codeLoadedExternally = false;
|
|
if (CONFIG(PATCHGAMES))
|
|
{
|
|
// Require both "load external FIRM & modules" and "patch games" for sysmodules
|
|
if (IsSysmoduleId(titleId))
|
|
codeLoadedExternally = CONFIG(LOADEXTFIRMSANDMODULES);
|
|
else
|
|
codeLoadedExternally = true;
|
|
}
|
|
|
|
if (codeLoadedExternally)
|
|
codeLoadedExternally = loadTitleCodeSection(titleId, (u8 *)mapped->text_addr, (u64)mapped->total_size << 12);
|
|
|
|
if(!codeLoadedExternally)
|
|
{
|
|
archivePath.type = PATH_BINARY;
|
|
archivePath.data = &programHandle;
|
|
archivePath.size = sizeof(programHandle);
|
|
|
|
filePath.type = PATH_BINARY;
|
|
filePath.data = &codeContentPath;
|
|
filePath.size = sizeof(codeContentPath);
|
|
|
|
assertSuccess(IFile_Open(&file, ARCHIVE_SAVEDATA_AND_CONTENT2, archivePath, filePath, FS_OPEN_READ));
|
|
assertSuccess(IFile_GetSize(&file, &size));
|
|
|
|
// check size
|
|
if (size > (u64)mapped->total_size << 12)
|
|
{
|
|
IFile_Close(&file);
|
|
return 0xC900464F;
|
|
}
|
|
|
|
// read code
|
|
assertSuccess(IFile_Read(&file, &total, (void *)mapped->text_addr, size));
|
|
IFile_Close(&file); // done reading
|
|
|
|
// decompress
|
|
if (isCompressed)
|
|
lzss_decompress((u8 *)mapped->text_addr + size);
|
|
}
|
|
|
|
patchCode(titleId, csi->flags.remaster_version, (u8 *)mapped->text_addr, mapped->total_size << 12, csi->text.size, csi->rodata.size, csi->data.size, csi->rodata.address, csi->data.address);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u32 plgldrRefcount = 0;
|
|
static Handle plgldrHandle = 0;
|
|
|
|
Result plgldrInit(void)
|
|
{
|
|
Result res;
|
|
if (AtomicPostIncrement(&plgldrRefcount)) return 0;
|
|
|
|
for(res = 0xD88007FA; res == (Result)0xD88007FA; svcSleepThread(500 * 1000LL)) {
|
|
res = svcConnectToPort(&plgldrHandle, "plg:ldr");
|
|
if(R_FAILED(res) && res != (Result)0xD88007FA) {
|
|
AtomicDecrement(&plgldrRefcount);
|
|
return res;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void plgldrExit(void)
|
|
{
|
|
if (AtomicDecrement(&plgldrRefcount)) return;
|
|
svcCloseHandle(plgldrHandle);
|
|
}
|
|
|
|
// Get plugin loader state
|
|
Result PLGLDR__IsPluginLoaderEnabled(bool *isEnabled)
|
|
{
|
|
Result res = 0;
|
|
|
|
u32 *cmdbuf = getThreadCommandBuffer();
|
|
|
|
cmdbuf[0] = IPC_MakeHeader(2, 0, 0);
|
|
if (R_SUCCEEDED((res = svcSendSyncRequest(plgldrHandle))))
|
|
{
|
|
res = cmdbuf[1];
|
|
*isEnabled = cmdbuf[2];
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// Try to load a plugin for the game
|
|
static Result PLGLDR_LoadPlugin(u32 processID)
|
|
{
|
|
// Special case handling: games rebooting the 3DS on old models
|
|
if (!isN3DS && g_exheaderInfo.aci.local_caps.core_info.o3ds_system_mode > 0)
|
|
{
|
|
// Check if the plugin loader is enabled, otherwise skip the loading part
|
|
bool enabled = false;
|
|
|
|
PLGLDR__IsPluginLoaderEnabled(&enabled);
|
|
if (!enabled) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
u32* cmdbuf = getThreadCommandBuffer();
|
|
|
|
cmdbuf[0] = IPC_MakeHeader(1, 1, 0);
|
|
cmdbuf[1] = processID;
|
|
return svcSendSyncRequest(plgldrHandle);
|
|
}
|
|
|
|
static inline bool IsHioId(u64 id)
|
|
{
|
|
// FS loads HIO titles at boot when it can. For HIO titles, title/programId and "program handle"
|
|
// are the same thing, although some of them can be aliased with their "real" titleId (i.e. in ExHeader).
|
|
|
|
if (id >> 32 == 0xFFFF0000u)
|
|
return true;
|
|
else
|
|
return R_LEVEL(FSREG_CheckHostLoadId(id)) == RL_SUCCESS; // check if this is an alias to an HIO-loaded title
|
|
}
|
|
|
|
static Result GetProgramInfoImpl(ExHeader_Info *exheaderInfo, u64 programHandle)
|
|
{
|
|
Result res;
|
|
|
|
// Load from CXI, and skip patches, if we were opened from it
|
|
if (IsSysmoduleCxiCookie(programHandle))
|
|
{
|
|
u64 titleId = 0x0004013000000000ull | (u32)programHandle;
|
|
if (g_cached_sysmoduleCxiCookie != programHandle)
|
|
{
|
|
InvalidateCachedCxiFile();
|
|
res = openSysmoduleCxi(&g_cached_sysmoduleCxiFile, titleId);
|
|
if (R_FAILED(res))
|
|
return res;
|
|
g_cached_sysmoduleCxiCookie = programHandle;
|
|
}
|
|
|
|
bool ok = readSysmoduleCxiNcchHeader(&g_cached_sysmoduleCxiNcch, &g_cached_sysmoduleCxiFile);
|
|
if (!ok)
|
|
return (Result)-2;
|
|
ok = readSysmoduleCxiExHeaderInfo(exheaderInfo, &g_cached_sysmoduleCxiNcch, &g_cached_sysmoduleCxiFile);
|
|
if (!ok)
|
|
return (Result)-2;
|
|
return 0;
|
|
}
|
|
|
|
if (IsHioId(programHandle))
|
|
res = FSREG_GetProgramInfo(exheaderInfo, 1, programHandle);
|
|
else
|
|
res = PXIPM_GetProgramInfo(exheaderInfo, programHandle);
|
|
|
|
if (R_FAILED(res))
|
|
return res;
|
|
|
|
u64 originalTitleId = exheaderInfo->aci.local_caps.title_id;
|
|
|
|
// Tweak 3dsx placeholder title exheaderInfo
|
|
if (hbldrIs3dsxTitle(exheaderInfo->aci.local_caps.title_id))
|
|
{
|
|
hbldrPatchExHeaderInfo(exheaderInfo);
|
|
}
|
|
else
|
|
{
|
|
bool exhLoadedExternally = false;
|
|
if (CONFIG(PATCHGAMES))
|
|
{
|
|
// Require both "load external FIRM & modules" and "patch games" for sysmodules
|
|
if (IsSysmoduleId(originalTitleId))
|
|
exhLoadedExternally = CONFIG(LOADEXTFIRMSANDMODULES);
|
|
else
|
|
exhLoadedExternally = true;
|
|
}
|
|
|
|
if (exhLoadedExternally)
|
|
exhLoadedExternally = loadTitleExheaderInfo(originalTitleId, exheaderInfo);
|
|
|
|
if(exhLoadedExternally)
|
|
exheaderInfo->aci.local_caps.title_id = originalTitleId;
|
|
}
|
|
|
|
if (IsApplicationId(originalTitleId)) {
|
|
if (g_memoryOverrideConfig.enable_o3ds)
|
|
exheaderInfo->aci.local_caps.core_info.o3ds_system_mode = g_memoryOverrideConfig.o3ds_mode;
|
|
if (g_memoryOverrideConfig.enable_n3ds)
|
|
exheaderInfo->aci.local_caps.core_info.n3ds_system_mode = g_memoryOverrideConfig.n3ds_mode;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static Result GetProgramInfo(u64 programHandle)
|
|
{
|
|
Result res = 0;
|
|
u64 cachedTitleId = g_exheaderInfo.aci.local_caps.title_id;
|
|
|
|
if (programHandle != g_cached_programHandle || hbldrIs3dsxTitle(cachedTitleId))
|
|
{
|
|
res = GetProgramInfoImpl(&g_exheaderInfo, programHandle);
|
|
g_cached_programHandle = R_SUCCEEDED(res) ? programHandle : 0;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static Result LoadProcessImpl(Handle *outProcessHandle, const ExHeader_Info *exhi, u64 programHandle)
|
|
{
|
|
const ExHeader_CodeSetInfo *csi = &exhi->sci.codeset_info;
|
|
|
|
Result res = 0;
|
|
u32 dummy;
|
|
prog_addrs_t mapped;
|
|
prog_addrs_t vaddr;
|
|
Handle codeset;
|
|
CodeSetHeader csh;
|
|
u32 dataMemSize;
|
|
|
|
u32 region = 0;
|
|
s32 count;
|
|
for (count = 0; count < 28; count++)
|
|
{
|
|
u32 desc = exhi->aci.kernel_caps.descriptors[count];
|
|
if (0x1FE == desc >> 23)
|
|
region = desc & 0xF00;
|
|
}
|
|
if (region == 0)
|
|
return MAKERESULT(RL_PERMANENT, RS_INVALIDARG, RM_KERNEL, 2);
|
|
|
|
// allocate process memory
|
|
vaddr.text_addr = csi->text.address;
|
|
vaddr.text_size = (csi->text.size + 4095) >> 12;
|
|
vaddr.ro_addr = csi->rodata.address;
|
|
vaddr.ro_size = (csi->rodata.size + 4095) >> 12;
|
|
vaddr.data_addr = csi->data.address;
|
|
vaddr.data_size = (csi->data.size + 4095) >> 12;
|
|
dataMemSize = (csi->data.size + csi->bss_size + 4095) >> 12;
|
|
vaddr.total_size = vaddr.text_size + vaddr.ro_size + vaddr.data_size;
|
|
TRY(allocateProgramMemoryWrapper(&mapped, exhi, &vaddr));
|
|
|
|
// load code
|
|
u64 titleId = exhi->aci.local_caps.title_id;
|
|
if (R_SUCCEEDED(res = loadCode(exhi, programHandle, &mapped)))
|
|
{
|
|
u32 *code = (u32 *)mapped.text_addr;
|
|
bool isHomebrew = code[0] == 0xEA000006 && code[8] == 0xE1A0400E;
|
|
|
|
memcpy(&csh.name, csi->name, 8);
|
|
csh.program_id = titleId;
|
|
csh.text_addr = vaddr.text_addr;
|
|
csh.text_size = vaddr.text_size;
|
|
csh.text_size_total = vaddr.text_size;
|
|
csh.ro_addr = vaddr.ro_addr;
|
|
csh.ro_size = vaddr.ro_size;
|
|
csh.ro_size_total = vaddr.ro_size;
|
|
csh.rw_addr = vaddr.data_addr;
|
|
csh.rw_size = vaddr.data_size;
|
|
csh.rw_size_total = dataMemSize;
|
|
res = svcCreateCodeSet(&codeset, &csh, mapped.text_addr, mapped.ro_addr, mapped.data_addr);
|
|
if (R_SUCCEEDED(res))
|
|
{
|
|
// There are always 28 descriptors
|
|
res = svcCreateProcess(outProcessHandle, codeset, exhi->aci.kernel_caps.descriptors, count);
|
|
svcCloseHandle(codeset);
|
|
res = R_SUCCEEDED(res) ? 0 : res;
|
|
|
|
// check for plugin
|
|
if (!res && !isHomebrew && ((u32)((titleId >> 0x20) & 0xFFFFFFEDULL) == 0x00040000))
|
|
{
|
|
u32 processID;
|
|
assertSuccess(svcGetProcessId(&processID, *outProcessHandle));
|
|
assertSuccess(plgldrInit());
|
|
assertSuccess(PLGLDR_LoadPlugin(processID));
|
|
plgldrExit();
|
|
}
|
|
}
|
|
}
|
|
|
|
svcControlMemory(&dummy, mapped.text_addr, 0, mapped.total_size << 12, MEMOP_FREE, 0);
|
|
return res;
|
|
}
|
|
|
|
static Result LoadProcess(Handle *process, u64 programHandle)
|
|
{
|
|
Result res = 0;
|
|
TRY(GetProgramInfo(programHandle));
|
|
|
|
if (hbldrIs3dsxTitle(g_exheaderInfo.aci.local_caps.title_id))
|
|
return assertSuccess(hbldrLoadProcess(process, &g_exheaderInfo));
|
|
else
|
|
// Break on failure, even here (if GetProgramInfo succeeds we shouldn't be here anyway)
|
|
return assertSuccess(LoadProcessImpl(process, &g_exheaderInfo, programHandle));
|
|
}
|
|
|
|
static Result RegisterProgram(u64 *programHandle, FS_ProgramInfo *title, FS_ProgramInfo *update)
|
|
{
|
|
Result res;
|
|
u64 titleId;
|
|
|
|
titleId = title->programId;
|
|
if (IsHioId(titleId))
|
|
{
|
|
if ((title->mediaType != update->mediaType) || (titleId != update->programId))
|
|
panic(1);
|
|
|
|
TRY(FSREG_LoadProgram(programHandle, title));
|
|
|
|
if (!IsHioId(*programHandle)) // double check this is indeed HIO
|
|
panic(2);
|
|
}
|
|
else
|
|
{
|
|
bool loadedCxiFromStorage = false;
|
|
if (IsSysmoduleId(titleId) && CONFIG(LOADEXTFIRMSANDMODULES))
|
|
{
|
|
u64 tid2 = titleId & ~0xF0000000ull;
|
|
// Forbid having two such file handles being open at the same time
|
|
// Also reload the file even if already cached.
|
|
InvalidateCachedCxiFile();
|
|
|
|
res = openSysmoduleCxi(&g_cached_sysmoduleCxiFile, tid2);
|
|
if (R_SUCCEEDED(res))
|
|
{
|
|
// A .cxi with the correct name in /luma/sysmodule exists, proceed (ignoring N3DS TID bits)
|
|
*programHandle = SYSMODULE_CXI_COOKIE_MASK | (u32)tid2;
|
|
g_cached_sysmoduleCxiCookie = *programHandle;
|
|
loadedCxiFromStorage = true;
|
|
}
|
|
}
|
|
|
|
if (!loadedCxiFromStorage)
|
|
{
|
|
// Otherwise, just load as normal
|
|
TRY(PXIPM_RegisterProgram(programHandle, title, update));
|
|
if (IsHioId(*programHandle)) // double check this is indeed *not* HIO
|
|
panic(0);
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static Result UnregisterProgram(u64 programHandle)
|
|
{
|
|
if (g_cached_programHandle == programHandle)
|
|
g_cached_programHandle = 0;
|
|
|
|
if (IsSysmoduleCxiCookie(programHandle))
|
|
{
|
|
if (programHandle == g_cached_sysmoduleCxiCookie)
|
|
InvalidateCachedCxiFile();
|
|
return 0;
|
|
}
|
|
else
|
|
return IsHioId(programHandle) ? FSREG_UnloadProgram(programHandle) : PXIPM_UnregisterProgram(programHandle);
|
|
}
|
|
|
|
void loaderHandleCommands(void *ctx)
|
|
{
|
|
(void)ctx;
|
|
FS_ProgramInfo title;
|
|
FS_ProgramInfo update;
|
|
ControlApplicationMemoryModeOverrideConfig memModeOverride;
|
|
u32* cmdbuf;
|
|
u16 cmdid;
|
|
int res;
|
|
Handle handle;
|
|
u64 programHandle;
|
|
|
|
cmdbuf = getThreadCommandBuffer();
|
|
cmdid = cmdbuf[0] >> 16;
|
|
res = 0;
|
|
switch (cmdid)
|
|
{
|
|
case 1: // LoadProcess
|
|
memcpy(&programHandle, &cmdbuf[1], 8);
|
|
handle = 0;
|
|
cmdbuf[1] = LoadProcess(&handle, programHandle);
|
|
cmdbuf[0] = IPC_MakeHeader(1, 1, 2);
|
|
cmdbuf[2] = IPC_Desc_MoveHandles(1);
|
|
cmdbuf[3] = handle;
|
|
break;
|
|
case 2: // RegisterProgram
|
|
memcpy(&title, &cmdbuf[1], sizeof(FS_ProgramInfo));
|
|
memcpy(&update, &cmdbuf[5], sizeof(FS_ProgramInfo));
|
|
cmdbuf[1] = RegisterProgram(&programHandle, &title, &update);
|
|
cmdbuf[0] = IPC_MakeHeader(2, 3, 0);
|
|
memcpy(&cmdbuf[2], &programHandle, 8);
|
|
break;
|
|
case 3: // UnregisterProgram
|
|
memcpy(&programHandle, &cmdbuf[1], 8);
|
|
cmdbuf[1] = UnregisterProgram(programHandle);
|
|
cmdbuf[0] = IPC_MakeHeader(3, 1, 0);
|
|
break;
|
|
case 4: // GetProgramInfo
|
|
memcpy(&programHandle, &cmdbuf[1], 8);
|
|
res = GetProgramInfo(programHandle);
|
|
cmdbuf[0] = IPC_MakeHeader(4, 1, 2);
|
|
cmdbuf[1] = res;
|
|
cmdbuf[2] = IPC_Desc_StaticBuffer(sizeof(ExHeader_Info), 0); //0x1000002;
|
|
cmdbuf[3] = (u32)&g_exheaderInfo; // official Loader makes a copy here, but this is isn't necessary
|
|
break;
|
|
// Custom
|
|
case 0x100: // DisableNextGamePatch
|
|
nextGamePatchDisabled = true;
|
|
cmdbuf[0] = IPC_MakeHeader(0x100, 1, 0);
|
|
cmdbuf[1] = (Result)0;
|
|
break;
|
|
case 0x101: // ControlApplicationMemoryModeOverride
|
|
memcpy(&memModeOverride, &cmdbuf[1], sizeof(ControlApplicationMemoryModeOverrideConfig));
|
|
if (!memModeOverride.query)
|
|
g_memoryOverrideConfig = memModeOverride;
|
|
cmdbuf[0] = IPC_MakeHeader(0x101, 2, 0);
|
|
cmdbuf[1] = (Result)0;
|
|
memcpy(&cmdbuf[2], &g_memoryOverrideConfig, sizeof(ControlApplicationMemoryModeOverrideConfig));
|
|
break;
|
|
default: // error
|
|
cmdbuf[0] = IPC_MakeHeader(0, 1, 0);
|
|
cmdbuf[1] = 0xD900182F;
|
|
break;
|
|
}
|
|
}
|