diff --git a/.gitignore b/.gitignore index 29fb8eb..b751d5a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ build loader/build screeninit/build injector/build +exceptions/arm9/build *.bin *.3dsx *.smdh diff --git a/Makefile b/Makefile index 15b883b..3ee0177 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ version := $(shell git describe --abbrev=0 --tags) dir_source := source dir_patches := patches dir_loader := loader +dir_arm9_exceptions := exceptions/arm9 dir_screeninit := screeninit dir_injector := injector dir_mset := CakeHax @@ -33,7 +34,7 @@ objects = $(patsubst $(dir_source)/%.s, $(dir_build)/%.o, \ $(patsubst $(dir_source)/%.c, $(dir_build)/%.o, \ $(call rwildcard, $(dir_source), *.s *.c))) -bundled = $(dir_build)/patches.h $(dir_build)/loader.h $(dir_build)/screeninit.h +bundled = $(dir_build)/patches.h $(dir_build)/loader.h $(dir_build)/arm9_exceptions.h $(dir_build)/screeninit.h .PHONY: all all: launcher a9lh ninjhax @@ -55,12 +56,14 @@ clean: @$(MAKE) $(FLAGS) -C $(dir_mset) clean @$(MAKE) $(FLAGS) -C $(dir_ninjhax) clean @$(MAKE) -C $(dir_loader) clean + @$(MAKE) -C $(dir_arm9_exceptions) clean @$(MAKE) -C $(dir_screeninit) clean @$(MAKE) -C $(dir_injector) clean @rm -rf $(dir_out) $(dir_build) $(dir_out): @mkdir -p "$(dir_out)/luma/payloads" + @mkdir -p "$(dir_out)/luma/dumps/arm9" $(dir_out)/$(name).dat: $(dir_build)/main.bin $(dir_out) @$(MAKE) $(FLAGS) -C $(dir_mset) launcher @@ -96,6 +99,11 @@ $(dir_build)/loader.h: $(dir_loader)/Makefile @mv $(dir_loader)/loader.bin $(@D) @bin2c -o $@ -n loader $(@D)/loader.bin +$(dir_build)/arm9_exceptions.h: $(dir_arm9_exceptions)/Makefile + @$(MAKE) -C $(dir_arm9_exceptions) + @mv $(dir_arm9_exceptions)/arm9_exceptions.bin $(@D) + @bin2c -o $@ -n arm9_exceptions $(@D)/arm9_exceptions.bin + $(dir_build)/screeninit.h: $(dir_screeninit)/Makefile @$(MAKE) -C $(dir_screeninit) @mv $(dir_screeninit)/screeninit.bin $(@D) diff --git a/exceptions/arm9/Makefile b/exceptions/arm9/Makefile new file mode 100644 index 0000000..de8f9b2 --- /dev/null +++ b/exceptions/arm9/Makefile @@ -0,0 +1,47 @@ +rwildcard = $(foreach d, $(wildcard $1*), $(filter $(subst *, %, $2), $d) $(call rwildcard, $d/, $2)) + +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +include $(DEVKITARM)/3ds_rules + +CC := arm-none-eabi-gcc +AS := arm-none-eabi-as +LD := arm-none-eabi-ld +OC := arm-none-eabi-objcopy + +name := arm9_exceptions + +dir_source := source +dir_build := build + +ASFLAGS := -mcpu=arm946e-s +CFLAGS := -Wall -Wextra -MMD -MP -mthumb -mthumb-interwork $(ASFLAGS) -fno-builtin -std=c11 -Wno-main -O2 -flto -ffast-math +LDFLAGS := -nostdlib + +objects = $(patsubst $(dir_source)/%.s, $(dir_build)/%.o, \ + $(patsubst $(dir_source)/%.c, $(dir_build)/%.o, \ + $(call rwildcard, $(dir_source), *.s *.c))) + +.PHONY: all +all: $(name).bin + +.PHONY: clean +clean: + @rm -rf $(dir_build) + +$(name).bin: $(dir_build)/$(name).elf + $(OC) -S -O binary $< $@ + +$(dir_build)/$(name).elf: $(objects) + $(CC) $(LDFLAGS) -T linker.ld $(OUTPUT_OPTION) $^ + +$(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) diff --git a/exceptions/arm9/linker.ld b/exceptions/arm9/linker.ld new file mode 100644 index 0000000..b27d4e0 --- /dev/null +++ b/exceptions/arm9/linker.ld @@ -0,0 +1,11 @@ +ENTRY(_start) +SECTIONS +{ + . = 0x01FF8000; + .text.start : { *(.text.start) } + .text : { *(.text) } + .data : { *(.data) } + .bss : { *(.bss COMMON) } + .rodata : { *(.rodata) } + . = ALIGN(4); +} diff --git a/exceptions/arm9/source/handlers.h b/exceptions/arm9/source/handlers.h new file mode 100644 index 0000000..7b4b219 --- /dev/null +++ b/exceptions/arm9/source/handlers.h @@ -0,0 +1,14 @@ +/* +* handlers.h +* by TuxSH +* +* This is part of Luma3DS, see LICENSE.txt for details +*/ + +#pragma once + +void setupStack(u32 mode, void *stack); +void FIQHandler(void); +void undefinedInstructionHandler(void); +void dataAbortHandler(void); +void prefetchAbortHandler(void); \ No newline at end of file diff --git a/exceptions/arm9/source/handlers.s b/exceptions/arm9/source/handlers.s new file mode 100644 index 0000000..14643b6 --- /dev/null +++ b/exceptions/arm9/source/handlers.s @@ -0,0 +1,57 @@ +@ +@ handlers.s +@ by TuxSH +@ +@ This is part of Luma3DS, see LICENSE.txt for details +@ + +.macro GEN_HANDLER name, addr_offset + .global \name + .type \name, %function +\name: + stmfd sp!, {r0-r7} @ FIQ has its own r8-r14 regs + ldr r0, =\addr_offset + sub r0, lr, r0 @ address of instruction that triggered the exception; we will handle the undef+Thumb case later + mrs r2, spsr + + mov r6, sp + mrs r3, cpsr + ands r4, r2, #0xf @ get the mode that triggered the exception + moveq r4, #0xf @ usr => sys + bic r5, r3, #0xf + orr r5, r4 + msr cpsr_c, r5 @ change processor mode + stmfd r6!, {r8-r14} + msr cpsr_c, r3 @ restore processor mode + mov sp, r6 + + stmfd sp!, {r0,r2} @ it's a bit of a mess, but we will fix that later + @ order of regs now: pc, spsr, r8-r14, r0-r7 + mov r0, sp + ldr r1, =\@ @ macro expansion counter + b mainHandler + + .size \name, . - \name +.endm + +.text +.arm +.align 4 + + GEN_HANDLER FIQHandler, 4 + GEN_HANDLER undefinedInstructionHandler, 4 + GEN_HANDLER prefetchAbortHandler, 4 + GEN_HANDLER dataAbortHandler, 8 + +.global setupStack +.type setupStack, %function +setupStack: + cmp r0, #0 + moveq r0, #0xf @ usr => sys + mrs r2, cpsr + bic r3, r2, #0xf + orr r3, r0 @ processor mode + msr cpsr_c, r3 @ change processor mode + mov sp, r1 + msr cpsr_c, r2 @ restore processor mode + bx lr diff --git a/exceptions/arm9/source/i2c.c b/exceptions/arm9/source/i2c.c new file mode 100644 index 0000000..5f89d0a --- /dev/null +++ b/exceptions/arm9/source/i2c.c @@ -0,0 +1,114 @@ +#include "i2c.h" + +//----------------------------------------------------------------------------- + +static const struct { u8 bus_id, reg_addr; } dev_data[] = { + {0, 0x4A}, {0, 0x7A}, {0, 0x78}, + {1, 0x4A}, {1, 0x78}, {1, 0x2C}, + {1, 0x2E}, {1, 0x40}, {1, 0x44}, + {2, 0xD6}, {2, 0xD0}, {2, 0xD2}, + {2, 0xA4}, {2, 0x9A}, {2, 0xA0}, +}; + +static inline u8 i2cGetDeviceBusId(u8 device_id) +{ + return dev_data[device_id].bus_id; +} + +static inline u8 i2cGetDeviceRegAddr(u8 device_id) +{ + return dev_data[device_id].reg_addr; +} + +//----------------------------------------------------------------------------- + +static vu8 *reg_data_addrs[] = { + (vu8 *)(I2C1_REG_OFF + I2C_REG_DATA), + (vu8 *)(I2C2_REG_OFF + I2C_REG_DATA), + (vu8 *)(I2C3_REG_OFF + I2C_REG_DATA), +}; + +static inline vu8 *i2cGetDataReg(u8 bus_id) +{ + return reg_data_addrs[bus_id]; +} + +//----------------------------------------------------------------------------- + +static vu8 *reg_cnt_addrs[] = { + (vu8 *)(I2C1_REG_OFF + I2C_REG_CNT), + (vu8 *)(I2C2_REG_OFF + I2C_REG_CNT), + (vu8 *)(I2C3_REG_OFF + I2C_REG_CNT), +}; + +static inline vu8 *i2cGetCntReg(u8 bus_id) +{ + return reg_cnt_addrs[bus_id]; +} + +//----------------------------------------------------------------------------- + +static inline void i2cWaitBusy(u8 bus_id) +{ + while (*i2cGetCntReg(bus_id) & 0x80); +} + +static inline u32 i2cGetResult(u8 bus_id) +{ + i2cWaitBusy(bus_id); + + return (*i2cGetCntReg(bus_id) >> 4) & 1; +} + +static void i2cStop(u8 bus_id, u8 arg0) +{ + *i2cGetCntReg(bus_id) = (arg0 << 5) | 0xC0; + i2cWaitBusy(bus_id); + *i2cGetCntReg(bus_id) = 0xC5; +} + +//----------------------------------------------------------------------------- + +static u32 i2cSelectDevice(u8 bus_id, u8 dev_reg) +{ + i2cWaitBusy(bus_id); + *i2cGetDataReg(bus_id) = dev_reg; + *i2cGetCntReg(bus_id) = 0xC2; + + return i2cGetResult(bus_id); +} + +static u32 i2cSelectRegister(u8 bus_id, u8 reg) +{ + i2cWaitBusy(bus_id); + *i2cGetDataReg(bus_id) = reg; + *i2cGetCntReg(bus_id) = 0xC0; + + return i2cGetResult(bus_id); +} + +//----------------------------------------------------------------------------- + +u32 i2cWriteRegister(u8 dev_id, u8 reg, u8 data) +{ + u8 bus_id = i2cGetDeviceBusId(dev_id); + u8 dev_addr = i2cGetDeviceRegAddr(dev_id); + + for(u32 i = 0; i < 8; i++) + { + if(i2cSelectDevice(bus_id, dev_addr) && i2cSelectRegister(bus_id, reg)) + { + i2cWaitBusy(bus_id); + *i2cGetDataReg(bus_id) = data; + *i2cGetCntReg(bus_id) = 0xC1; + i2cStop(bus_id, 0); + + if(i2cGetResult(bus_id)) + return 1; + } + *i2cGetCntReg(bus_id) = 0xC5; + i2cWaitBusy(bus_id); + } + + return 0; +} \ No newline at end of file diff --git a/exceptions/arm9/source/i2c.h b/exceptions/arm9/source/i2c.h new file mode 100644 index 0000000..00658ea --- /dev/null +++ b/exceptions/arm9/source/i2c.h @@ -0,0 +1,18 @@ +#pragma once + +#include "types.h" + +#define I2C1_REG_OFF 0x10161000 +#define I2C2_REG_OFF 0x10144000 +#define I2C3_REG_OFF 0x10148000 + +#define I2C_REG_DATA 0 +#define I2C_REG_CNT 1 +#define I2C_REG_CNTEX 2 +#define I2C_REG_SCL 4 + +#define I2C_DEV_MCU 3 +#define I2C_DEV_GYRO 10 +#define I2C_DEV_IR 13 + +u32 i2cWriteRegister(u8 dev_id, u8 reg, u8 data); \ No newline at end of file diff --git a/exceptions/arm9/source/main.c b/exceptions/arm9/source/main.c new file mode 100644 index 0000000..d405967 --- /dev/null +++ b/exceptions/arm9/source/main.c @@ -0,0 +1,84 @@ +/* +* main.c +* by TuxSH +* +* This is part of Luma3DS, see LICENSE.txt for details +*/ + +#include "types.h" +#include "i2c.h" +#include "handlers.h" + +#define TEMP_BUFFER 0x1FF80000 //We choose AXIWRAM as tmp buffer since it's usually ARM11 payloads running there +#define FINAL_BUFFER 0x25000000 +#define STACK_DUMP_SIZE 0x2000 +#define SP ((void *)(TEMP_BUFFER + 4 * STACK_DUMP_SIZE)) + +void __attribute__((noreturn)) mainHandler(u32 regs[17], u32 type) +{ + vu32 *dump = (u32 *)TEMP_BUFFER; + + dump[0] = 0xDEADC0DE; //Magic + dump[1] = 0xDEADCAFE; //Magic + dump[2] = (1 << 16) | 0; //Dump format version number + dump[3] = 9; //Processor + dump[4] = type; //Exception type + dump[6] = 4 * 17; //Register dump size (r0-r12, sp, lr, pc, cpsr) + dump[7] = 40; //Code dump size (10 ARM instructions, up to 20 Thumb instructions). + dump[8] = STACK_DUMP_SIZE; //Stack dump size + dump[9] = 0; //Other data size + dump[5] = 40 + dump[6] + dump[7] + dump[8] + dump[9]; //Total size + + //Dump registers + //Current order of regs: pc, spsr, r8-r12, sp, lr, r0-r7 + vu32 *regdump = dump + 10; + + u32 cpsr = regs[1]; + u32 pc = regs[0] + (((cpsr & 0x20) != 0 && type == 1) ? 2 : 0); + + regdump[15] = pc; + regdump[16] = cpsr; + + for(u32 i = 0; i < 7; i++) + regdump[8 + i] = regs[2 + i]; + + for(u32 i = 0; i < 8; i++) + regdump[i] = regs[9 + i]; + + //Dump code + vu16 *codedump = (vu16 *)(regdump + dump[6] / 4); + vu16 *instr = (vu16 *)pc - dump[7] / 2 + 1; + for(u32 i = 0; i < dump[7] / 2; i++) + codedump[i] = instr[i]; + + //Dump stack + vu32 *sp = (vu32 *)regdump[13]; + vu32 *stackdump = (vu32 *)(codedump + dump[7] / 2); + /* Homebrew/CFW set their stack at 0x27000000, but we'd better not make any assumption here + as it breaks things it seems */ + for(u32 i = 0; i < dump[8] / 4; i++) + stackdump[i] = sp[i]; + + vu32 *final = (u32 *)FINAL_BUFFER; + for(u32 i = 0; i < dump[5] / 4; i++) + final[i] = dump[i]; + + i2cWriteRegister(I2C_DEV_MCU, 0x20, 1 << 2); //Reboot + while(1); +} + +void main(void) +{ + setupStack(1, SP); //FIQ + setupStack(7, SP); //Abort + setupStack(11, SP); //Undefined + + *(vu32 *)0x08000004 = 0xE51FF004; + *(vu32 *)0x08000008 = (u32)FIQHandler; + *(vu32 *)0x08000014 = 0xE51FF004; + *(vu32 *)0x08000018 = (u32)undefinedInstructionHandler; + *(vu32 *)0x0800001C = 0xE51FF004; + *(vu32 *)0x08000020 = (u32)prefetchAbortHandler; + *(vu32 *)0x08000028 = 0xE51FF004; + *(vu32 *)0x0800002C = (u32)dataAbortHandler; +} \ No newline at end of file diff --git a/exceptions/arm9/source/start.s b/exceptions/arm9/source/start.s new file mode 100644 index 0000000..82107f4 --- /dev/null +++ b/exceptions/arm9/source/start.s @@ -0,0 +1,5 @@ +.section .text.start +.align 4 +.global _start +_start: + b main diff --git a/exceptions/arm9/source/types.h b/exceptions/arm9/source/types.h new file mode 100644 index 0000000..d27412b --- /dev/null +++ b/exceptions/arm9/source/types.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +//Common data types +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef volatile u8 vu8; +typedef volatile u16 vu16; +typedef volatile u32 vu32; +typedef volatile u64 vu64; \ No newline at end of file diff --git a/exceptions/exception_dump_parser.py b/exceptions/exception_dump_parser.py new file mode 100644 index 0000000..07df5d5 --- /dev/null +++ b/exceptions/exception_dump_parser.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +# Requires Python >= 3.2 or >= 2.7 + +# This is part of AuReiNand + +__author__ = "TuxSH" +__copyright__ = "Copyright (c) 2016 TuxSH" +__license__ = "GPLv3" +__version__ = "v1.0" + +""" +Parses AuReiNand exception dumps +""" + +import argparse +from struct import unpack_from + +# Source of hexdump: https://gist.github.com/ImmortalPC/c340564823f283fe530b +# Credits for hexdump go to the original authors +# Slightly edited by TuxSH + +def hexdump(addr, src, length=16, sep='.' ): + ''' + @brief Return {src} in hex dump. + @param[in] length {Int} Nb Bytes by row. + @param[in] sep {Char} For the text part, {sep} will be used for non ASCII char. + @return {Str} The hexdump + @note Full support for python2 and python3 ! + ''' + result = [] + + # Python3 support + try: + xrange(0,1) + except NameError: + xrange = range + + for i in xrange(0, len(src), length): + subSrc = src[i:i+length] + hexa = '' + isMiddle = False + for h in xrange(0,len(subSrc)): + if h == length/2: + hexa += ' ' + h = subSrc[h] + if not isinstance(h, int): + h = ord(h) + h = hex(h).replace('0x','') + if len(h) == 1: + h = '0'+h + hexa += h+' ' + hexa = hexa.strip(' ') + text = '' + for c in subSrc: + if not isinstance(c, int): + c = ord(c) + if 0x20 <= c < 0x7F: + text += chr(c) + else: + text += sep + result.append(('%08X: %-'+str(length*(2+1)+1)+'s |%s|') % (addr + i, hexa, text)) + + return '\n'.join(result) + + +def makeRegisterLine(A, rA, B, rB): + return "{0:<15}{1:<20}{2:<15}{3:<20}".format(A, "{0:08x}".format(rA), B, "{0:08x}".format(rB)) + +handledExceptionNames = ("FIQ", "undefined instruction", "prefetch abort", "data abort") +registerNames = tuple("r{0}".format(i) for i in range(13)) + ("sp", "lr", "pc", "cpsr") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Parse AuReiNand exception dumps") + parser.add_argument("filename") + args = parser.parse_args() + data = b"" + with open(args.filename, "rb") as f: data = f.read() + if unpack_from("<2I", data) != (0xdeadc0de, 0xdeadcafe): + raise SystemExit("Invalid file format") + + processor, exceptionType, _, _, codeDumpSize, stackDumpSize = unpack_from("<6I", data, 12) + + print("Processor: ARM{0}".format(processor)) + print("Exception type: {0}".format("unknown" if exceptionType >= len(handledExceptionNames) else handledExceptionNames[exceptionType])) + + registers = unpack_from("<17I", data, 40) + print("\nRegister dump:\n") + for i in range(0, 16, 2): + print(makeRegisterLine(registerNames[i], registers[i], registerNames[i+1], registers[i+1])) + print("{0:<15}{1:<20}".format(registerNames[-1], "{0:08x}".format(registers[-1]))) + + codeDump = data[40+4*17 : 40+4*17 + codeDumpSize] + print("\nCode dump:\n") + print(hexdump(registers[15] - codeDumpSize + 2, codeDump)) + + # Homebrew/CFW set their stack at 0x27000000, let's detect it + if 0 <= 0x27000000 - registers[13] <= stackDumpSize: stackDumpSize = 0x27000000 - registers[13] + + stackOffset = 40+4*17 + codeDumpSize + stackDump = data[stackOffset : stackOffset + stackDumpSize] + print("\nStack dump:\n") + print(hexdump(registers[13], stackDump)) + \ No newline at end of file diff --git a/source/config.c b/source/config.c index 939bc64..0d6781a 100644 --- a/source/config.c +++ b/source/config.c @@ -24,7 +24,7 @@ void configureCFW(const char *configPath) "( ) Force A9LH detection", "( ) Use second EmuNAND as default", "( ) Enable region/language emulation", - "( ) Use developer UNITINFO", + "( ) Enable developer features", "( ) Show current NAND in System Settings", "( ) Show GBA boot screen in patched AGB_FIRM", "( ) Enable splash screen with no screen-init" }; diff --git a/source/exceptions.c b/source/exceptions.c new file mode 100644 index 0000000..97eb516 --- /dev/null +++ b/source/exceptions.c @@ -0,0 +1,38 @@ +/* +* exceptions.c +* by TuxSH +*/ + +#include "exceptions.h" +#include "fs.h" +#include "memory.h" +#include "utils.h" +#include "../build/arm9_exceptions.h" + +#define PAYLOAD_ADDRESS 0x01FF8000 + +void installArm9Handlers(void) +{ + memcpy((void *)PAYLOAD_ADDRESS, arm9_exceptions, arm9_exceptions_size); + ((void (*)())PAYLOAD_ADDRESS)(); +} + +void detectAndProcessExceptionDumps(void) +{ + vu32 *dump = (u32 *)0x25000000; + + if(dump[0] == 0xDEADC0DE && dump[1] == 0xDEADCAFE && dump[3] == 9) + { + char path[41] = "/luma/dumps/arm9"; + char fileName[] = "crash_dump_00000000.dmp"; + + findDumpFile(path, fileName); + + path[16] = '/'; + memcpy(&path[17], fileName, sizeof(fileName)); + + fileWrite((void *)dump, path, dump[5]); + + error("An ARM9 exception occured.\nPlease check your /luma/dumps/arm9 folder."); + } +} \ No newline at end of file diff --git a/source/exceptions.h b/source/exceptions.h new file mode 100644 index 0000000..c771e0d --- /dev/null +++ b/source/exceptions.h @@ -0,0 +1,11 @@ +/* +* exceptions.h +* by TuxSH +*/ + +#pragma once + +#include "types.h" + +void installArm9Handlers(void); +void detectAndProcessExceptionDumps(void); \ No newline at end of file diff --git a/source/firm.c b/source/firm.c index 12d8bfa..4caac54 100755 --- a/source/firm.c +++ b/source/firm.c @@ -13,6 +13,7 @@ #include "draw.h" #include "screeninit.h" #include "loader.h" +#include "exceptions.h" #include "buttons.h" #include "../build/patches.h" @@ -67,6 +68,13 @@ void main(void) } else { + //Only when "Enable developer features" is set + if(CONFIG(5)) + { + detectAndProcessExceptionDumps(); + installArm9Handlers(); + } + bootType = 0; firmType = 0; diff --git a/source/fs.c b/source/fs.c index 6428f7a..c848729 100644 --- a/source/fs.c +++ b/source/fs.c @@ -61,6 +61,27 @@ u32 defPayloadExists(void) return (result == FR_OK && info.fname[0]); } +void findDumpFile(const char *path, char *fileName) +{ + DIR dir; + FILINFO info; + u32 n = 0; + + while(f_findfirst(&dir, &info, path, fileName) == FR_OK && info.fname[0]) + { + u32 i = 18, + tmp = ++n; + + while(tmp) + { + fileName[i--] = '0' + (tmp % 10); + tmp /= 10; + } + } + + f_closedir(&dir); +} + void firmRead(void *dest, const char *firmFolder) { char path[48] = "1:/title/00040138/00000000/content"; diff --git a/source/fs.h b/source/fs.h index 4620204..a5c348c 100644 --- a/source/fs.h +++ b/source/fs.h @@ -10,4 +10,5 @@ u32 mountFs(void); u32 fileRead(void *dest, const char *path, u32 size); u32 fileWrite(const void *buffer, const char *path, u32 size); u32 defPayloadExists(void); +void findDumpFile(const char *path, char *fileName); void firmRead(void *dest, const char *firmFolder); \ No newline at end of file