Merge branch 'master' into developer
Conflicts: source/firm.c source/firm.h source/patches.c source/patches.h
This commit is contained in:
commit
cb9576b10e
@ -22,13 +22,13 @@ LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
|
||||
|
||||
INCLUDE := $(foreach dir,$(LIBDIRS),-I$(dir)/include)
|
||||
|
||||
ARCH := -mcpu=mpcore -mfloat-abi=hard -mtp=soft
|
||||
CFLAGS := -Wall -Wextra -MMD -MP -marm $(ARCH) -fno-builtin -std=c11 -O2 -flto -ffast-math -mword-relocations \
|
||||
ASFLAGS := -mcpu=mpcore -mfloat-abi=hard -mtp=soft
|
||||
CFLAGS := -Wall -Wextra -MMD -MP -marm $(ASFLAGS) -fno-builtin -std=c11 -O2 -flto -ffast-math -mword-relocations \
|
||||
-ffunction-sections -fdata-sections $(INCLUDE) -DARM11 -D_3DS
|
||||
LDFLAGS := -Xlinker --defsym="__start__=0x14000000" -specs=3dsx.specs $(ARCH)
|
||||
LDFLAGS := -Xlinker --defsym="__start__=0x14000000" -specs=3dsx.specs $(ASFLAGS) -L$(DEVKITPRO)/libctru/lib
|
||||
|
||||
objects = $(patsubst $(dir_source)/%.c, $(dir_build)/%.o, \
|
||||
$(call rwildcard, $(dir_source), *.c))
|
||||
$(call rwildcard, $(dir_source), *.s *.c))
|
||||
|
||||
.PHONY: all
|
||||
all: ../$(dir_build)/$(name).cxi
|
||||
@ -48,4 +48,8 @@ $(dir_build)/memory.o : CFLAGS += -O3
|
||||
$(dir_build)/%.o: $(dir_source)/%.c
|
||||
@mkdir -p "$(@D)"
|
||||
$(COMPILE.c) $(OUTPUT_OPTION) $<
|
||||
|
||||
$(dir_build)/%.o: $(dir_source)/%.s
|
||||
@mkdir -p "$(@D)"
|
||||
$(COMPILE.s) $(OUTPUT_OPTION) $<
|
||||
include $(call rwildcard, $(dir_build), *.d)
|
||||
|
19
injector/source/CFWInfo.h
Normal file
19
injector/source/CFWInfo.h
Normal file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <3ds/types.h>
|
||||
|
||||
typedef struct __attribute__((packed))
|
||||
{
|
||||
char magic[4];
|
||||
|
||||
u8 versionMajor;
|
||||
u8 versionMinor;
|
||||
u8 versionBuild;
|
||||
u8 flags; /* bit 0: dev branch; bit 1: is release */
|
||||
|
||||
u32 commitHash;
|
||||
|
||||
u32 config;
|
||||
} CFWInfo;
|
||||
|
||||
int svcGetCFWInfo(CFWInfo *info);
|
9
injector/source/CFWInfo.s
Normal file
9
injector/source/CFWInfo.s
Normal file
@ -0,0 +1,9 @@
|
||||
.text
|
||||
.arm
|
||||
.align 4
|
||||
|
||||
.global svcGetCFWInfo
|
||||
.type svcGetCFWInfo, %function
|
||||
svcGetCFWInfo:
|
||||
svc 0x2e
|
||||
bx lr
|
@ -2,8 +2,9 @@
|
||||
#include "memory.h"
|
||||
#include "patcher.h"
|
||||
#include "ifile.h"
|
||||
#include "CFWInfo.h"
|
||||
|
||||
static CFWInfo info = {0};
|
||||
static CFWInfo info;
|
||||
|
||||
static int memcmp(const void *buf1, const void *buf2, u32 size)
|
||||
{
|
||||
@ -85,11 +86,6 @@ static int fileOpen(IFile *file, FS_ArchiveID archiveId, const char *path, int f
|
||||
return IFile_Open(file, archiveId, archivePath, filePath, flags);
|
||||
}
|
||||
|
||||
int __attribute__((naked)) svcGetCFWInfo(CFWInfo __attribute__((unused)) *out)
|
||||
{
|
||||
__asm__ volatile("svc 0x2E; bx lr");
|
||||
}
|
||||
|
||||
static void loadCFWInfo(void)
|
||||
{
|
||||
static bool infoLoaded = false;
|
||||
@ -97,11 +93,10 @@ static void loadCFWInfo(void)
|
||||
if(!infoLoaded)
|
||||
{
|
||||
svcGetCFWInfo(&info);
|
||||
|
||||
IFile file;
|
||||
if(BOOTCONFIG(5, 1) && R_SUCCEEDED(fileOpen(&file, ARCHIVE_SDMC, "/", FS_OPEN_READ))) //Init SD card if SAFE_MODE is being booted
|
||||
{
|
||||
IFile_Close(&file);
|
||||
}
|
||||
|
||||
infoLoaded = true;
|
||||
}
|
||||
|
@ -8,18 +8,4 @@
|
||||
#define MULTICONFIG(a) ((info.config >> (a * 2 + 6)) & 3)
|
||||
#define BOOTCONFIG(a, b) ((info.config >> a) & b)
|
||||
|
||||
typedef struct __attribute__((packed))
|
||||
{
|
||||
char magic[4];
|
||||
|
||||
u8 versionMajor;
|
||||
u8 versionMinor;
|
||||
u8 versionBuild;
|
||||
u8 flags; /* bit 0: dev branch; bit 1: is release */
|
||||
|
||||
u32 commitHash;
|
||||
|
||||
u32 config;
|
||||
} CFWInfo;
|
||||
|
||||
void patchCode(u64 progId, u8 *code, u32 size);
|
@ -23,7 +23,7 @@
|
||||
#include "memory.h"
|
||||
#include "cache.h"
|
||||
|
||||
extern u32 payloadSize; //defined in start.s
|
||||
extern u32 payloadSize; //Defined in start.s
|
||||
|
||||
void main(void)
|
||||
{
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "screen.h"
|
||||
#include "draw.h"
|
||||
#include "buttons.h"
|
||||
#include "pin.h"
|
||||
|
||||
bool readConfig(const char *configPath)
|
||||
{
|
||||
@ -64,7 +65,7 @@ void writeConfig(const char *configPath, u32 configTemp)
|
||||
}
|
||||
}
|
||||
|
||||
void configure(void)
|
||||
void configMenu(bool oldPinStatus)
|
||||
{
|
||||
initScreens();
|
||||
|
||||
@ -234,6 +235,11 @@ void configure(void)
|
||||
for(u32 i = 0; i < singleOptionsAmount; i++)
|
||||
configData.config |= (singleOptions[i].enabled ? 1 : 0) << (i + 16);
|
||||
|
||||
if(CONFIG(8)) newPin(oldPinStatus);
|
||||
else if(oldPinStatus) fileDelete("/luma/pin.bin");
|
||||
|
||||
//Wait for the pressed buttons to change
|
||||
while(HID_PAD == BUTTON_START);
|
||||
while(HID_PAD & PIN_BUTTONS);
|
||||
|
||||
chrono(2);
|
||||
}
|
@ -44,4 +44,4 @@ extern cfgData configData;
|
||||
|
||||
bool readConfig(const char *configPath);
|
||||
void writeConfig(const char *configPath, u32 configTemp);
|
||||
void configure(void);
|
||||
void configMenu(bool oldPinStatus);
|
||||
|
@ -457,13 +457,13 @@ void arm9Loader(u8 *arm9Section)
|
||||
}
|
||||
}
|
||||
|
||||
void computePINHash(u8 out[32], u8 *in, u32 blockCount)
|
||||
void computePinHash(u8 *out, u8 *in, u32 blockCount)
|
||||
{
|
||||
u8 __attribute__((aligned(4))) cid[0x10];
|
||||
u8 __attribute__((aligned(4))) cipherText[0x10];
|
||||
sdmmc_get_cid(1, (u32 *)cid);
|
||||
|
||||
aes_use_keyslot(4); // console-unique keyslot which keys are set by the Arm9 bootROM
|
||||
aes_use_keyslot(4); //Console-unique keyslot whose keys are set by the ARM9 bootROM
|
||||
aes(cipherText, in, blockCount, cid, AES_CBC_ENCRYPT_MODE, AES_INPUT_BE | AES_INPUT_NORMAL);
|
||||
|
||||
sha(out, cipherText, 0x10, SHA_256_MODE);
|
||||
|
@ -100,8 +100,7 @@
|
||||
#define SHA_1_HASH_SIZE (160 / 8)
|
||||
|
||||
extern u32 emuOffset;
|
||||
extern bool isN3DS;
|
||||
extern bool isDevUnit;
|
||||
extern bool isN3DS, isDevUnit;
|
||||
extern FirmwareSource firmSource;
|
||||
|
||||
void ctrNandInit(void);
|
||||
@ -109,5 +108,4 @@ u32 ctrNandRead(u32 sector, u32 sectorCount, u8 *outbuf);
|
||||
void setRSAMod0DerivedKeys(void);
|
||||
void decryptExeFs(u8 *inbuf);
|
||||
void arm9Loader(u8 *arm9Section);
|
||||
|
||||
void computePINHash(u8 out[32], u8 *in, u32 blockCount);
|
||||
void computePinHash(u8 *out, u8 *in, u32 blockCount);
|
@ -25,12 +25,12 @@
|
||||
#include "fatfs/sdmmc/sdmmc.h"
|
||||
#include "../build/emunandpatch.h"
|
||||
|
||||
void locateEmuNAND(u32 *off, u32 *head, FirmwareSource *emuNAND)
|
||||
void locateEmuNand(u32 *off, u32 *head, FirmwareSource *emuNand)
|
||||
{
|
||||
static u8 temp[0x200];
|
||||
|
||||
const u32 nandSize = getMMCDevice(0)->total_size;
|
||||
u32 nandOffset = *emuNAND == FIRMWARE_EMUNAND ? 0 :
|
||||
u32 nandOffset = *emuNand == FIRMWARE_EMUNAND ? 0 :
|
||||
(nandSize > 0x200000 ? 0x400000 : 0x200000);
|
||||
|
||||
//Check for RedNAND
|
||||
@ -53,12 +53,12 @@ void locateEmuNAND(u32 *off, u32 *head, FirmwareSource *emuNAND)
|
||||
or to SysNAND if there isn't any */
|
||||
else
|
||||
{
|
||||
*emuNAND = (*emuNAND == FIRMWARE_EMUNAND2) ? FIRMWARE_EMUNAND : FIRMWARE_SYSNAND;
|
||||
if(*emuNAND) locateEmuNAND(off, head, emuNAND);
|
||||
*emuNand = (*emuNand == FIRMWARE_EMUNAND2) ? FIRMWARE_EMUNAND : FIRMWARE_SYSNAND;
|
||||
if(*emuNand) locateEmuNand(off, head, emuNand);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void *getEmuCode(u8 *pos, u32 size)
|
||||
static inline void *getFreeK9Space(u8 *pos, u32 size)
|
||||
{
|
||||
const u8 pattern[] = {0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00};
|
||||
|
||||
@ -66,7 +66,7 @@ static inline void *getEmuCode(u8 *pos, u32 size)
|
||||
return memsearch(pos + 0x13500, pattern, size - 0x13500, 6) + 0x455;
|
||||
}
|
||||
|
||||
static inline u32 getSDMMC(u8 *pos, u32 size)
|
||||
static inline u32 getSdmmc(u8 *pos, u32 size)
|
||||
{
|
||||
//Look for struct code
|
||||
const u8 pattern[] = {0x21, 0x20, 0x18, 0x20};
|
||||
@ -75,7 +75,7 @@ static inline u32 getSDMMC(u8 *pos, u32 size)
|
||||
return *(u32 *)(off + 9) + *(u32 *)(off + 0xD);
|
||||
}
|
||||
|
||||
static inline void patchNANDRW(u8 *pos, u32 size, u32 branchOffset)
|
||||
static inline void patchNandRw(u8 *pos, u32 size, u32 branchOffset)
|
||||
{
|
||||
const u16 nandRedir[2] = {0x4C00, 0x47A0};
|
||||
|
||||
@ -93,7 +93,7 @@ static inline void patchNANDRW(u8 *pos, u32 size, u32 branchOffset)
|
||||
((u32 *)writeOffset)[1] = branchOffset;
|
||||
}
|
||||
|
||||
static inline void patchMPU(u8 *pos, u32 size)
|
||||
static inline void patchMpu(u8 *pos, u32 size)
|
||||
{
|
||||
const u32 mpuPatch[3] = {0x00360003, 0x00200603, 0x001C0603};
|
||||
|
||||
@ -107,26 +107,26 @@ static inline void patchMPU(u8 *pos, u32 size)
|
||||
off[9] = mpuPatch[2];
|
||||
}
|
||||
|
||||
void patchEmuNAND(u8 *arm9Section, u32 arm9SectionSize, u8 *process9Offset, u32 process9Size, u32 emuOffset, u32 emuHeader, u32 branchAdditive)
|
||||
void patchEmuNand(u8 *arm9Section, u32 arm9SectionSize, u8 *process9Offset, u32 process9Size, u32 emuHeader, u32 branchAdditive)
|
||||
{
|
||||
//Copy emuNAND code
|
||||
void *emuCodeOffset = getEmuCode(arm9Section, arm9SectionSize);
|
||||
memcpy(emuCodeOffset, emunand, emunand_size);
|
||||
void *freeK9Space = getFreeK9Space(arm9Section, arm9SectionSize);
|
||||
memcpy(freeK9Space, emunand, emunand_size);
|
||||
|
||||
//Add the data of the found emuNAND
|
||||
u32 *pos_offset = (u32 *)memsearch(emuCodeOffset, "NAND", emunand_size, 4),
|
||||
*pos_header = (u32 *)memsearch(emuCodeOffset, "NCSD", emunand_size, 4);
|
||||
*pos_offset = emuOffset;
|
||||
*pos_header = emuHeader;
|
||||
u32 *posOffset = (u32 *)memsearch(freeK9Space, "NAND", emunand_size, 4),
|
||||
*posHeader = (u32 *)memsearch(freeK9Space, "NCSD", emunand_size, 4);
|
||||
*posOffset = emuOffset;
|
||||
*posHeader = emuHeader;
|
||||
|
||||
//Find and add the SDMMC struct
|
||||
u32 *pos_sdmmc = (u32 *)memsearch(emuCodeOffset, "SDMC", emunand_size, 4);
|
||||
*pos_sdmmc = getSDMMC(process9Offset, process9Size);
|
||||
u32 *posSdmmc = (u32 *)memsearch(freeK9Space, "SDMC", emunand_size, 4);
|
||||
*posSdmmc = getSdmmc(process9Offset, process9Size);
|
||||
|
||||
//Add emuNAND hooks
|
||||
u32 branchOffset = (u32)emuCodeOffset - branchAdditive;
|
||||
patchNANDRW(process9Offset, process9Size, branchOffset);
|
||||
u32 branchOffset = (u32)freeK9Space - branchAdditive;
|
||||
patchNandRw(process9Offset, process9Size, branchOffset);
|
||||
|
||||
//Set MPU for emu code region
|
||||
patchMPU(arm9Section, arm9SectionSize);
|
||||
patchMpu(arm9Section, arm9SectionSize);
|
||||
}
|
@ -26,5 +26,7 @@
|
||||
|
||||
#define NCSD_MAGIC 0x4453434E
|
||||
|
||||
void locateEmuNAND(u32 *off, u32 *head, FirmwareSource *emuNAND);
|
||||
void patchEmuNAND(u8 *arm9Section, u32 arm9SectionSize, u8 *process9Offset, u32 process9Size, u32 emuOffset, u32 emuHeader, u32 branchAdditive);
|
||||
extern u32 emuOffset;
|
||||
|
||||
void locateEmuNand(u32 *off, u32 *head, FirmwareSource *emuNand);
|
||||
void patchEmuNand(u8 *arm9Section, u32 arm9SectionSize, u8 *process9Offset, u32 process9Size, u32 emuHeader, u32 branchAdditive);
|
@ -1,4 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "../../types.h"
|
114
source/firm.c
114
source/firm.c
@ -36,7 +36,7 @@
|
||||
#include "pin.h"
|
||||
#include "../build/injector.h"
|
||||
|
||||
extern u16 launchedFirmTIDLow[8]; //Defined in start.s
|
||||
extern u16 launchedFirmTidLow[8]; //Defined in start.s
|
||||
|
||||
static firmHeader *const firm = (firmHeader *)0x24000000;
|
||||
static const firmSectionHeader *section;
|
||||
@ -80,14 +80,14 @@ void main(void)
|
||||
}
|
||||
|
||||
//Determine if this is a firmlaunch boot
|
||||
if(launchedFirmTIDLow[5] != 0)
|
||||
if(launchedFirmTidLow[5] != 0)
|
||||
{
|
||||
if(needConfig == CREATE_CONFIGURATION) mcuReboot();
|
||||
|
||||
isFirmlaunch = true;
|
||||
|
||||
//'0' = NATIVE_FIRM, '1' = TWL_FIRM, '2' = AGB_FIRM
|
||||
firmType = launchedFirmTIDLow[7] == u'3' ? SAFE_FIRM : (FirmwareType)(launchedFirmTIDLow[5] - u'0');
|
||||
firmType = launchedFirmTidLow[7] == u'3' ? SAFE_FIRM : (FirmwareType)(launchedFirmTidLow[5] - u'0');
|
||||
|
||||
nandType = (FirmwareSource)BOOTCONFIG(0, 3);
|
||||
firmSource = (FirmwareSource)BOOTCONFIG(2, 1);
|
||||
@ -134,23 +134,14 @@ void main(void)
|
||||
//Boot options aren't being forced
|
||||
if(needConfig != DONT_CONFIGURE)
|
||||
{
|
||||
PINData pin;
|
||||
|
||||
bool pinExists = CONFIG(8) && readPin(&pin);
|
||||
|
||||
//If we get here we should check the PIN (if it exists) in all cases
|
||||
if(pinExists) verifyPin(&pin);
|
||||
bool pinExists = CONFIG(8) && verifyPin();
|
||||
|
||||
//If no configuration file exists or SELECT is held, load configuration menu
|
||||
bool shouldLoadConfigurationMenu = needConfig == CREATE_CONFIGURATION || ((pressed & BUTTON_SELECT) && !(pressed & BUTTON_L1));
|
||||
bool shouldLoadConfigMenu = needConfig == CREATE_CONFIGURATION || ((pressed & BUTTON_SELECT) && !(pressed & BUTTON_L1));
|
||||
|
||||
if(shouldLoadConfigurationMenu)
|
||||
if(shouldLoadConfigMenu)
|
||||
{
|
||||
configure();
|
||||
|
||||
if(!pinExists && CONFIG(8)) newPin();
|
||||
|
||||
chrono(2);
|
||||
configMenu(pinExists);
|
||||
|
||||
//Update pressed buttons
|
||||
pressed = HID_PAD;
|
||||
@ -163,13 +154,20 @@ void main(void)
|
||||
|
||||
//Flag to tell loader to init SD
|
||||
configTemp |= 1 << 5;
|
||||
|
||||
//If the PIN has been verified, wait to make it easier to press the SAFE_MODE combo
|
||||
if(pinExists && !shouldLoadConfigMenu)
|
||||
{
|
||||
while(HID_PAD & PIN_BUTTONS);
|
||||
chrono(2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(CONFIG(7) && loadSplash()) pressed = HID_PAD;
|
||||
|
||||
/* If L and R/A/Select or one of the single payload buttons are pressed,
|
||||
chainload an external payload (the PIN, if any, has been verified)*/
|
||||
chainload an external payload */
|
||||
bool shouldLoadPayload = (pressed & SINGLE_PAYLOAD_BUTTONS) || ((pressed & BUTTON_L1) && (pressed & L_PAYLOAD_BUTTONS));
|
||||
|
||||
if(shouldLoadPayload) loadPayload(pressed);
|
||||
@ -182,8 +180,8 @@ void main(void)
|
||||
//If R is pressed, boot the non-updated NAND with the FIRM of the opposite one
|
||||
if(pressed & BUTTON_R1)
|
||||
{
|
||||
nandType = (useSysAsDefault) ? FIRMWARE_EMUNAND : FIRMWARE_SYSNAND;
|
||||
firmSource = (useSysAsDefault) ? FIRMWARE_SYSNAND : FIRMWARE_EMUNAND;
|
||||
nandType = useSysAsDefault ? FIRMWARE_EMUNAND : FIRMWARE_SYSNAND;
|
||||
firmSource = useSysAsDefault ? FIRMWARE_SYSNAND : FIRMWARE_EMUNAND;
|
||||
}
|
||||
|
||||
/* Else, boot the NAND the user set to autoboot or the opposite one, depending on L,
|
||||
@ -204,13 +202,13 @@ void main(void)
|
||||
//If we need to boot emuNAND, make sure it exists
|
||||
if(nandType != FIRMWARE_SYSNAND)
|
||||
{
|
||||
locateEmuNAND(&emuOffset, &emuHeader, &nandType);
|
||||
locateEmuNand(&emuOffset, &emuHeader, &nandType);
|
||||
if(nandType == FIRMWARE_SYSNAND) firmSource = FIRMWARE_SYSNAND;
|
||||
}
|
||||
|
||||
//Same if we're using emuNAND as the FIRM source
|
||||
else if(firmSource != FIRMWARE_SYSNAND)
|
||||
locateEmuNAND(&emuOffset, &emuHeader, &firmSource);
|
||||
locateEmuNand(&emuOffset, &emuHeader, &firmSource);
|
||||
|
||||
if(!isFirmlaunch)
|
||||
{
|
||||
@ -218,7 +216,7 @@ void main(void)
|
||||
writeConfig(configPath, configTemp);
|
||||
}
|
||||
|
||||
u32 firmVersion = loadFirm(firmType);
|
||||
u32 firmVersion = loadFirm(&firmType, firmSource);
|
||||
|
||||
switch(firmType)
|
||||
{
|
||||
@ -226,7 +224,8 @@ void main(void)
|
||||
patchNativeFirm(firmVersion, nandType, emuHeader, isA9lh);
|
||||
break;
|
||||
case SAFE_FIRM:
|
||||
patchSafeFirm();
|
||||
case NATIVE_FIRM2X:
|
||||
if(isA9lh) patch2xNativeAndSafeFirm();
|
||||
break;
|
||||
default:
|
||||
//Skip patching on unsupported O3DS AGB/TWL FIRMs
|
||||
@ -237,7 +236,7 @@ void main(void)
|
||||
launchFirm(firmType);
|
||||
}
|
||||
|
||||
static inline u32 loadFirm(FirmwareType firmType)
|
||||
static inline u32 loadFirm(FirmwareType *firmType, FirmwareSource firmSource)
|
||||
{
|
||||
section = firm->section;
|
||||
const char *firmwareFiles[4] = {
|
||||
@ -251,24 +250,38 @@ static inline u32 loadFirm(FirmwareType firmType)
|
||||
|
||||
if(fileRead(firm, firmwareFiles[(u32)firmType]))
|
||||
{
|
||||
firmVersion = 0xffffffff;
|
||||
firmVersion = 0xFFFFFFFF;
|
||||
}
|
||||
else
|
||||
{
|
||||
firmVersion = firmRead(firm, (u32)firmType);
|
||||
|
||||
if(firmType == NATIVE_FIRM && !isN3DS)
|
||||
if(!isN3DS && *firmType == NATIVE_FIRM)
|
||||
{
|
||||
//We can't boot < 3.x NANDs (if firmware.bin is in the /luma folder, booting will fail)
|
||||
//We can't boot < 2.x SysNANDs and < 3.x EmuNANDs
|
||||
if(firmVersion < 0x18)
|
||||
error("An old unsupported NAND has been detected.\nLuma3DS is unable to boot it.");
|
||||
{
|
||||
if(firmSource != FIRMWARE_SYSNAND || firmVersion < 9)
|
||||
error("An old unsupported NAND has been detected.\nLuma3DS is unable to boot it");
|
||||
|
||||
//We can't boot a 4.x NATIVE_FIRM
|
||||
if(firmVersion < 0x25)
|
||||
if(BOOTCONFIG(5, 1)) error("SAFE_MODE is not supported on 2.x FIRM");
|
||||
|
||||
*firmType = NATIVE_FIRM2X;
|
||||
}
|
||||
|
||||
//We can't boot a 3.x/4.x NATIVE_FIRM, load one from SD
|
||||
else if(firmVersion < 0x25)
|
||||
{
|
||||
if(!fileRead(firm, "/luma/firmware.bin") || (((u32)section[2].address >> 8) & 0xFF) != 0x68)
|
||||
error("An old unsupported FIRM has been detected.\nCopy firmware.bin in /luma to boot");
|
||||
|
||||
//No assumption regarding FIRM version
|
||||
firmVersion = 0xFFFFFFFF;
|
||||
}
|
||||
decryptExeFs((u8 *)firm);
|
||||
}
|
||||
}
|
||||
|
||||
if(firmVersion != 0xFFFFFFFF) decryptExeFs((u8 *)firm);
|
||||
|
||||
return firmVersion;
|
||||
}
|
||||
@ -293,6 +306,13 @@ static inline void patchNativeFirm(u32 firmVersion, FirmwareSource nandType, u32
|
||||
process9MemAddr;
|
||||
u8 *process9Offset = getProcess9(arm9Section + 0x15000, section[2].size - 0x15000, &process9Size, &process9MemAddr);
|
||||
|
||||
//Find Kernel11 SVC table and free space locations
|
||||
u8 *freeK11Space;
|
||||
u32 *arm11SvcHandler,
|
||||
*arm11ExceptionsPage;
|
||||
|
||||
u32 *arm11SvcTable = getKernel11Info(arm11Section1, section[1].size, &freeK11Space, &arm11SvcHandler, &arm11ExceptionsPage);
|
||||
|
||||
//Apply signature patches
|
||||
patchSignatureChecks(process9Offset, process9Size);
|
||||
|
||||
@ -300,7 +320,7 @@ static inline void patchNativeFirm(u32 firmVersion, FirmwareSource nandType, u32
|
||||
if(nandType != FIRMWARE_SYSNAND)
|
||||
{
|
||||
u32 branchAdditive = (u32)firm + section[2].offset - (u32)section[2].address;
|
||||
patchEmuNAND(arm9Section, section[2].size, process9Offset, process9Size, emuOffset, emuHeader, branchAdditive);
|
||||
patchEmuNand(arm9Section, section[2].size, process9Offset, process9Size, emuHeader, branchAdditive);
|
||||
}
|
||||
|
||||
//Apply FIRM0/1 writes patches on sysNAND to protect A9LH
|
||||
@ -316,9 +336,11 @@ static inline void patchNativeFirm(u32 firmVersion, FirmwareSource nandType, u32
|
||||
patchTitleInstallMinVersionCheck(process9Offset, process9Size);
|
||||
|
||||
//Restore svcBackdoor
|
||||
reimplementSvcBackdoor(arm11Section1, section[1].size);
|
||||
reimplementSvcBackdoor(arm11Section1, arm11SvcTable, &freeK11Space);
|
||||
}
|
||||
|
||||
implementSvcGetCFWInfo(arm11Section1, arm11SvcTable, &freeK11Space);
|
||||
|
||||
//Apply UNITINFO patch
|
||||
if(DEV_OPTIONS == 1) patchUnitInfoValueSet(arm9Section, section[2].size);
|
||||
|
||||
@ -326,28 +348,27 @@ static inline void patchNativeFirm(u32 firmVersion, FirmwareSource nandType, u32
|
||||
{
|
||||
//Install arm11 exception handlers
|
||||
u32 stackAddress, codeSetOffset;
|
||||
u32 *exceptionsPage = getInfoForArm11ExceptionHandlers(arm11Section1, section[1].size, &stackAddress, &codeSetOffset);
|
||||
installArm11Handlers(exceptionsPage, stackAddress, codeSetOffset);
|
||||
getInfoForArm11ExceptionHandlers(arm11Section1, section[1].size, &stackAddress, &codeSetOffset);
|
||||
installArm11Handlers(arm11ExceptionsPage, stackAddress, codeSetOffset);
|
||||
|
||||
//Kernel9/Process9 debugging
|
||||
patchExceptionHandlersInstall(arm9Section, section[2].size);
|
||||
patchArm9ExceptionHandlersInstall(arm9Section, section[2].size);
|
||||
patchSvcBreak9(arm9Section, section[2].size, (u32)(section[2].address));
|
||||
patchKernel9Panic(arm9Section, section[2].size, NATIVE_FIRM);
|
||||
|
||||
//Stub svcBreak11 with "bkpt 65535"
|
||||
patchSvcBreak11(arm11Section1, section[1].size);
|
||||
patchSvcBreak11(arm11Section1, arm11SvcTable);
|
||||
|
||||
//Stub kernel11panic with "bkpt 65534"
|
||||
patchKernel11Panic(arm11Section1, section[1].size);
|
||||
}
|
||||
|
||||
if(CONFIG(9))
|
||||
{
|
||||
patchArm11SvcAccessChecks(arm11Section1, section[1].size);
|
||||
patchK11ModuleChecks(arm11Section1, section[1].size);
|
||||
patchArm11SvcAccessChecks(arm11SvcHandler);
|
||||
patchK11ModuleChecks(arm11Section1, section[1].size, &freeK11Space);
|
||||
patchP9AccessChecks(process9Offset, process9Size);
|
||||
}
|
||||
|
||||
implementSvcGetCFWInfo(arm11Section1, section[1].size);
|
||||
}
|
||||
|
||||
static inline void patchLegacyFirm(FirmwareType firmType)
|
||||
@ -367,18 +388,17 @@ static inline void patchLegacyFirm(FirmwareType firmType)
|
||||
if(DEV_OPTIONS != 2)
|
||||
{
|
||||
//Kernel9/Process9 debugging
|
||||
patchExceptionHandlersInstall(arm9Section, section[3].size);
|
||||
patchArm9ExceptionHandlersInstall(arm9Section, section[3].size);
|
||||
patchSvcBreak9(arm9Section, section[3].size, (u32)(section[3].address));
|
||||
patchKernel9Panic(arm9Section, section[3].size, firmType);
|
||||
}
|
||||
|
||||
applyLegacyFirmPatches((u8 *)firm, firmType);
|
||||
|
||||
if(firmType == TWL_FIRM && CONFIG(5))
|
||||
patchTwlBg((u8 *)firm + section[1].offset);
|
||||
if(firmType == TWL_FIRM && CONFIG(5)) patchTwlBg((u8 *)firm + section[1].offset);
|
||||
}
|
||||
|
||||
static inline void patchSafeFirm(void)
|
||||
static inline void patch2xNativeAndSafeFirm(void)
|
||||
{
|
||||
u8 *arm9Section = (u8 *)firm + section[2].offset;
|
||||
|
||||
@ -390,12 +410,12 @@ static inline void patchSafeFirm(void)
|
||||
|
||||
patchFirmWrites(arm9Section, section[2].size);
|
||||
}
|
||||
else patchFirmWriteSafe(arm9Section, section[2].size);
|
||||
else patchOldFirmWrites(arm9Section, section[2].size);
|
||||
|
||||
if(DEV_OPTIONS != 2)
|
||||
{
|
||||
//Kernel9/Process9 debugging
|
||||
patchExceptionHandlersInstall(arm9Section, section[2].size);
|
||||
patchArm9ExceptionHandlersInstall(arm9Section, section[2].size);
|
||||
patchSvcBreak9(arm9Section, section[2].size, (u32)(section[2].address));
|
||||
}
|
||||
}
|
||||
|
@ -54,9 +54,9 @@ typedef enum ConfigurationStatus
|
||||
CREATE_CONFIGURATION = 2
|
||||
} ConfigurationStatus;
|
||||
|
||||
static inline u32 loadFirm(FirmwareType firmType);
|
||||
static inline u32 loadFirm(FirmwareType *firmType, FirmwareSource firmSource);
|
||||
static inline void patchNativeFirm(u32 firmVersion, FirmwareSource nandType, u32 emuHeader, bool isA9lh);
|
||||
static inline void patchLegacyFirm(FirmwareType firmType);
|
||||
static inline void patchSafeFirm(void);
|
||||
static inline void patch2xNativeAndSafeFirm(void);
|
||||
static inline void copySection0AndInjectSystemModules(FirmwareType firmType);
|
||||
static inline void launchFirm(FirmwareType firmType);
|
@ -76,6 +76,11 @@ bool fileWrite(const void *buffer, const char *path, u32 size)
|
||||
return false;
|
||||
}
|
||||
|
||||
void fileDelete(const char *path)
|
||||
{
|
||||
f_unlink(path);
|
||||
}
|
||||
|
||||
void createDirectory(const char *path)
|
||||
{
|
||||
f_mkdir(path);
|
||||
|
@ -32,6 +32,7 @@ void mountFs(void);
|
||||
u32 fileRead(void *dest, const char *path);
|
||||
u32 getFileSize(const char *path);
|
||||
bool fileWrite(const void *buffer, const char *path, u32 size);
|
||||
void fileDelete(const char *path);
|
||||
void createDirectory(const char *path);
|
||||
void findDumpFile(const char *path, char *fileName);
|
||||
void loadPayload(u32 pressed);u32 firmRead(void *dest, u32 firmType);
|
402
source/patches.c
402
source/patches.c
@ -28,35 +28,6 @@
|
||||
#include "../build/k11modulespatch.h"
|
||||
#include "../build/twl_k11modulespatch.h"
|
||||
|
||||
static u32 *arm11ExceptionsPage = NULL;
|
||||
static u32 *arm11SvcTable = NULL;
|
||||
static u32 *arm11SvcHandler = NULL;
|
||||
|
||||
static u8 *freeK11Space = NULL;
|
||||
|
||||
static void findArm11ExceptionsPageAndSvcHandlerAndTable(u8 *pos, u32 size)
|
||||
{
|
||||
const u8 arm11ExceptionsPagePattern[] = {0x00, 0xB0, 0x9C, 0xE5};
|
||||
|
||||
if(arm11ExceptionsPage == NULL) arm11ExceptionsPage = (u32 *)memsearch(pos, arm11ExceptionsPagePattern, size, 4) - 0xB;
|
||||
if((arm11SvcTable == NULL || arm11SvcHandler == NULL) && arm11ExceptionsPage != NULL)
|
||||
{
|
||||
u32 svcOffset = (-((arm11ExceptionsPage[2] & 0xFFFFFF) << 2) & (0xFFFFFF << 2)) - 8; //Branch offset + 8 for prefetch
|
||||
arm11SvcHandler = arm11SvcTable = (u32 *)(pos + *(u32 *)(pos + 0xFFFF0008 - svcOffset - 0xFFF00000 + 8) - 0xFFF00000); //SVC handler address
|
||||
while(*arm11SvcTable) arm11SvcTable++; //Look for SVC0 (NULL)
|
||||
}
|
||||
}
|
||||
|
||||
static void findFreeK11Space(u8 *pos, u32 size)
|
||||
{
|
||||
if(freeK11Space == NULL)
|
||||
{
|
||||
const u8 pattern[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
|
||||
freeK11Space = memsearch(pos, pattern, size, 5) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
u8 *getProcess9(u8 *pos, u32 size, u32 *process9Size, u32 *process9MemAddr)
|
||||
{
|
||||
u8 *off = memsearch(pos, "ess9", size, 4);
|
||||
@ -68,22 +39,21 @@ u8 *getProcess9(u8 *pos, u32 size, u32 *process9Size, u32 *process9MemAddr)
|
||||
return off - 0x204 + (*(u32 *)(off - 0x64) * 0x200) + 0x200;
|
||||
}
|
||||
|
||||
u32* getInfoForArm11ExceptionHandlers(u8 *pos, u32 size, u32 *stackAddr, u32 *codeSetOffset)
|
||||
u32 *getKernel11Info(u8 *pos, u32 size, u8 **freeK11Space, u32 **arm11SvcHandler, u32 **arm11ExceptionsPage)
|
||||
{
|
||||
//This function has to succeed. Crash if it doesn't (we'll get an exception dump of it anyways)
|
||||
const u8 pattern[] = {0x00, 0xB0, 0x9C, 0xE5};
|
||||
|
||||
const u8 callExceptionDispatcherPattern[] = {0x0F, 0x00, 0xBD, 0xE8, 0x13, 0x00, 0x02, 0xF1};
|
||||
const u8 getTitleIDFromCodeSetPattern[] = {0xDC, 0x05, 0xC0, 0xE1, 0x20, 0x04, 0xA0, 0xE1};
|
||||
*arm11ExceptionsPage = (u32 *)memsearch(pos, pattern, size, 4) - 0xB;
|
||||
u32 svcOffset = (-(((*arm11ExceptionsPage)[2] & 0xFFFFFF) << 2) & (0xFFFFFF << 2)) - 8; //Branch offset + 8 for prefetch
|
||||
u32 *arm11SvcTable = (u32 *)(pos + *(u32 *)(pos + 0xFFFF0008 - svcOffset - 0xFFF00000 + 8) - 0xFFF00000); //SVC handler address
|
||||
*arm11SvcHandler = arm11SvcTable;
|
||||
while(*arm11SvcTable) arm11SvcTable++; //Look for SVC0 (NULL)
|
||||
|
||||
*stackAddr = *((u32 *)memsearch(pos, callExceptionDispatcherPattern, size, 8) + 3);
|
||||
const u8 pattern2[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
|
||||
u32 *loadCodeSet = (u32 *)memsearch(pos, getTitleIDFromCodeSetPattern, size, 8);
|
||||
while((*loadCodeSet >> 20) != 0xE59 || ((*loadCodeSet >> 12) & 0xF) != 0) //ldr r0, [rX, #offset]
|
||||
loadCodeSet--;
|
||||
*codeSetOffset = *loadCodeSet & 0xFFF;
|
||||
*freeK11Space = memsearch(pos, pattern2, size, 5) + 1;
|
||||
|
||||
findArm11ExceptionsPageAndSvcHandlerAndTable(pos, size);
|
||||
return arm11ExceptionsPage;
|
||||
return arm11SvcTable;
|
||||
}
|
||||
|
||||
void patchSignatureChecks(u8 *pos, u32 size)
|
||||
@ -134,168 +104,20 @@ void patchFirmWrites(u8 *pos, u32 size)
|
||||
off2[1] = writeBlock[1];
|
||||
}
|
||||
|
||||
void patchFirmWriteSafe(u8 *pos, u32 size)
|
||||
void patchOldFirmWrites(u8 *pos, u32 size)
|
||||
{
|
||||
const u16 writeBlockSafe[2] = {0x2400, 0xE01D};
|
||||
const u16 writeBlockOld[2] = {0x2400, 0xE01D};
|
||||
|
||||
//Look for FIRM writing code
|
||||
const u8 pattern[] = {0x04, 0x1E, 0x1D, 0xDB};
|
||||
|
||||
u16 *off = (u16 *)memsearch(pos, pattern, size, 4);
|
||||
|
||||
off[0] = writeBlockSafe[0];
|
||||
off[1] = writeBlockSafe[1];
|
||||
off[0] = writeBlockOld[0];
|
||||
off[1] = writeBlockOld[1];
|
||||
}
|
||||
|
||||
void patchExceptionHandlersInstall(u8 *pos, u32 size)
|
||||
{
|
||||
const u8 pattern[] = {
|
||||
0x18, 0x10, 0x80, 0xE5,
|
||||
0x10, 0x10, 0x80, 0xE5,
|
||||
0x20, 0x10, 0x80, 0xE5,
|
||||
0x28, 0x10, 0x80, 0xE5,
|
||||
}; //i.e when it stores ldr pc, [pc, #-4]
|
||||
|
||||
u32* off = (u32 *)(memsearch(pos, pattern, size, sizeof(pattern)));
|
||||
if(off == NULL) return;
|
||||
off += sizeof(pattern)/4;
|
||||
|
||||
u32 r0 = 0x08000000;
|
||||
|
||||
for(; *off != 0xE3A01040; off++) //Until mov r1, #0x40
|
||||
{
|
||||
if((*off >> 26) != 0x39 || ((*off >> 16) & 0xF) != 0 || ((*off >> 25) & 1) != 0 || ((*off >> 20) & 5) != 0)
|
||||
continue; //Discard everything that's not str rX, [r0, #imm](!)
|
||||
|
||||
int rD = (*off >> 12) & 0xF,
|
||||
offset = (*off & 0xFFF) * ((((*off >> 23) & 1) == 0) ? -1 : 1),
|
||||
writeback = (*off >> 21) & 1,
|
||||
pre = (*off >> 24) & 1;
|
||||
|
||||
u32 addr = r0 + ((pre || !writeback) ? offset : 0);
|
||||
if((addr & 7) != 0 && addr != 0x08000014 && addr != 0x08000004)
|
||||
*off = 0xE1A00000; //nop
|
||||
else
|
||||
*off = 0xE5800000 | (rD << 12) | (addr & 0xFFF); //Preserve IRQ and SVC handlers
|
||||
|
||||
if(!pre) addr += offset;
|
||||
if(writeback) r0 = addr;
|
||||
}
|
||||
}
|
||||
|
||||
void patchSvcBreak9(u8 *pos, u32 size, u32 k9addr)
|
||||
{
|
||||
//Stub svcBreak with "bkpt 65535" so we can debug the panic.
|
||||
//Thanks @yellows8 and others for mentioning this idea on #3dsdev.
|
||||
const u8 svcHandlerPattern[] = {0x00, 0xE0, 0x4F, 0xE1}; //mrs lr, spsr
|
||||
|
||||
u32 *arm9SvcTable = (u32 *)memsearch(pos, svcHandlerPattern, size, 4);
|
||||
while(*arm9SvcTable) arm9SvcTable++; //Look for SVC0 (NULL)
|
||||
u32 *addr = (u32 *)(pos + arm9SvcTable[0x3C] - k9addr);
|
||||
*addr = 0xE12FFF7F;
|
||||
}
|
||||
|
||||
void patchSvcBreak11(u8 *pos, u32 size)
|
||||
{
|
||||
//Same as above, for NFIRM arm11
|
||||
|
||||
findArm11ExceptionsPageAndSvcHandlerAndTable(pos, size);
|
||||
u32 *addr = (u32 *)(pos + arm11SvcTable[0x3C] - 0xFFF00000);
|
||||
*addr = 0xE12FFF7F;
|
||||
}
|
||||
|
||||
void patchKernel9Panic(u8 *pos, u32 size, FirmwareType firmType)
|
||||
{
|
||||
if(firmType == TWL_FIRM || firmType == AGB_FIRM)
|
||||
{
|
||||
u8 *off = pos + ((isN3DS) ? 0x723C : 0x69A8);
|
||||
*(u16 *)off = 0x4778; //bx pc
|
||||
*(u16 *)(off + 2) = 0x46C0; //nop
|
||||
*(u32 *)(off + 4) = 0xE12FFF7E; //bkpt 65534
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
const u8 pattern[] = {0x00, 0x20, 0xA0, 0xE3, 0x02, 0x30, 0xA0, 0xE1, 0x02, 0x10, 0xA0, 0xE1, 0x05, 0x00, 0xA0, 0xE3};
|
||||
|
||||
u32 *off = (u32 *)memsearch(pos, pattern, size, 16);
|
||||
*off = 0xE12FFF7E;
|
||||
}
|
||||
}
|
||||
|
||||
void patchKernel11Panic(u8 *pos, u32 size)
|
||||
{
|
||||
const u8 pattern[] = {0x02, 0x0B, 0x44, 0xE2, 0x00, 0x10, 0x90, 0xE5};
|
||||
|
||||
u32 *off = (u32 *)memsearch(pos, pattern, size, 8);
|
||||
*off = 0xE12FFF7E;
|
||||
}
|
||||
|
||||
void patchArm11SvcAccessChecks(u8 *pos, u32 size)
|
||||
{
|
||||
findArm11ExceptionsPageAndSvcHandlerAndTable(pos, size);
|
||||
|
||||
u32 *off = arm11SvcHandler;
|
||||
while(*off != 0xE11A0E1B) off++; //TST R10, R11,LSL LR
|
||||
|
||||
*off = 0xE3B0A001; //MOVS R10, #1
|
||||
}
|
||||
|
||||
//It's mainly Subv's code here:
|
||||
void patchK11ModuleChecks(u8 *pos, u32 size)
|
||||
{
|
||||
// We have to detour a function in the ARM11 kernel because builtin modules
|
||||
// are compressed in memory and are only decompressed at runtime.
|
||||
|
||||
findFreeK11Space(pos, size);
|
||||
u8 *freeSpace = freeK11Space;
|
||||
freeK11Space += k11modules_size;
|
||||
|
||||
// Inject our code into the free space
|
||||
memcpy(freeSpace, k11modules, k11modules_size);
|
||||
|
||||
// Find the code that decompresses the .code section of the builtin modules and detour it with a jump to our code
|
||||
const u8 pattern[] = { 0x00, 0x00, 0x94, 0xE5, 0x18, 0x10, 0x90, 0xE5, 0x28, 0x20,
|
||||
0x90, 0xE5, 0x48, 0x00, 0x9D, 0xE5 };
|
||||
|
||||
u8 *off = memsearch(pos, pattern, size, 16);
|
||||
|
||||
// We couldn't find the code that decompresses the module
|
||||
if (off == NULL)
|
||||
return;
|
||||
|
||||
// Inject a jump instruction to our code at the offset we found
|
||||
// Construct a jump (BL) instruction to our code
|
||||
u32 offset = ((((u32)freeSpace) - ((u32)off + 8)) >> 2) & 0xFFFFFF;
|
||||
u32 instruction = offset | (1 << 24) | (0x5 << 25) | (0xE << 28);
|
||||
|
||||
// Write our jump
|
||||
memcpy(off, &instruction, 4);
|
||||
}
|
||||
|
||||
void patchP9AccessChecks(u8 *pos, u32 size)
|
||||
{
|
||||
const u8 pattern[] = {0xE0, 0x00, 0x40, 0x39, 0x08, 0x58};
|
||||
|
||||
u16 *off = (u16 *)memsearch(pos, pattern, size, 6) - 7;
|
||||
|
||||
off[0] = 0x2001; //mov r0, #1
|
||||
off[1] = 0x4770; //bx lr
|
||||
}
|
||||
|
||||
|
||||
void patchUnitInfoValueSet(u8 *pos, u32 size)
|
||||
{
|
||||
//Look for UNITINFO value being set during kernel sync
|
||||
const u8 pattern[] = {0x01, 0x10, 0xA0, 0x13};
|
||||
|
||||
u8 *off = memsearch(pos, pattern, size, 4);
|
||||
|
||||
off[0] = (isDevUnit) ? 0 : 1;
|
||||
off[3] = 0xE3;
|
||||
}
|
||||
|
||||
void reimplementSvcBackdoor(u8 *pos, u32 size)
|
||||
void reimplementSvcBackdoor(u8 *pos, u32 *arm11SvcTable, u8 **freeK11Space)
|
||||
{
|
||||
//Official implementation of svcBackdoor
|
||||
const u8 svcBackdoor[40] = {0xFF, 0x10, 0xCD, 0xE3, //bic r1, sp, #0xff
|
||||
@ -309,26 +131,21 @@ void reimplementSvcBackdoor(u8 *pos, u32 size)
|
||||
0x00, 0xD0, 0xA0, 0xE1, //mov sp, r0
|
||||
0x11, 0xFF, 0x2F, 0xE1}; //bx r1
|
||||
|
||||
findArm11ExceptionsPageAndSvcHandlerAndTable(pos, size);
|
||||
|
||||
if(!arm11SvcTable[0x7B])
|
||||
{
|
||||
findFreeK11Space(pos, size);
|
||||
memcpy(*freeK11Space, svcBackdoor, 40);
|
||||
|
||||
memcpy(freeK11Space, svcBackdoor, 40);
|
||||
|
||||
arm11SvcTable[0x7B] = 0xFFF00000 + freeK11Space - pos;
|
||||
freeK11Space += 40;
|
||||
arm11SvcTable[0x7B] = 0xFFF00000 + *freeK11Space - pos;
|
||||
(*freeK11Space) += 40;
|
||||
}
|
||||
}
|
||||
|
||||
void implementSvcGetCFWInfo(u8 *pos, u32 size)
|
||||
|
||||
void implementSvcGetCFWInfo(u8 *pos, u32 *arm11SvcTable, u8 **freeK11Space)
|
||||
{
|
||||
findFreeK11Space(pos, size);
|
||||
memcpy(*freeK11Space, svcGetCFWInfo, svcGetCFWInfo_size);
|
||||
|
||||
memcpy(freeK11Space, svcGetCFWInfo, svcGetCFWInfo_size);
|
||||
|
||||
CFWInfo *info = (CFWInfo *)memsearch(freeK11Space, "LUMA", svcGetCFWInfo_size, 4);
|
||||
CFWInfo *info = (CFWInfo *)memsearch(*freeK11Space, "LUMA", svcGetCFWInfo_size, 4);
|
||||
|
||||
const char *rev = REVISION;
|
||||
bool isRelease;
|
||||
@ -344,12 +161,10 @@ void implementSvcGetCFWInfo(u8 *pos, u32 size)
|
||||
}
|
||||
else isRelease = rev[4] == 0;
|
||||
|
||||
info->flags = 1 /* dev branch */ | (((isRelease) ? 1 : 0) << 1) /* is release */;
|
||||
info->flags = 1 /* dev branch */ | ((isRelease ? 1 : 0) << 1) /* is release */;
|
||||
|
||||
findArm11ExceptionsPageAndSvcHandlerAndTable(pos, size);
|
||||
|
||||
arm11SvcTable[0x2E] = 0xFFF00000 + freeK11Space - pos; //Stubbed svc
|
||||
freeK11Space += svcGetCFWInfo_size;
|
||||
arm11SvcTable[0x2E] = 0xFFF00000 + *freeK11Space - pos; //Stubbed svc
|
||||
(*freeK11Space) += svcGetCFWInfo_size;
|
||||
}
|
||||
|
||||
void patchTitleInstallMinVersionCheck(u8 *pos, u32 size)
|
||||
@ -404,15 +219,15 @@ void applyLegacyFirmPatches(u8 *pos, FirmwareType firmType)
|
||||
|
||||
void patchTwlBg(u8 *pos)
|
||||
{
|
||||
u8 *dst = pos + ((isN3DS) ? 0xFEA4 : 0xFCA0);
|
||||
u8 *dst = pos + (isN3DS ? 0xFEA4 : 0xFCA0);
|
||||
|
||||
memcpy(dst, twl_k11modules, twl_k11modules_size); //Install K11 hook
|
||||
|
||||
u32 *off = (u32 *)memsearch(dst, "LAUN", twl_k11modules_size, 4);
|
||||
*off = (isN3DS) ? 0xCDE88 : 0xCD5F8; //Dev SRL launcher offset
|
||||
*off = isN3DS ? 0xCDE88 : 0xCD5F8; //Dev SRL launcher offset
|
||||
|
||||
u16 *src1 = (u16 *)(pos + ((isN3DS) ? 0xE38 : 0xE3C)),
|
||||
*src2 = (u16 *)(pos + ((isN3DS) ? 0xE54 : 0xE58));
|
||||
u16 *src1 = (u16 *)(pos + (isN3DS ? 0xE38 : 0xE3C)),
|
||||
*src2 = (u16 *)(pos + (isN3DS ? 0xE54 : 0xE58));
|
||||
|
||||
//Construct BLX instructions:
|
||||
src1[0] = 0xF000 | ((((u32)dst - (u32)src1 - 4) & (0xFFF << 11)) >> 12);
|
||||
@ -421,3 +236,162 @@ void patchTwlBg(u8 *pos)
|
||||
src2[0] = 0xF000 | ((((u32)dst - (u32)src2 - 4) & (0xFFF << 11)) >> 12);
|
||||
src2[1] = 0xE800 | ((((u32)dst - (u32)src2 - 4) & 0xFFF) >> 1);
|
||||
}
|
||||
|
||||
void getInfoForArm11ExceptionHandlers(u8 *pos, u32 size, u32 *stackAddr, u32 *codeSetOffset)
|
||||
{
|
||||
//This function has to succeed. Crash if it doesn't (we'll get an exception dump of it anyways)
|
||||
|
||||
const u8 callExceptionDispatcherPattern[] = {0x0F, 0x00, 0xBD, 0xE8, 0x13, 0x00, 0x02, 0xF1};
|
||||
const u8 getTitleIDFromCodeSetPattern[] = {0xDC, 0x05, 0xC0, 0xE1, 0x20, 0x04, 0xA0, 0xE1};
|
||||
|
||||
*stackAddr = *((u32 *)memsearch(pos, callExceptionDispatcherPattern, size, 8) + 3);
|
||||
|
||||
u32 *loadCodeSet = (u32 *)memsearch(pos, getTitleIDFromCodeSetPattern, size, 8);
|
||||
while((*loadCodeSet >> 20) != 0xE59 || ((*loadCodeSet >> 12) & 0xF) != 0) //ldr r0, [rX, #offset]
|
||||
loadCodeSet--;
|
||||
*codeSetOffset = *loadCodeSet & 0xFFF;
|
||||
}
|
||||
|
||||
void patchArm9ExceptionHandlersInstall(u8 *pos, u32 size)
|
||||
{
|
||||
const u8 pattern[] = {
|
||||
0x18, 0x10, 0x80, 0xE5,
|
||||
0x10, 0x10, 0x80, 0xE5,
|
||||
0x20, 0x10, 0x80, 0xE5,
|
||||
0x28, 0x10, 0x80, 0xE5,
|
||||
}; //i.e when it stores ldr pc, [pc, #-4]
|
||||
|
||||
u32* off = (u32 *)(memsearch(pos, pattern, size, sizeof(pattern)));
|
||||
if(off == NULL) return;
|
||||
off += sizeof(pattern)/4;
|
||||
|
||||
u32 r0 = 0x08000000;
|
||||
|
||||
for(; *off != 0xE3A01040; off++) //Until mov r1, #0x40
|
||||
{
|
||||
if((*off >> 26) != 0x39 || ((*off >> 16) & 0xF) != 0 || ((*off >> 25) & 1) != 0 || ((*off >> 20) & 5) != 0)
|
||||
continue; //Discard everything that's not str rX, [r0, #imm](!)
|
||||
|
||||
int rD = (*off >> 12) & 0xF,
|
||||
offset = (*off & 0xFFF) * ((((*off >> 23) & 1) == 0) ? -1 : 1),
|
||||
writeback = (*off >> 21) & 1,
|
||||
pre = (*off >> 24) & 1;
|
||||
|
||||
u32 addr = r0 + ((pre || !writeback) ? offset : 0);
|
||||
if((addr & 7) != 0 && addr != 0x08000014 && addr != 0x08000004)
|
||||
*off = 0xE1A00000; //nop
|
||||
else
|
||||
*off = 0xE5800000 | (rD << 12) | (addr & 0xFFF); //Preserve IRQ and SVC handlers
|
||||
|
||||
if(!pre) addr += offset;
|
||||
if(writeback) r0 = addr;
|
||||
}
|
||||
}
|
||||
|
||||
void patchSvcBreak9(u8 *pos, u32 size, u32 k9addr)
|
||||
{
|
||||
//Stub svcBreak with "bkpt 65535" so we can debug the panic.
|
||||
//Thanks @yellows8 and others for mentioning this idea on #3dsdev.
|
||||
const u8 svcHandlerPattern[] = {0x00, 0xE0, 0x4F, 0xE1}; //mrs lr, spsr
|
||||
|
||||
u32 *arm9SvcTable = (u32 *)memsearch(pos, svcHandlerPattern, size, 4);
|
||||
while(*arm9SvcTable) arm9SvcTable++; //Look for SVC0 (NULL)
|
||||
u32 *addr = (u32 *)(pos + arm9SvcTable[0x3C] - k9addr);
|
||||
*addr = 0xE12FFF7F;
|
||||
}
|
||||
|
||||
void patchSvcBreak11(u8 *pos, u32 *arm11SvcTable)
|
||||
{
|
||||
//Same as above, for NFIRM arm11
|
||||
|
||||
u32 *addr = (u32 *)(pos + arm11SvcTable[0x3C] - 0xFFF00000);
|
||||
*addr = 0xE12FFF7F;
|
||||
}
|
||||
|
||||
void patchKernel9Panic(u8 *pos, u32 size, FirmwareType firmType)
|
||||
{
|
||||
if(firmType == TWL_FIRM || firmType == AGB_FIRM)
|
||||
{
|
||||
u8 *off = pos + ((isN3DS) ? 0x723C : 0x69A8);
|
||||
*(u16 *)off = 0x4778; //bx pc
|
||||
*(u16 *)(off + 2) = 0x46C0; //nop
|
||||
*(u32 *)(off + 4) = 0xE12FFF7E; //bkpt 65534
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
const u8 pattern[] = {0x00, 0x20, 0xA0, 0xE3, 0x02, 0x30, 0xA0, 0xE1, 0x02, 0x10, 0xA0, 0xE1, 0x05, 0x00, 0xA0, 0xE3};
|
||||
|
||||
u32 *off = (u32 *)memsearch(pos, pattern, size, 16);
|
||||
*off = 0xE12FFF7E;
|
||||
}
|
||||
}
|
||||
|
||||
void patchKernel11Panic(u8 *pos, u32 size)
|
||||
{
|
||||
const u8 pattern[] = {0x02, 0x0B, 0x44, 0xE2, 0x00, 0x10, 0x90, 0xE5};
|
||||
|
||||
u32 *off = (u32 *)memsearch(pos, pattern, size, 8);
|
||||
*off = 0xE12FFF7E;
|
||||
}
|
||||
|
||||
void patchArm11SvcAccessChecks(u32 *arm11SvcHandler)
|
||||
{
|
||||
u32 *off = arm11SvcHandler;
|
||||
while(*off != 0xE11A0E1B) off++; //TST R10, R11,LSL LR
|
||||
|
||||
*off = 0xE3B0A001; //MOVS R10, #1
|
||||
}
|
||||
|
||||
//It's mainly Subv's code here:
|
||||
void patchK11ModuleChecks(u8 *pos, u32 size, u8 **freeK11Space)
|
||||
{
|
||||
// We have to detour a function in the ARM11 kernel because builtin modules
|
||||
// are compressed in memory and are only decompressed at runtime.
|
||||
|
||||
u8 *freeSpace = *freeK11Space;
|
||||
*freeK11Space += k11modules_size;
|
||||
|
||||
// Inject our code into the free space
|
||||
memcpy(freeSpace, k11modules, k11modules_size);
|
||||
|
||||
// Find the code that decompresses the .code section of the builtin modules and detour it with a jump to our code
|
||||
const u8 pattern[] = { 0x00, 0x00, 0x94, 0xE5, 0x18, 0x10, 0x90, 0xE5, 0x28, 0x20,
|
||||
0x90, 0xE5, 0x48, 0x00, 0x9D, 0xE5 };
|
||||
|
||||
u8 *off = memsearch(pos, pattern, size, 16);
|
||||
|
||||
// We couldn't find the code that decompresses the module
|
||||
if (off == NULL)
|
||||
return;
|
||||
|
||||
// Inject a jump instruction to our code at the offset we found
|
||||
// Construct a jump (BL) instruction to our code
|
||||
u32 offset = ((((u32)freeSpace) - ((u32)off + 8)) >> 2) & 0xFFFFFF;
|
||||
u32 instruction = offset | (1 << 24) | (0x5 << 25) | (0xE << 28);
|
||||
|
||||
// Write our jump
|
||||
memcpy(off, &instruction, 4);
|
||||
}
|
||||
|
||||
void patchP9AccessChecks(u8 *pos, u32 size)
|
||||
{
|
||||
const u8 pattern[] = {0xE0, 0x00, 0x40, 0x39, 0x08, 0x58};
|
||||
|
||||
u16 *off = (u16 *)memsearch(pos, pattern, size, 6) - 7;
|
||||
|
||||
off[0] = 0x2001; //mov r0, #1
|
||||
off[1] = 0x4770; //bx lr
|
||||
}
|
||||
|
||||
|
||||
void patchUnitInfoValueSet(u8 *pos, u32 size)
|
||||
{
|
||||
//Look for UNITINFO value being set during kernel sync
|
||||
const u8 pattern[] = {0x01, 0x10, 0xA0, 0x13};
|
||||
|
||||
u8 *off = memsearch(pos, pattern, size, 4);
|
||||
|
||||
off[0] = (isDevUnit) ? 0 : 1;
|
||||
off[3] = 0xE3;
|
||||
}
|
@ -50,22 +50,24 @@ typedef struct __attribute__((packed))
|
||||
extern bool isN3DS, isDevUnit;
|
||||
|
||||
u8 *getProcess9(u8 *pos, u32 size, u32 *process9Size, u32 *process9MemAddr);
|
||||
u32* getInfoForArm11ExceptionHandlers(u8 *pos, u32 size, u32 *stackAddr, u32 *codeSetOffset);
|
||||
u32 *getKernel11Info(u8 *pos, u32 size, u8 **freeK11Space, u32 **arm11SvcHandler, u32 **arm11ExceptionsPage);
|
||||
void patchSignatureChecks(u8 *pos, u32 size);
|
||||
void patchTitleInstallMinVersionCheck(u8 *pos, u32 size);
|
||||
void patchFirmlaunches(u8 *pos, u32 size, u32 process9MemAddr);
|
||||
void patchFirmWrites(u8 *pos, u32 size);
|
||||
void patchFirmWriteSafe(u8 *pos, u32 size);
|
||||
void patchExceptionHandlersInstall(u8 *pos, u32 size);
|
||||
void patchSvcBreak9(u8 *pos, u32 size, u32 k9addr);
|
||||
void patchSvcBreak11(u8 *pos, u32 size);
|
||||
void patchKernel9Panic(u8 *pos, u32 size, FirmwareType firmType);
|
||||
void patchKernel11Panic(u8 *pos, u32 size);
|
||||
void patchArm11SvcAccessChecks(u8 *pos, u32 size);
|
||||
void patchK11ModuleChecks(u8 *pos, u32 size);
|
||||
void patchP9AccessChecks(u8 *pos, u32 size);
|
||||
void patchUnitInfoValueSet(u8 *pos, u32 size);
|
||||
void reimplementSvcBackdoor(u8 *pos, u32 size);
|
||||
void implementSvcGetCFWInfo(u8 *pos, u32 size);
|
||||
void patchOldFirmWrites(u8 *pos, u32 size);
|
||||
void reimplementSvcBackdoor(u8 *pos, u32 *arm11SvcTable, u8 **freeK11Space);
|
||||
void implementSvcGetCFWInfo(u8 *pos, u32 *arm11SvcTable, u8 **freeK11Space);
|
||||
void applyLegacyFirmPatches(u8 *pos, FirmwareType firmType);
|
||||
void patchTwlBg(u8 *pos);
|
||||
|
||||
void getInfoForArm11ExceptionHandlers(u8 *pos, u32 size, u32 *stackAddr, u32 *codeSetOffset);
|
||||
void patchArm9ExceptionHandlersInstall(u8 *pos, u32 size);
|
||||
void patchSvcBreak9(u8 *pos, u32 size, u32 k9addr);
|
||||
void patchSvcBreak11(u8 *pos, u32 *arm11SvcTable);
|
||||
void patchKernel9Panic(u8 *pos, u32 size, FirmwareType firmType);
|
||||
void patchKernel11Panic(u8 *pos, u32 size);
|
||||
void patchArm11SvcAccessChecks(u32 *arm11SvcHandler);
|
||||
void patchK11ModuleChecks(u8 *pos, u32 size, u8 **freeK11Space);
|
||||
void patchP9AccessChecks(u8 *pos, u32 size);
|
||||
void patchUnitInfoValueSet(u8 *pos, u32 size);
|
98
source/pin.c
98
source/pin.c
@ -34,43 +34,29 @@
|
||||
#include "pin.h"
|
||||
#include "crypto.h"
|
||||
|
||||
bool readPin(PINData *out)
|
||||
{
|
||||
if(fileRead(out, "/luma/pin.bin") != sizeof(PINData) ||
|
||||
memcmp(out->magic, "PINF", 4) != 0 ||
|
||||
out->formatVersionMajor != PIN_VERSIONMAJOR ||
|
||||
out->formatVersionMinor != PIN_VERSIONMINOR)
|
||||
return false;
|
||||
|
||||
u8 __attribute__((aligned(4))) zeroes[16] = {0};
|
||||
u8 __attribute__((aligned(4))) tmp[32];
|
||||
|
||||
computePINHash(tmp, zeroes, 1);
|
||||
|
||||
return memcmp(out->testHash, tmp, 32) == 0; //test vector verification (SD card has (or hasn't) been used on another console)
|
||||
}
|
||||
|
||||
static inline char PINKeyToLetter(u32 pressed)
|
||||
static char pinKeyToLetter(u32 pressed)
|
||||
{
|
||||
const char keys[] = "AB--------XY";
|
||||
|
||||
u32 i;
|
||||
__asm__ volatile("clz %[i], %[pressed]" : [i] "=r" (i) : [pressed] "r" (pressed));
|
||||
for(i = 31; pressed > 1; i--) pressed /= 2;
|
||||
|
||||
return keys[31 - i];
|
||||
}
|
||||
|
||||
void newPin(void)
|
||||
void newPin(bool allowSkipping)
|
||||
{
|
||||
clearScreens();
|
||||
|
||||
drawString("Enter your NEW PIN: ", 10, 10, COLOR_WHITE);
|
||||
char *title = allowSkipping ? "Press START to skip or enter a new PIN" : "Enter a new PIN to proceed";
|
||||
drawString(title, 10, 10, COLOR_TITLE);
|
||||
drawString("PIN: ", 10, 10 + 2 * SPACING_Y, COLOR_WHITE);
|
||||
|
||||
// Set the default value as 0x00 so we can check if there are any unentered characters.
|
||||
u8 __attribute__((aligned(4))) enteredPassword[16 * ((PIN_LENGTH + 15) / 16)] = {0}; // pad to AES block length
|
||||
//Pad to AES block length with zeroes
|
||||
u8 __attribute__((aligned(4))) enteredPassword[16 * ((PIN_LENGTH + 15) / 16)] = {0};
|
||||
|
||||
u32 cnt = 0;
|
||||
int charDrawPos = 20 * SPACING_X;
|
||||
int charDrawPos = 5 * SPACING_X;
|
||||
|
||||
while(cnt < PIN_LENGTH)
|
||||
{
|
||||
@ -79,16 +65,19 @@ void newPin(void)
|
||||
{
|
||||
pressed = waitInput();
|
||||
}
|
||||
while(!(pressed & PIN_BUTTONS & ~BUTTON_START));
|
||||
while(!(pressed & PIN_BUTTONS));
|
||||
|
||||
pressed &= PIN_BUTTONS & ~BUTTON_START;
|
||||
pressed &= PIN_BUTTONS;
|
||||
if(!allowSkipping) pressed &= ~BUTTON_START;
|
||||
|
||||
if(pressed & BUTTON_START) return;
|
||||
if(!pressed) continue;
|
||||
char key = PINKeyToLetter(pressed);
|
||||
enteredPassword[cnt++] = (u8)key; // add character to password.
|
||||
|
||||
// visualize character on screen.
|
||||
drawCharacter(key, 10 + charDrawPos, 10, COLOR_WHITE);
|
||||
char key = pinKeyToLetter(pressed);
|
||||
enteredPassword[cnt++] = (u8)key; //Add character to password
|
||||
|
||||
//Visualize character on screen
|
||||
drawCharacter(key, 10 + charDrawPos, 10 + 2 * SPACING_Y, COLOR_WHITE);
|
||||
charDrawPos += 2 * SPACING_X;
|
||||
}
|
||||
|
||||
@ -100,10 +89,10 @@ void newPin(void)
|
||||
pin.formatVersionMajor = PIN_VERSIONMAJOR;
|
||||
pin.formatVersionMinor = PIN_VERSIONMINOR;
|
||||
|
||||
computePINHash(tmp, zeroes, 1);
|
||||
computePinHash(tmp, zeroes, 1);
|
||||
memcpy(pin.testHash, tmp, 32);
|
||||
|
||||
computePINHash(tmp, enteredPassword, (PIN_LENGTH + 15) / 16);
|
||||
computePinHash(tmp, enteredPassword, (PIN_LENGTH + 15) / 16);
|
||||
memcpy(pin.hash, tmp, 32);
|
||||
|
||||
if(!fileWrite(&pin, "/luma/pin.bin", sizeof(PINData)))
|
||||
@ -112,18 +101,29 @@ void newPin(void)
|
||||
if(!fileWrite(&pin, "/luma/pin.bin", sizeof(PINData)))
|
||||
error("Error writing the PIN file");
|
||||
}
|
||||
|
||||
while(HID_PAD & PIN_BUTTONS);
|
||||
}
|
||||
|
||||
void verifyPin(PINData *in)
|
||||
bool verifyPin(void)
|
||||
{
|
||||
initScreens();
|
||||
|
||||
drawString("Press START to shutdown or enter pin to proceed.", 10, 10, COLOR_WHITE);
|
||||
drawString("Pin: ", 10, 10 + 2 * SPACING_Y, COLOR_WHITE);
|
||||
PINData pin;
|
||||
|
||||
// Set the default characters as 0x00 so we can check if there are any unentered characters.
|
||||
if(fileRead(&pin, "/luma/pin.bin") != sizeof(PINData) ||
|
||||
memcmp(pin.magic, "PINF", 4) != 0 ||
|
||||
pin.formatVersionMajor != PIN_VERSIONMAJOR ||
|
||||
pin.formatVersionMinor != PIN_VERSIONMINOR)
|
||||
return false;
|
||||
|
||||
u8 __attribute__((aligned(4))) zeroes[16] = {0};
|
||||
u8 __attribute__((aligned(4))) tmp[32];
|
||||
|
||||
computePinHash(tmp, zeroes, 1);
|
||||
|
||||
//Test vector verification (SD card has, or hasn't been used on another console)
|
||||
if(memcmp(pin.testHash, tmp, 32) != 0) return false;
|
||||
|
||||
//Pad to AES block length with zeroes
|
||||
u8 __attribute__((aligned(4))) enteredPassword[16 * ((PIN_LENGTH + 15) / 16)] = {0};
|
||||
|
||||
u32 cnt = 0;
|
||||
@ -132,6 +132,9 @@ void verifyPin(PINData *in)
|
||||
|
||||
while(!unlock)
|
||||
{
|
||||
drawString("Press START to shutdown or enter PIN to proceed", 10, 10, COLOR_TITLE);
|
||||
drawString("PIN: ", 10, 10 + 2 * SPACING_Y, COLOR_WHITE);
|
||||
|
||||
u32 pressed;
|
||||
do
|
||||
{
|
||||
@ -141,22 +144,21 @@ void verifyPin(PINData *in)
|
||||
|
||||
if(pressed & BUTTON_START) mcuPowerOff();
|
||||
|
||||
pressed &= PIN_BUTTONS & ~BUTTON_START;
|
||||
pressed &= PIN_BUTTONS;
|
||||
|
||||
if(!pressed) continue;
|
||||
|
||||
char key = PINKeyToLetter(pressed);
|
||||
enteredPassword[cnt++] = (u8)key; // add character to password.
|
||||
char key = pinKeyToLetter(pressed);
|
||||
enteredPassword[cnt++] = (u8)key; //Add character to password
|
||||
|
||||
// visualize character on screen.
|
||||
//Visualize character on screen
|
||||
drawCharacter(key, 10 + charDrawPos, 10 + 2 * SPACING_Y, COLOR_WHITE);
|
||||
charDrawPos += 2 * SPACING_X;
|
||||
|
||||
if(cnt >= PIN_LENGTH)
|
||||
{
|
||||
u8 __attribute__((aligned(4))) tmp[32];
|
||||
|
||||
computePINHash(tmp, enteredPassword, (PIN_LENGTH + 15) / 16);
|
||||
unlock = memcmp(in->hash, tmp, 32) == 0;
|
||||
computePinHash(tmp, enteredPassword, (PIN_LENGTH + 15) / 16);
|
||||
unlock = memcmp(pin.hash, tmp, 32) == 0;
|
||||
|
||||
if(!unlock)
|
||||
{
|
||||
@ -165,10 +167,10 @@ void verifyPin(PINData *in)
|
||||
|
||||
clearScreens();
|
||||
|
||||
drawString("Press START to shutdown or enter pin to proceed.", 10, 10, COLOR_WHITE);
|
||||
drawString("Pin: ", 10, 10 + 2 * SPACING_Y, COLOR_WHITE);
|
||||
drawString("Wrong pin! Try again!", 10, 10 + 3 * SPACING_Y, COLOR_RED);
|
||||
drawString("Wrong PIN, try again", 10, 10 + 4 * SPACING_Y, COLOR_RED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
@ -43,6 +43,5 @@ typedef struct __attribute__((packed))
|
||||
u8 hash[32];
|
||||
} PINData;
|
||||
|
||||
bool readPin(PINData* out);
|
||||
void newPin(void);
|
||||
void verifyPin(PINData *in);
|
||||
void newPin(bool allowSkipping);
|
||||
bool verifyPin(void);
|
@ -26,8 +26,8 @@
|
||||
_start:
|
||||
b start
|
||||
|
||||
.global launchedFirmTIDLow
|
||||
launchedFirmTIDLow:
|
||||
.global launchedFirmTidLow
|
||||
launchedFirmTidLow:
|
||||
.hword 0, 0, 0, 0, 0, 0, 0, 0
|
||||
|
||||
start:
|
||||
|
@ -49,5 +49,6 @@ typedef enum FirmwareType
|
||||
NATIVE_FIRM = 0,
|
||||
TWL_FIRM = 1,
|
||||
AGB_FIRM = 2,
|
||||
SAFE_FIRM = 3
|
||||
SAFE_FIRM = 3,
|
||||
NATIVE_FIRM2X = 4
|
||||
} FirmwareType;
|
Reference in New Issue
Block a user