- Update loader to latest ctrulib (FS_Archive changed quite a bit, broke API and ABI, ugh)
- Clean up some patch handling code for future
This is (yet another) CFW for the 3DS. Unlike other CFWs, this was mostly written from scratch and for fun - because I'm a control freak, and this carries quite a bit of OSDev and RE with it.
-Some parts are inherited from other CFW - for example, the firmware loading code in src/firm is based on Cakes, and sigpatch/firmprot/svcbackdoor are all loosely based on Luma3DS while using more correct Cakes-style section code.
+Some parts are inherited from other CFW - for example, the firmware loading code in src/firm is based on Cakes, and sigpatch/firmprot/svcbackdoor were loosely based on Luma3DS.
Eventually in operation, this will be most similar to Cakes out of the bunch. That is, it will use external patches from the filesystem and is intended for developers and control freaks. Unlike cakes, patches will be dynamically loaded binaries for arm9/arm11 which will be relocated against internal functions, allowing relatively tiny patches. This is not stable, and does not work yet.
For compilation instructions, see `doc/compiling.md`.
Unless otherwise noted, everything in this repo can be used under the terms of the GNU GPL, Version 3 or later (if ever) at your discretion. This includes situations where there's no copyright header within a source file. I get lazy with those; assume everything can be used under the GPL, or files were from GPLv2 or later (and thus are upgraded.)
-
-This repo is named after a certain "factor". DATA DRAAAAIN!
+QoTD: "And I will tell you again...Welcome to 'The World.'"
ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft
ERROR := -Werror
-CFLAGS := -flto -Wall -Os -mword-relocations \
+CFLAGS := -flto -Wall -Os -mword-relocations $(ERROR) \
-fomit-frame-pointer -ffunction-sections -fdata-sections -fshort-wchar \
$(ARCH)
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu99
ASFLAGS := $(ARCH)
-LDFLAGS = -flto -Xlinker --defsym="__start__=0x14000000" -specs=3dsx.specs $(ARCH) -Wl,-Map,$(notdir $*.map)
+LDFLAGS = -flto -Xlinker --defsym="__start__=0x14000000" -specs=3dsx.specs $(ARCH) -Wl,-Map,$(notdir $*.map) -z defs
LIBS := -lctru
## Roadmap
Right now, this can serve as an open-source replacement for the built in loader.
+
There is additional support for patching any executable after it's loaded but
before it starts. For example, you can patch `menu` to skip region checks and
-have region free game launching directly from the home menu. There is also
-support for SDMC reading (not found in original loader implementation) which
-means that patches can be loaded from the SD card. Ultimately, there would be
+have region free game launching directly from the home menu.
+
+There is also support for SDMC reading (not found in original loader implementation)
+which means that patches can be loaded from the SD card. Ultimately, there would be
a patch system that supports easy loading of patches from the SD card.
+At the moment this copy has support for resizing segments before loading the
+executable. This means that segments can be appended to. Notably, this allows
+ADDING code, not simply changing it.
+
+The text, data, and ro segments are handled separately to streamline segment
+expansion and limit search space.
+
+A lot of the 'disassembled' looking code has been rewritten for readability, and
+many cruft-ish artifacts of it have been cleaned up. Some wrapper functions
+have also been rewritten out of the code, and anything nigh-unreadable has
+been documented when I figure out what it does.
+
+It has also been updated to the latest git ctrulib (in which FS_Archive is a u64,
+not a struct.) Thanks, @TuxSH. It required manual conflict merged (my code is different)
+but you provided the information needed to fix it up.
+
## Build
You need a working 3DS build environment with a fairly recent copy of devkitARM,
-ctrulib, and makerom. If you see any errors in the build process, it's likely
-that you're using an older version.
-
-Currently, there is no support for FIRM building, so you need to do some steps
-manually. First, you have to add padding to make sure the NCCH is of the right
-size to drop in as a replacement. A hacky way is
-[this patch](http://pastebin.com/nyKXLnNh) which adds junk data. Play around
-with the size value to get the NCCH to be the exact same size as the one
-found in your decrypted FIRM dump.
-
-Once you have a NCCH of the right size, just replace it in your decrypted FIRM
-and find a way to launch it (for example with ReiNAND).
+ctrulib, and makerom.
+
+Currently, there is no support for FIRM building in toolchain; whatever method
+you use to inject this is up to you, but the software must have support for
+resizing the sysmodule segment, or you must make sure the module size matches
+the original.
#ifndef __CONFIG_H
#define __CONFIG_H
-static unsigned int config_version = 1;
+__attribute__((unused)) static unsigned int config_version = 1;
#define CONFIG_MAGIC "OVAN"
return cmdbuf[1];
}
-Result FSLDR_OpenFileDirectly(Handle* out, FS_Archive archive, FS_Path path, u32 openFlags, u32 attributes)
+Result FSLDR_OpenFileDirectly(Handle* out, FS_ArchiveID archiveId, FS_Path archivePath, FS_Path filePath, u32 openFlags, u32 attributes)
{
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0x803,8,4); // 0x8030204
cmdbuf[1] = 0;
- cmdbuf[2] = archive.id;
- cmdbuf[3] = archive.lowPath.type;
- cmdbuf[4] = archive.lowPath.size;
- cmdbuf[5] = path.type;
- cmdbuf[6] = path.size;
+ cmdbuf[2] = archiveId;
+ cmdbuf[3] = archivePath.type;
+ cmdbuf[4] = archivePath.size;
+ cmdbuf[5] = filePath.type;
+ cmdbuf[6] = filePath.size;
cmdbuf[7] = openFlags;
cmdbuf[8] = attributes;
- cmdbuf[9] = IPC_Desc_StaticBuffer(archive.lowPath.size, 2);
- cmdbuf[10] = (u32) archive.lowPath.data;
- cmdbuf[11] = IPC_Desc_StaticBuffer(path.size, 0);
- cmdbuf[12] = (u32) path.data;
+ cmdbuf[9] = IPC_Desc_StaticBuffer(archivePath.size, 2);
+ cmdbuf[10] = (u32)archivePath.data;
+ cmdbuf[11] = IPC_Desc_StaticBuffer(filePath.size, 0);
+ cmdbuf[12] = (u32)filePath.data;
Result ret = 0;
if(R_FAILED(ret = svcSendSyncRequest(fsldrHandle))) return ret;
void fsldrExit(void);
Result FSLDR_InitializeWithSdkVersion(Handle session, u32 version);
Result FSLDR_SetPriority(u32 priority);
-Result FSLDR_OpenFileDirectly(Handle* out, FS_Archive archive, FS_Path path, u32 openFlags, u32 attributes);
+Result FSLDR_OpenFileDirectly(Handle* out, FS_ArchiveID archiveId, FS_Path archivePath, FS_Path filePath, u32 openFlags, u32 attributes);
\ No newline at end of file
#include "ifile.h"
#include "fsldr.h"
-Result IFile_Open(IFile *file, FS_Archive archive, FS_Path path, u32 flags)
+// TODO - What exactly is the point of these? Can't we just use FSFILE directly?
+// These are just shitty wrappers.
+
+Result IFile_Open(IFile *file, FS_ArchiveID archiveId, FS_Path archivePath, FS_Path filePath, u32 flags)
{
Result res;
- res = FSLDR_OpenFileDirectly(&file->handle, archive, path, flags, 0);
+ res = FSLDR_OpenFileDirectly(&file->handle, archiveId, archivePath, filePath, flags, 0);
file->pos = 0;
file->size = 0;
return res;
u64 size;
} IFile;
-Result IFile_Open(IFile *file, FS_Archive archive, FS_Path path, u32 flags);
+Result IFile_Open(IFile *file, FS_ArchiveID archiveId, FS_Path archivePath, FS_Path filePath, u32 flags);
Result IFile_Close(IFile *file);
Result IFile_GetSize(IFile *file, u64 *size);
Result IFile_Read(IFile *file, u64 *total, void *buffer, u32 len);
--- /dev/null
+#pragma once
+
+// This is a GCC builtin, and so there's no need to carry an implementation here.
+void* memcpy(void* dest, const void* src, size_t len);
#include <3ds.h>
-#include "memory.h"
#include "patcher.h"
#include "exheader.h"
#include "ifile.h"
#include "fsreg.h"
#include "pxipm.h"
#include "srvsys.h"
+#include "internal.h"
+
+// TODO - a lot of this is unecessarily verbose and shitty. Clean it up to be tidy.
#define MAX_SESSIONS 1
static exheader_header g_exheader;
static char g_ret_buf[1024];
-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;
+// TODO - This doesn't belong in loader.c - It's a decompression function, stuff it in its own file.
+// Not to mention, it is nigh unreadable. Replace it with a more readable version of LZSS.
+// This decompresses in-place.
- ret = 0;
- if ( end )
+// end should be 'buffer'
+static int lzss_decompress(u8 *buffer)
+{
+ // This WAS originally a decompilation in @yifan_lu's repo; it was rewritten for readability following ctrtool's namings.
+ unsigned int decompSize, v15;
+ u8 *compressEndOff, *index, *stopIndex;
+ char control;
+ int v9, v13, v14, v16;
+ int ret = 0;
+
+ if ( !buffer ) // Return immediately when buffer is invalid.
+ return 0;
+
+ // v1=decompressedSize, v2=compressedSize, v3=index, v4=stopIndex
+ decompSize = *((u32 *)buffer - 2);
+ compressEndOff = &buffer[*((u32 *)buffer - 1)];
+ index = &buffer[-(decompSize >> 24)]; // FIXME - This is not correct code. It's optimized, but incorrect here. Fix it.
+ stopIndex = &buffer[-(decompSize & 0xFFFFFF)];
+
+ while ( index > stopIndex ) // index > stopIndex
{
- v1 = *((u32 *)end - 2);
- v2 = &end[*((u32 *)end - 1)];
- v3 = &end[-(v1 >> 24)];
- v4 = &end[-(v1 & 0xFFFFFF)];
- while ( v3 > v4 )
+ control = *(index-- - 1); // control (just scoping though)
+ for (int i=0; i<8; i++)
{
- v6 = *(v3-- - 1);
- v5 = v6;
- v7 = 8;
- while ( 1 )
+ if ( control & 0x80 ) // control & 0x80
{
- 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
+ v13 = *(index - 1);
+ v14 = *(index - 2);
+ index -= 2;
+ v15 = ((v14 | (v13 << 8)) & 0xFFFF0FFF) + 2;
+ v16 = v13 + 32;
+ do
{
- v9 = *(v3-- - 1);
- ret = v9;
- *(v2-- - 1) = v9;
+ ret = compressEndOff[v15];
+ *(compressEndOff-- - 1) = ret;
+ v16 -= 16;
}
- v5 *= 2;
- if ( v3 <= v4 )
- return ret;
+ while ( !(v16 < 0) );
+ }
+ else
+ {
+ v9 = *(index-- - 1);
+ ret = v9;
+ *(compressEndOff-- - 1) = v9;
}
+ control *= 2;
+ if ( index <= stopIndex )
+ return ret;
}
}
+
return ret;
}
static Result load_code(u64 progid, prog_addrs_t *shared, prog_addrs_t *original, u64 prog_handle, int is_compressed)
{
IFile file;
- FS_Archive archive;
+ FS_Path archivePath;
FS_Path path;
Result res;
u64 size;
u64 total;
- archive.id = ARCHIVE_SAVEDATA_AND_CONTENT2;
- archive.lowPath.type = PATH_BINARY;
- archive.lowPath.data = &prog_handle;
- archive.lowPath.size = 8;
- //archive.handle = prog_handle; // not needed
+ archivePath.type = PATH_BINARY;
+ archivePath.data = &prog_handle;
+ archivePath.size = 8;
+
path.type = PATH_BINARY;
path.data = CODE_PATH;
path.size = sizeof(CODE_PATH);
- if (R_FAILED(IFile_Open(&file, archive, path, FS_OPEN_READ)))
+ if (R_FAILED(IFile_Open(&file, ARCHIVE_SAVEDATA_AND_CONTENT2, archivePath, path, FS_OPEN_READ))) svcBreak(USERBREAK_ASSERT);
{
svcBreak(USERBREAK_ASSERT);
}
svcBreak(USERBREAK_ASSERT);
}
- // decompress
+ // decompress in place
if (is_compressed)
{
lzss_decompress((u8 *)shared->text_addr + size);
void __sync_init();
void __sync_fini();
void __system_initSyscalls();
-
+
void __ctru_exit()
{
__appExit();
__sync_fini();
svcExitProcess();
}
-
+
void initSystem()
{
__sync_init();
+++ /dev/null
-#include "memory.h"
-
-void memcpy(void *dest, const void *src, u32 size)
-{
- u8 *destc = (u8 *)dest;
- const u8 *srcc = (const u8 *)src;
-
- for(u32 i = 0; i < size; i++)
- destc[i] = srcc[i];
-}
-
+++ /dev/null
-#ifndef __MEMORY_H__
-#define __MEMORY_H__
-
-#include <3ds/types.h>
-
-void memcpy(void *dest, const void *src, u32 size);
-
-#endif
#include <3ds.h>
-#include "../memory.h"
#include "../patcher.h"
#include "../ifile.h"
#include <3ds.h>
-#include "../memory.h"
#include "../patcher.h"
#include "../ifile.h"
#include <3ds.h>
-#include "../memory.h"
#include "../patcher.h"
#include "../ifile.h"
#include <3ds.h>
-#include "../memory.h"
#include "../patcher.h"
#include "../ifile.h"
#include <3ds.h>
-#include "../memory.h"
#include "../patcher.h"
#include "../ifile.h"
#include <3ds.h>
-#include "../memory.h"
#include "../patcher.h"
#include "../ifile.h"
#include <3ds.h>
-#include "../memory.h"
#include "../patcher.h"
#include "../ifile.h"
#include <3ds.h>
-#include "../memory.h"
#include "../patcher.h"
#include "../ifile.h"
#include <3ds.h>
-#include "memory.h"
#include "patcher.h"
#include "ifile.h"
+#include "internal.h"
#ifndef PATH_MAX
#define PATH_MAX 255
static int fileOpen(IFile *file, FS_ArchiveID id, const char *path, int flags)
{
- FS_Archive archive;
+ FS_Path apath;
FS_Path ppath;
size_t len = strnlen(path, PATH_MAX);
- archive.id = id;
- archive.lowPath.type = PATH_EMPTY;
- archive.lowPath.size = 1;
- archive.lowPath.data = (u8 *)"";
+ apath.type = PATH_EMPTY;
+ apath.size = 1;
+ apath.data = (u8 *)"";
ppath.type = PATH_ASCII;
ppath.data = path;
ppath.size = len + 1;
- return IFile_Open(file, archive, ppath, flags);
-}
-
-static u32 secureInfoExists(void)
-{
- static u32 secureInfoExists = 0;
-
- if(!secureInfoExists)
- {
- IFile file;
- if(!fileOpen(&file, ARCHIVE_NAND_RW, "/sys/SecureInfo_C", FS_OPEN_READ))
- {
- secureInfoExists = 1;
- IFile_Close(&file);
- }
- }
-
- return secureInfoExists;
+ return IFile_Open(file, id, apath, ppath, flags);
}
static struct config_file config;
// Hexdump progId
for(int i=0; i < 8; i++) {
static const char hexDigits[] = "0123456789ABCDEF";
- progIdStr[i*2] = hexDigits[(((uint8_t*)progId)[i] >> 4) & 0xf];
- progIdStr[i*2+1] = hexDigits[ ((uint8_t*)progId)[i] & 0xf];
+ progIdStr[i*2] = hexDigits[(((uint8_t*)&progId)[i] >> 4) & 0xf];
+ progIdStr[i*2+1] = hexDigits[ ((uint8_t*)&progId)[i] & 0xf];
}
IFile file;
-#include <stdint.h>
-#include "../std/unused.h"
-#include "../std/memory.h"
-#include "../firm/firm.h"
-#include "../config.h"
-#include "../common.h"
+#include "patch_file.h"
// Do you like examples?
-int patch_test() {
+PATCH(example) {
fprintf(stderr, "Testing, testing, 1, 2, 3, 4..\n");
return 0;
-#include <stdint.h>
-#include "../std/unused.h"
-#include "../std/memory.h"
-#include "../firm/firm.h"
-#include "../firm/fcram.h"
-#include "../config.h"
-#include "../common.h"
+#include "patch_file.h"
-int patch_modules() {
+PATCH(modules) {
// TODO - load module cxi here
FILE* f = fopen(PATH_MODULES "/loader.cxi", "r");
if (!f) {
--- /dev/null
+#ifndef __PATCH_HEADER__
+#define __PATCH_HEADER__
+
+#include <stdint.h>
+
+// TODO - Temporary.
+#define BUILTIN 1
+
+#ifdef BUILTIN
+ // Build patch into CFW instead of as module.
+ #include "../std/unused.h"
+ #include "../std/memory.h"
+ #include "../firm/firm.h"
+ #include "../firm/fcram.h"
+ #include "../config.h"
+ #include "../common.h"
+
+ #define PATCH(name) int patch_##name ()
+#elif MODULE
+ // Build patch as loadable executable.
+
+ // TODO - Includes.
+
+ #define PATCH(name) int main()
+#else
+ #define PATCH(name) (void)
+
+ // Don't build patch; Exclude its code.
+#endif
+
+#endif
-#include <stdint.h>
-#include "../std/unused.h"
-#include "../std/memory.h"
-#include "../firm/firm.h"
-#include "../config.h"
-#include "../common.h"
+#include "patch_file.h"
// This patch applies the FIRM protection code needed for safe a9lh usage.
-int patch_firmprot() {
+PATCH(firmprot) {
uint8_t *firm_mem = (uint8_t*)firm_p9_exefs + sizeof(exefs_h) + firm_p9_exefs->fileHeaders[0].offset;
uint32_t size = firm_p9_exefs->fileHeaders[0].size;
-#include <stdint.h>
-#include "../std/unused.h"
-#include "../std/memory.h"
-#include "../firm/firm.h"
-#include "../config.h"
-#include "../common.h"
+#include "patch_file.h"
// This patch is responsible for fixing signature checks for the firmware.
-int patch_signatures() {
+PATCH(signatures) {
//Look for signature checks
uint8_t pat1[] = {0xC0, 0x1C, 0x76, 0xE7};
-#include <stdint.h>
-#include "../std/unused.h"
-#include "../std/memory.h"
-#include "../firm/firm.h"
-#include "../config.h"
-#include "../common.h"
+#include "patch_file.h"
// This patch handles replacement of services. This includes backdoor, but not just backdoor.
// Any service can be replaced provided there's enough space within the exception page.
uint32_t *freeSpace = NULL;
-int patch_services() {
+PATCH(services) {
// Make sure svcBackdoor is there.
uint8_t* arm11Section1 = (uint8_t*)firm_loc + firm_loc->section[1].offset;
uint32_t *exceptionsPage;