From 398773e5029e03f3b35433e843d37b0e4c02d1d5 Mon Sep 17 00:00:00 2001 From: dimok789 Date: Tue, 17 Nov 2015 22:11:18 +0100 Subject: [PATCH] wip commit: - added kernel syscall to do our patches and hooks to kernel code - added hook to BAT setup function - setup an IBAT memory section for our own code for user and kernel code execution. about 7.3 MB of memory available here for our code - reworked many parts of the project structure. the FS, loader and menu are now all in one ELF file and can reference functions between them and the util/string/logging functions - moved all project sections to the new area and added a BSS section (for global variables over all parts of code) - added a proper Makefile with dependencies and many more which now builds a loadiine.elf and a loadiine_dbg.elf (only required for developers) - added hook of kernel function PrepareTitle to change the used cos.xml and app.xml values with our own from the game (this fixes many if not all game starts) - changed FS function patching way to allow re-installation of the FS functions (watch out if you remove a function from the array) - moved FS function patching to start of menu code to avoid a race condition on browser FS usage and IBAT setup - added several util and standart string functions - added automatic cos.xml and app.xml parsing code which are than applied in the kernel to it's struct - added a default data XML struct which is used as fallback if app.xml and cos.xml are not available on the SD card next is fimport section parsing before release of 4.0 --- .gitignore | 2 + Makefile | 182 +++++++++++++++ fs/Makefile | 68 ------ fs/utils.c | 164 ------------- installer/Makefile | 26 +-- installer/kernel_patches.S | 173 ++++++++++++++ installer/launcher.c | 317 +++++++++++++++----------- installer/launcher.h | 2 +- loader/Makefile | 76 ------ loader/loader532.ld | 28 --- menu/Makefile | 66 ------ menu/menu532.ld | 65 ------ menu/util.h | 9 - {common => src/common}/common.h | 6 +- {common => src/common}/fs_defs.h | 0 src/common/kernel_defs.h | 98 ++++++++ src/common/loader_defs.h | 20 ++ {common => src/common}/types.h | 0 {fs => src/fs}/fs.c | 222 +++++++++--------- src/fs/fs.h | 44 ++++ src/kernel/kernel_functions.c | 69 ++++++ src/kernel/kernel_hooks.S | 110 +++++++++ fs/fs532.ld => src/link.ld | 83 +++++-- {loader => src/loader}/loader.c | 44 ++-- {loader => src/loader}/loader.h | 3 - {menu => src/menu}/menu.c | 91 +++++--- {menu => src/menu}/menu.h | 21 +- {fs => src/utils}/exception_handler.c | 0 {fs => src/utils}/exception_handler.h | 0 src/utils/logger.c | 93 ++++++++ fs/fs.h => src/utils/logger.h | 89 +------- src/utils/socket.c | 38 +++ src/utils/socket.h | 33 +++ src/utils/strings.c | 169 ++++++++++++++ src/utils/strings.h | 26 +++ menu/util.c => src/utils/utils.c | 73 +++--- src/utils/utils.h | 31 +++ src/utils/xml.c | 258 +++++++++++++++++++++ src/utils/xml.h | 9 + www/fs532.elf | Bin 77572 -> 0 bytes www/loader532.elf | Bin 59408 -> 0 bytes www/loadiine.elf | Bin 0 -> 23872 bytes www/loadiine_dbg.elf | Bin 0 -> 29900 bytes www/menu532.elf | Bin 57944 -> 0 bytes 44 files changed, 1840 insertions(+), 968 deletions(-) create mode 100644 Makefile delete mode 100755 fs/Makefile delete mode 100755 fs/utils.c create mode 100644 installer/kernel_patches.S delete mode 100755 loader/Makefile delete mode 100755 loader/loader532.ld delete mode 100755 menu/Makefile delete mode 100755 menu/menu532.ld delete mode 100644 menu/util.h rename {common => src/common}/common.h (93%) mode change 100755 => 100644 rename {common => src/common}/fs_defs.h (100%) mode change 100755 => 100644 create mode 100644 src/common/kernel_defs.h create mode 100644 src/common/loader_defs.h rename {common => src/common}/types.h (100%) mode change 100755 => 100644 rename {fs => src/fs}/fs.c (86%) mode change 100755 => 100644 create mode 100644 src/fs/fs.h create mode 100644 src/kernel/kernel_functions.c create mode 100644 src/kernel/kernel_hooks.S rename fs/fs532.ld => src/link.ld (61%) mode change 100755 => 100644 rename {loader => src/loader}/loader.c (89%) mode change 100755 => 100644 rename {loader => src/loader}/loader.h (88%) mode change 100755 => 100644 rename {menu => src/menu}/menu.c (90%) mode change 100755 => 100644 rename {menu => src/menu}/menu.h (78%) mode change 100755 => 100644 rename {fs => src/utils}/exception_handler.c (100%) rename {fs => src/utils}/exception_handler.h (100%) create mode 100644 src/utils/logger.c rename fs/fs.h => src/utils/logger.h (51%) mode change 100755 => 100644 create mode 100644 src/utils/socket.c create mode 100644 src/utils/socket.h create mode 100644 src/utils/strings.c create mode 100644 src/utils/strings.h rename menu/util.c => src/utils/utils.c (60%) create mode 100644 src/utils/utils.h create mode 100644 src/utils/xml.c create mode 100644 src/utils/xml.h delete mode 100755 www/fs532.elf delete mode 100755 www/loader532.elf create mode 100644 www/loadiine.elf create mode 100644 www/loadiine_dbg.elf delete mode 100755 www/menu532.elf diff --git a/.gitignore b/.gitignore index 0debc25..45b1e96 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ /loader/build /menu/build /server/logs/*.txt +/build +/*.elf diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b9edc03 --- /dev/null +++ b/Makefile @@ -0,0 +1,182 @@ +#--------------------------------------------------------------------------------- +# Clear the implicit built in rules +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- +ifeq ($(strip $(DEVKITPPC)),) +$(error "Please set DEVKITPPC in your environment. export DEVKITPPC=devkitPPC") +endif +export PORTLIBS := ../../portlibs/ppc +export PATH := $(DEVKITPPC)/bin:$(PORTLIBS)/bin:$(PATH) +export LIBOGC_INC := $(DEVKITPRO)/libogc/include +export LIBOGC_LIB := $(DEVKITPRO)/libogc/lib/wii + +PREFIX := powerpc-eabi- + +export AS := $(PREFIX)as +export CC := $(PREFIX)gcc +export CXX := $(PREFIX)g++ +export AR := $(PREFIX)ar +export OBJCOPY := $(PREFIX)objcopy + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# INCLUDES is a list of directories containing extra header files +#--------------------------------------------------------------------------------- +TARGET := loadiine +BUILD := build +BUILD_DBG := $(TARGET)_dbg +SOURCES := src \ + src/fs \ + src/kernel \ + src/loader \ + src/menu \ + src/utils +DATA := data +INCLUDES := + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +CFLAGS := -Os -nostdinc -nostdlib -Wall -x c -std=gnu99 -nostdlib \ + -fno-builtin -ffreestanding -mrvl -mcpu=750 -meabi -mhard-float \ + -fshort-wchar -msdata=none -memb -ffunction-sections -fdata-sections \ + -Wno-unknown-pragmas -Wno-strict-aliasing $(INCLUDE) + +ASFLAGS := -mregnames +LDFLAGS := -nostartfiles -nostdlib + +#--------------------------------------------------------------------------------- +# move loader to another location - THANKS CREDIAR - 0x81330000 for HBC +#--------------------------------------------------------------------------------- +#LDFLAGS = -g $(MACHDEP) -Wl,-Map,$(notdir $@).map +#LDFLAGS = -g $(MACHDEP) -Wl,-Map,$(notdir $@).map -Wl,--section-start,.init=0x80003f00 +Q := @ +MAKEFLAGS += --no-print-directory +#--------------------------------------------------------------------------------- +# any extra libraries we wish to link with the project +#--------------------------------------------------------------------------------- +LIBS := -lgcc + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(CURDIR) \ + $(DEVKITPPC)/lib/gcc/powerpc-eabi/4.8.2 + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- +export PROJECTDIR := $(CURDIR) +export OUTPUT := $(CURDIR)/$(TARGETDIR)/$(TARGET) +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) +export DEPSDIR := $(CURDIR)/$(BUILD) + +#--------------------------------------------------------------------------------- +# automatically build a list of object files for our project +#--------------------------------------------------------------------------------- +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +sFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.S))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) +PNGFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.png))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) + export LD := $(CC) +else + export LD := $(CXX) +endif + +export OFILES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) \ + $(sFILES:.s=.o) $(SFILES:.S=.o) \ + $(PNGFILES:.png=.png.o) $(addsuffix .o,$(BINFILES)) + +#--------------------------------------------------------------------------------- +# build a list of include paths +#--------------------------------------------------------------------------------- +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) -I$(LIBOGC_INC) \ + -I$(PORTLIBS)/include -I$(PORTLIBS)/include/freetype2 + +#--------------------------------------------------------------------------------- +# build a list of library paths +#--------------------------------------------------------------------------------- +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) \ + -L$(LIBOGC_LIB) -L$(PORTLIBS)/lib + +export OUTPUT := $(CURDIR)/$(TARGET) +.PHONY: $(BUILD) clean install + +#--------------------------------------------------------------------------------- +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(OUTPUT).elf $(OUTPUT).bin $(BUILD_DBG).elf + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT).elf: $(OFILES) + +#--------------------------------------------------------------------------------- +# This rule links in binary data with the .jpg extension +#--------------------------------------------------------------------------------- +%.elf: link.ld $(OFILES) + @echo "linking ... $(TARGET).elf" + $(Q)$(LD) -n -T $^ $(LDFLAGS) -o ../$(BUILD_DBG).elf $(LIBPATHS) $(LIBS) + $(Q)$(OBJCOPY) -S -R .comment -R .gnu.attributes ../$(BUILD_DBG).elf $@ + +#--------------------------------------------------------------------------------- +%.a: +#--------------------------------------------------------------------------------- + @echo $(notdir $@) + @rm -f $@ + @$(AR) -rc $@ $^ + +#--------------------------------------------------------------------------------- +%.o: %.cpp + @echo $(notdir $<) + @$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.o: %.c + @echo $(notdir $<) + @$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.o: %.S + @echo $(notdir $<) + @$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.png.o : %.png + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- diff --git a/fs/Makefile b/fs/Makefile deleted file mode 100755 index 6f824d0..0000000 --- a/fs/Makefile +++ /dev/null @@ -1,68 +0,0 @@ - -PATH := $(DEVKITPPC)/bin:$(PATH) - -PREFIX ?= powerpc-eabi- -LD := $(PREFIX)ld -AS := $(PREFIX)as -CC := $(PREFIX)gcc -OBJDUMP ?= $(PREFIX)objdump -OBJCOPY ?= $(PREFIX)objcopy - -SFLAGS := -mgekko -mregnames - -# -O2: optimise lots -# -Wall: generate lots of warnings -# -x c: compile as C code -# -std=gnu99: use the C99 standard with GNU extensions -# -ffreestanding: we don't have libc; don't expect we do -# -mrvl: enable wii/gamecube compilation -# -mcpu=750: enable processor specific compilation -# -meabi: enable eabi specific compilation -# -mhard-float: enable hardware floating point instructions -# -fshort-wchar: use 16 bit whcar_t type in keeping with Wii executables -# -msdata-none: do not use r2 or r13 as small data areas -# -memb: enable embedded application specific compilation -# -ffunction-sections: split up functions so linker can garbage collect -# -fdata-sections: split up data so linker can garbage collect -CFLAGS := -Os -nostdinc -nostdlib -Wall -x c -std=gnu99 -nostdlib \ - -ffreestanding \ - -mrvl -mcpu=750 -meabi -mhard-float -fshort-wchar \ - -msdata=none -memb -ffunction-sections -fdata-sections \ - -Wno-unknown-pragmas -Wno-strict-aliasing \ - -SRC := $(wildcard *.S) $(wildcard *.c) -OBJ := $(patsubst %.S,%.o,$(patsubst %.c,%.o,$(SRC))) - -# Simulate an order only dependency -BUILD_REQ := $(filter-out $(wildcard build),build) -BIN_REQ := $(filter-out $(wildcard bin),bin) - -all: build/fs532.elf copy - -../installer/fs%.h: build/fs%.text.bin build/fs%.magic.bin $(BIN_REQ) - xxd -i build/fs$*.magic.bin | sed "s/unsigned/static const unsigned/g;s/fs$*/fs/g" > $@ - xxd -i build/fs$*.text.bin | sed "s/unsigned/static const unsigned/g;s/fs$*/fs/g" >> $@ - -build/fs%.text.bin: build/fs%.elf $(BUILD_REQ) - $(OBJCOPY) -j .text -O binary $< $@ -build/fs%.magic.bin: build/fs%.elf $(BUILD_REQ) - $(OBJCOPY) -j .magic -O binary $< $@ - -build/fs%.elf: fs%.ld $(OBJ) $(BUILD_REQ) - $(LD) -o $@ -T $< $(OBJ) -s -L"$(DEVKITPPC)/lib/gcc/powerpc-eabi/4.8.2" -lgcc - -build/%.o: %.c $(BUILD_REQ) - $(CC) -c $(CFLAGS) -o $@ $+ -build/%.o: %.S $(BUILD_REQ) - $(AS) $(SFLAGS) -o $@ $+ - -bin: - mkdir $@ -build: - mkdir $@ - -copy: - cp build/fs532.elf ../installer/bin/ - -clean: - rm -rf $(wildcard build) $(wildcard bin) $(wildcard ../installer/fs532.h) diff --git a/fs/utils.c b/fs/utils.c deleted file mode 100755 index 5d0231a..0000000 --- a/fs/utils.c +++ /dev/null @@ -1,164 +0,0 @@ -#include "fs.h" - -static int recvwait(int sock, void *buffer, int len); -static int recvbyte(int sock); -static int sendwait(int sock, const void *buffer, int len); - -#define CHECK_ERROR(cond) if (cond) { goto error; } - -int fs_connect(int *psock) { - extern unsigned int server_ip; - struct sockaddr_in addr; - int sock, ret; - - // No ip means that we don't have any server running, so no logs - if (server_ip == 0) { - *psock = -1; - return 0; - } - - socket_lib_init(); - - sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - CHECK_ERROR(sock == -1); - - addr.sin_family = AF_INET; - addr.sin_port = 7332; - addr.sin_addr.s_addr = server_ip; - - ret = connect(sock, (void *)&addr, sizeof(addr)); - CHECK_ERROR(ret < 0); - - ret = recvbyte(sock); - CHECK_ERROR(ret < 0); - - *psock = sock; - return 0; - -error: - if (sock != -1) - socketclose(sock); - - *psock = -1; - return -1; -} - -void fs_disconnect(int sock) { - CHECK_ERROR(sock == -1); - - char byte = BYTE_DISCONNECT; - sendwait(sock, &byte, 1); - - socketclose(sock); -error: - return; -} - -int fs_mount_sd(int sock, void* pClient, void* pCmd) { - while (bss.lock) GX2WaitForVsync(); - bss.lock = 1; - - int is_mounted = 0; - char buffer[1]; - - if (sock != -1) { - buffer[0] = BYTE_MOUNT_SD; - sendwait(sock, buffer, 1); - } - - // mount sdcard - FSMountSource mountSrc; - char mountPath[FS_MAX_MOUNTPATH_SIZE]; - int status = FSGetMountSource(pClient, pCmd, FS_SOURCETYPE_EXTERNAL, &mountSrc, FS_RET_NO_ERROR); - if (status == FS_STATUS_OK) - { - status = FSMount(pClient, pCmd, &mountSrc, mountPath, sizeof(mountPath), FS_RET_UNSUPPORTED_CMD); - if (status == FS_STATUS_OK) - { - // set as mounted - is_mounted = 1; - } - } - - if (sock != -1) { - buffer[0] = is_mounted ? BYTE_MOUNT_SD_OK : BYTE_MOUNT_SD_BAD; - sendwait(sock, buffer, 1); - } - - bss.lock = 0; - return is_mounted; -} - -void log_string(int sock, const char* str, char flag_byte) { - if(sock == -1) { - return; - } - while (bss.lock) GX2WaitForVsync(); - bss.lock = 1; - - int i; - int len_str = 0; - while (str[len_str++]); - - // - { - char buffer[1 + 4 + len_str]; - buffer[0] = flag_byte; - *(int *)(buffer + 1) = len_str; - for (i = 0; i < len_str; i++) - buffer[5 + i] = str[i]; - - buffer[5 + i] = 0; - - sendwait(sock, buffer, 1 + 4 + len_str); - } - - bss.lock = 0; -} - -void log_byte(int sock, char byte) { - while (bss.lock) GX2WaitForVsync(); - bss.lock = 1; - - CHECK_ERROR(sock == -1); - - sendwait(sock, &byte, 1); - -error: - bss.lock = 0; -} - -static int recvwait(int sock, void *buffer, int len) { - int ret; - while (len > 0) { - ret = recv(sock, buffer, len, 0); - CHECK_ERROR(ret < 0); - len -= ret; - buffer += ret; - } - return 0; -error: - return ret; -} - -static int recvbyte(int sock) { - unsigned char buffer[1]; - int ret; - - ret = recvwait(sock, buffer, 1); - if (ret < 0) return ret; - return buffer[0]; -} - -static int sendwait(int sock, const void *buffer, int len) { - int ret; - while (len > 0) { - ret = send(sock, buffer, len, 0); - CHECK_ERROR(ret < 0); - len -= ret; - buffer += ret; - } - return 0; -error: - return ret; -} diff --git a/installer/Makefile b/installer/Makefile index adb2d04..7c26b99 100644 --- a/installer/Makefile +++ b/installer/Makefile @@ -1,5 +1,6 @@ PATH := $(DEVKITPPC)/bin:$(PATH) CC=powerpc-eabi-gcc +AS=powerpc-eabi-as CFLAGS=-std=gnu99 -nostdinc -fno-builtin -c LD=powerpc-eabi-ld LDFLAGS=-Ttext 1800000 --oformat binary @@ -10,37 +11,28 @@ build := $(root)/bin www_loadiine :=$(root)/../../www/loadiine framework:=$(root)/../../../framework -all: clean setup menu loader fs main532 print_stats +all: clean setup main main532 print_stats setup: mkdir -p $(root)/bin/ -menu: - cd ../menu/ && make - -loader: - cd ../loader/ && make - -fs: - cd ../fs/ && make +main: + cd ../ && make clean + cd ../ && make main532: $(CC) $(CFLAGS) -DVER=532 $(project)/launcher.c + $(AS) -mregnames -o kernel_patches.o $(project)/kernel_patches.S #-Wa,-a,-ad cp -r $(root)/*.o $(build) rm $(root)/*.o $(LD) $(LDFLAGS) -s -o $(build)/code532.bin $(build)/launcher.o `find $(build) -name "*.o" ! -name "launcher.o"` - cp -rf $(build)/*.elf $(www_loadiine) - cp -rf $(build)/*.elf ../www/ + cp -rf ../*.elf $(www_loadiine) + cp -rf ../*.elf ../www/ clean: - make -C ../menu/ clean - make -C ../loader/ clean - make -C ../fs/ clean rm -rf $(build) print_stats: @echo - @echo "code size : menu =>" `$(OBJDUMP) -h $(build)/menu532.elf | awk '/.text|rodata/ { sum+=strtonum("0x"$$3) } END {print sum}'` / 5120 - @echo "code size : loader =>" `$(OBJDUMP) -h $(build)/loader532.elf | awk '/.text/ { sum+=strtonum("0x"$$3) } END {print sum}'` / 5120 - @echo "code size : fs =>" `$(OBJDUMP) -h $(build)/fs532.elf | awk '/.text|rodata|magic/ { sum+=strtonum("0x"$$3) } END {print sum}'` / 16364 + @echo "code size : loadiine =>" `$(OBJDUMP) -h ../loadiine.elf | awk '/.kernel_code|.text|.menu_code|.rodata|.fs_magic|.loader_magic|.bss/ { sum+=strtonum("0x"$$3) } END {print sum}'` / 7530312 diff --git a/installer/kernel_patches.S b/installer/kernel_patches.S new file mode 100644 index 0000000..950c411 --- /dev/null +++ b/installer/kernel_patches.S @@ -0,0 +1,173 @@ + .globl Syscall_0x36 +Syscall_0x36: + mflr r0 + stwu r1, -0x10(r1) + stw r30, 0x4(r1) + stw r31, 0x8(r1) + mr r5, r0 + mr r6, r1 + li r0, 0x3600 + sc + nop + mr r0, r5 + mr r1, r6 + lwz r30, 0x04(r1) + lwz r31, 0x08(r1) + addi r1, r1, 0x10 + mtlr r0 + blr + + .globl KernelPatches +KernelPatches: + # store the old DBAT0 + mfdbatu r30, 0 + mfdbatl r31, 0 + + # setup DBAT0 for access to kernel code memory + lis r3, 0xFFF0 + ori r3, r3, 0x0002 + mtdbatu 0, r3 + lis r3, 0xFFF0 + ori r3, r3, 0x0032 + mtdbatl 0, r3 + + # memory barrier + eieio + isync + + # SaveAndResetDataBATs_And_SRs hook setup, but could be any BAT function though + # just chosen because its simple + lis r3, 0xFFF1 + ori r3, r3, 0xD744 + + # make the kernel setup our section in IBAT4 and + # jump to our function to restore the replaced instructions + lis r4, 0x3ce0 # lis r7, 0x2C80 + ori r4, r4, 0x2c80 + stw r4, 0x00(r3) + lis r4, 0x60e7 # ori r7, r7, 0x0013 + ori r4, r4, 0x0013 + stw r4, 0x04(r3) + lis r4, 0x7cf1 # mtspr 561, r7 + ori r4, r4, 0x8ba6 + stw r4, 0x08(r3) + lis r4, 0x3ce0 # lis r7, 0x0080 + ori r4, r4, 0x0080 + stw r4, 0x0C(r3) + lis r4, 0x60e7 # ori r7, r7, 0x00FF + ori r4, r4, 0x00ff + stw r4, 0x10(r3) + lis r4, 0x7cf0 # mtspr 560, r7 + ori r4, r4, 0x8ba6 + stw r4, 0x14(r3) + lis r4, 0x7c00 # eieio + ori r4, r4, 0x06ac + stw r4, 0x18(r3) + lis r4, 0x4c00 # isync + ori r4, r4, 0x012c + stw r4, 0x1C(r3) + lis r4, 0x48ae # ba 0x00AE1000 + ori r4, r4, 0x1002 + stw r4, 0x20(r3) + + # flush and invalidate the replaced instructions + lis r3, 0xFFF1 + ori r3, r3, 0xD740 + dcbf 0, r3 + icbi 0, r3 + + # write "nop" to some positions + lis r4, 0x6000 + + # nop on IBATU 4 set/reset + lis r3, 0xFFF1 + ori r3, r3, 0xD558 + stw r4, 0(r3) + dcbf 0, r3 + icbi 0, r3 + + lis r3, 0xFFF1 + ori r3, r3, 0xD73C + stw r4, 0(r3) + dcbf 0, r3 + icbi 0, r3 + + # nop on IBATL 4 set/reset + lis r3, 0xFFF1 + ori r3, r3, 0xD550 + stw r4, 0(r3) + dcbf 0, r3 + icbi 0, r3 + + # nop on remove of supervisor level from IBATU 4 + lis r3, 0xFFF0 + ori r3, r3, 0x6A14 + stw r4, 0(r3) + dcbf 0, r3 + icbi 0, r3 + + lis r3, 0xFFF0 + ori r3, r3, 0x6AA0 + stw r4, 0(r3) + dcbf 0, r3 + icbi 0, r3 + isync + + # setup IBAT4 for core 1 at this position (not really required but wont hurt) + # IBATL 4 + lis r3, 0x2C80 + ori r3, r3, 0x0013 + mtspr 561, r3 + + # IBATU 4 + lis r3, 0x0080 + ori r3, r3, 0x00FF + mtspr 560, r3 + + + # while we are at it, let's give IBATU0 kernel permissions, maybe it will come in handy + # write "nop" to some positions that set/reset IBATU0 + lis r4, 0x6000 + + lis r3, 0xFFF1 + ori r3, r3, 0xD518 + stw r4, 0(r3) + dcbf 0, r3 + icbi 0, r3 + + lis r3, 0xFFF1 + ori r3, r3, 0xD72C + stw r4, 0(r3) + dcbf 0, r3 + icbi 0, r3 + + # add code execution permission for supervisor to IBATU0 + # IBATU 0 + lis r3, 0x0100 + ori r3, r3, 0x00FF + mtibatu 0,r3 + + # Now lets hook the kernel function for result printing of PrepareTitle add jump to our function + # at location 0x00AE1058 + lis r3, 0xFFF1 + ori r3, r3, 0x8558 + lis r4, 0x48AE + ori r4, r4, 0x105A + stw r4, 0(r3) + eieio + isync + + # memory barrier + eieio + isync + + # restore DBAT 0 and return from interrupt + mtdbatu 0, r30 + mtdbatl 0, r31 + + # memory barrier + eieio + isync + + rfi + diff --git a/installer/launcher.c b/installer/launcher.c index dee9a45..88cef59 100755 --- a/installer/launcher.c +++ b/installer/launcher.c @@ -1,11 +1,14 @@ #include "launcher.h" #include "elf_abi.h" -#include "../common/common.h" +#include "../src/common/common.h" #include "../../libwiiu/src/coreinit.h" #include "../../libwiiu/src/vpad.h" #include "../../libwiiu/src/socket.h" #if VER == 532 + #define CODE_RW_BASE_OFFSET 0xBC000000 + #define DATA_RW_BASE_OFFSET 0 + // Function definitions #define SYSLaunchMiiStudio ((void (*)(void))0xDEAAEB8) #define _Exit ((void (*)(void))0x0101cd70) @@ -13,13 +16,6 @@ #define memcpy ((void * (*)(void * dest, const void * src, int num))0x1035a6c) #define DCFlushRange ((void (*)(const void *addr, uint length))0x1023ee8) #define ICInvalidateRange ((void (*)(const void *addr, uint length))0x1024010) - - // Install addresses - #define INSTALL_FS_ADDR 0x011df800 // where the fs functions are copied in memory - - // Install flags - #define INSTALL_FS_DONE_ADDR (INSTALL_FS_ADDR - 0x4) // Used to know if fs is already installed - #define INSTALL_FS_DONE_FLAG 0xCACACACA #endif #define PRINT_TEXT1(x, y, str) { OSScreenPutFontEx(1, x, y, str); } @@ -65,13 +61,7 @@ typedef struct { } file_struct_t; typedef struct { - unsigned char *data_menu; - unsigned char *data_loader; - unsigned char *data_fs; - int len_menu; - int len_loader; - int len_fs; - + unsigned char *data_elf; unsigned int coreinit_handle; /* function pointers */ void*(*memset)(void * dest, unsigned int value, unsigned int bytes); @@ -87,6 +77,7 @@ typedef struct { } private_data_t; /* Install functions */ +static void InstallMain(private_data_t *private_data); static void InstallMenu(private_data_t *private_data); static void InstallLoader(private_data_t *private_data); static void InstallFS(private_data_t *private_data); @@ -94,6 +85,12 @@ static void InstallFS(private_data_t *private_data); static int show_ip_selection_screen(unsigned int coreinit_handle, unsigned int *ip_address); static void curl_thread_callback(int argc, void *argv); +static void SetupKernelSyscall(unsigned int addr); + +/* assembly functions */ +extern void Syscall_0x36(void); +extern void KernelPatches(void); + /* ****************************************************************** */ /* ENTRY POINT */ /* ****************************************************************** */ @@ -211,32 +208,70 @@ void _start() /* Install our ELF files */ if(result){ - InstallMenu(&private_data); - InstallLoader(&private_data); - InstallFS(&private_data); /* patch server IP */ - *((volatile unsigned int *)(INSTALL_FS_ADDR + 0xC1000000)) = ip_address; + SERVER_IP = ip_address; /* Set GAME_LAUNCHED to 0 */ GAME_LAUNCHED = 0; GAME_RPX_LOADED = 0; + RPX_CHECK_NAME = 0xDEADBEAF; /* Set LOADIINE mode to smash bros initially */ LOADIINE_MODE = LOADIINE_MODE_SMASH_BROS; + /* Install our code now */ + InstallMain(&private_data); + + /* setup our own syscall and call it */ + SetupKernelSyscall((unsigned int)KernelPatches); + Syscall_0x36(); + + InstallMenu(&private_data); + InstallLoader(&private_data); + InstallFS(&private_data); } /* free memory allocated */ - if(private_data.data_menu) { - private_data.MEMFreeToDefaultHeap(private_data.data_menu); - } - if(private_data.data_loader) { - private_data.MEMFreeToDefaultHeap(private_data.data_loader); - } - if(private_data.data_fs) { - private_data.MEMFreeToDefaultHeap(private_data.data_fs); + if(private_data.data_elf) { + private_data.MEMFreeToDefaultHeap(private_data.data_elf); } } + _Exit(); } +/* ***************************************************************************** + * Base functions + * ****************************************************************************/ + +/* Write a 32-bit word with kernel permissions */ +static void kern_write(uint32_t addr, uint32_t value) +{ + asm volatile( + "li 3,1\n" + "li 4,0\n" + "mr 5,%1\n" + "li 6,0\n" + "li 7,0\n" + "lis 8,1\n" + "mr 9,%0\n" + "mr %1,1\n" + "li 0,0x3500\n" + "sc\n" + "nop\n" + "mr 1,%1\n" + : + : "r"(addr), "r"(value) + : "memory", "ctr", "lr", "0", "3", "4", "5", "6", "7", "8", "9", "10", + "11", "12" + ); +} + +#define KERN_SYSCALL_TBL_5 0xFFEAA0E0 // works with browser + +static void SetupKernelSyscall(unsigned int address) +{ + // Add syscall #0x36 + kern_write(KERN_SYSCALL_TBL_5 + (0x36 * 4), address); +} + /* IP selection screen implemented by Maschell */ static int show_ip_selection_screen(unsigned int coreinit_handle, unsigned int *ip_address) { @@ -486,12 +521,8 @@ static void curl_thread_callback(int argc, void *argv) while(*ptr != 0x2F) ptr--; - memcpy(ptr+1, "fs532.elf", 10); - private_data->len_fs = curl_download_file(private_data, curl, buf, &private_data->data_fs); - memcpy(ptr+1, "menu532.elf", 12); - private_data->len_menu = curl_download_file(private_data, curl, buf, &private_data->data_menu); - memcpy(ptr+1, "loader532.elf", 14); - private_data->len_loader = curl_download_file(private_data, curl, buf, &private_data->data_loader); + memcpy(ptr+1, "loadiine.elf", 13); + curl_download_file(private_data, curl, buf, &private_data->data_elf); /* Cleanup to gain back memory */ private_data->curl_easy_cleanup(curl); @@ -555,36 +586,86 @@ static unsigned int get_section(private_data_t *private_data, unsigned char *dat } /* ****************************************************************** */ -/* INSTALL MENU */ +/* INSTALL MAIN CODE */ /* ****************************************************************** */ -static void InstallMenu(private_data_t *private_data) +static void InstallMain(private_data_t *private_data) { + // get .kernel_code section + unsigned int kernel_code_addr = 0; + unsigned int kernel_code_len = 0; + unsigned int section_offset = get_section(private_data, private_data->data_elf, ".kernel_code", &kernel_code_len, &kernel_code_addr); + unsigned char *kernel_code = private_data->data_elf + section_offset; + + /* Copy kernel code section to memory */ + unsigned int cpy_addr = (CODE_RW_BASE_OFFSET + kernel_code_addr); + memcpy((void*)cpy_addr, kernel_code, kernel_code_len); + DCFlushRange((void*)cpy_addr, kernel_code_len); + ICInvalidateRange((void*)cpy_addr, kernel_code_len); + // get .text section - unsigned int menu_text_addr = 0; - unsigned int menu_text_len = 0; - unsigned int section_offset = get_section(private_data, private_data->data_menu, ELF_TEXT, &menu_text_len, &menu_text_addr); - unsigned char *menu_text = private_data->data_menu + section_offset; + unsigned int main_text_addr = 0; + unsigned int main_text_len = 0; + section_offset = get_section(private_data, private_data->data_elf, ELF_TEXT, &main_text_len, &main_text_addr); + unsigned char *main_text = private_data->data_elf + section_offset; + /* Copy main .text to memory */ + memcpy((void*)(CODE_RW_BASE_OFFSET + main_text_addr), main_text, main_text_len); + DCFlushRange((void*)(CODE_RW_BASE_OFFSET + main_text_addr), main_text_len); + ICInvalidateRange((void*)(CODE_RW_BASE_OFFSET + main_text_addr), main_text_len); // get the .rodata section - unsigned int menu_rodata_addr = 0; - unsigned int menu_rodata_len = 0; - section_offset = get_section(private_data, private_data->data_menu, ELF_RODATA, &menu_rodata_len, &menu_rodata_addr); - unsigned char *menu_rodata = private_data->data_menu + section_offset; - - /* Copy menu code to memory */ - memcpy((void*)0xC1000000 + menu_text_addr, menu_text, menu_text_len); - DCFlushRange((void*)(0xC1000000 + menu_text_addr), menu_text_len); - ICInvalidateRange((void*)(0xC1000000 + menu_text_addr), menu_text_len); - - /* Copy menu rodata to memory */ - memcpy((void*)0xC1000000 + menu_rodata_addr, menu_rodata, menu_rodata_len); - DCFlushRange((void*)(0xC1000000 + menu_rodata_addr), menu_rodata_len); - - /* Patch coreinit - on 5.3.2 coreinit.rpl starts at 0x101c400 */ - int jump_length = menu_text_addr - 0x0101c55c; // => jump to (101C55C + 1C0AA4) = 11DD000 which is the codehandler - *((volatile uint32_t *)(0xC1000000 + 0x0101c55c)) = 0x48000001 | jump_length; // 0x481c0aa5 => bl 0x1C0AA4 => write at 0x15C in coreinit file => end of the coreinit_start function - DCFlushRange((void*)(0xC1000000 + 0x0101c55c), 4); - ICInvalidateRange((void*)(0xC1000000 + 0x0101c55c), 4); + unsigned int main_rodata_addr = 0; + unsigned int main_rodata_len = 0; + section_offset = get_section(private_data, private_data->data_elf, ELF_RODATA, &main_rodata_len, &main_rodata_addr); + unsigned char *main_rodata = private_data->data_elf + section_offset; + /* Copy main rodata to memory */ + memcpy((void*)(DATA_RW_BASE_OFFSET + main_rodata_addr), main_rodata, main_rodata_len); + DCFlushRange((void*)(DATA_RW_BASE_OFFSET + main_rodata_addr), main_rodata_len); + + // get the .bss section + unsigned int main_bss_addr = 0; + unsigned int main_bss_len = 0; + section_offset = get_section(private_data, private_data->data_elf, ELF_BSS, &main_bss_len, &main_bss_addr); + // Copy main bss to memory + private_data->memset((void*)(DATA_RW_BASE_OFFSET + main_bss_addr), 0, main_bss_len); + DCFlushRange((void*)(DATA_RW_BASE_OFFSET + main_bss_addr), main_bss_len); +} + +/* ****************************************************************** */ +/* INSTALL MENU CODE */ +/* ****************************************************************** */ +static void InstallMenu(private_data_t *private_data) +{ + // get .menu_magic section + unsigned int menu_magic_addr = 0; + unsigned int menu_magic_len = 0; + unsigned int section_offset = get_section(private_data, private_data->data_elf, ".menu_magic", &menu_magic_len, &menu_magic_addr); + unsigned char *menu_magic = private_data->data_elf + section_offset; + + /* Get our functions */ + struct magic_t + { + const unsigned int repl_func; // our replacement function which is called + const unsigned int repl_addr; // address where to place the jump to the our function + const unsigned int call_type; // call type, e.g. 0x48000000 for branch and 0x48000001 for bl + } *magic = (struct magic_t *)menu_magic; + int magic_len = menu_magic_len / sizeof(struct magic_t); + + /* Replace loader instructions */ + /* Loop to replace instructions in loader code by a "bl"(jump) instruction to our replacement function */ + int i; + for (i = 0; i < magic_len; i ++) + { + unsigned int repl_func = magic[i].repl_func; + unsigned int repl_addr = magic[i].repl_addr; + unsigned int call_type = magic[i].call_type; + + // Install function hook only if needed + unsigned int jump_addr = repl_func & 0x03fffffc; // Compute jump length to jump from current instruction address to our function address + *((volatile uint32_t *)(0xC1000000 + repl_addr)) = call_type | jump_addr; // Replace the instruction in the loader by the jump to our function + // flush caches and invalidate instruction cache + DCFlushRange((void*)(0xC1000000 + repl_addr), 4); + ICInvalidateRange((void*)(0xC1000000 + repl_addr), 4); + } } /* ****************************************************************** */ @@ -593,45 +674,17 @@ static void InstallMenu(private_data_t *private_data) /* ****************************************************************** */ static void InstallLoader(private_data_t *private_data) { - // get .text section - unsigned int loader_text_addr = 0; - unsigned int loader_text_len = 0; - unsigned int section_offset = get_section(private_data, private_data->data_loader, ELF_TEXT, &loader_text_len, &loader_text_addr); - unsigned char *loader_text = private_data->data_loader + section_offset; // get .magic section unsigned int loader_magic_addr = 0; unsigned int loader_magic_len = 0; - section_offset = get_section(private_data, private_data->data_loader, ".magic", &loader_magic_len, &loader_magic_addr); - unsigned char *loader_magic = private_data->data_loader + section_offset; - - /* Patch to bypass SDK version tests */ - *((volatile uint32_t *)(0xC1000000 + 0x010095b4)) = 0x480000a0; // ble loc_1009654 (0x408100a0) => b loc_1009654 (0x480000a0) - *((volatile uint32_t *)(0xC1000000 + 0x01009658)) = 0x480000e8; // bge loc_1009740 (0x408100a0) => b loc_1009740 (0x480000e8) - DCFlushRange((void*)(0xC1000000 + 0x010095b4), 4); - ICInvalidateRange((void*)(0xC1000000 + 0x010095b4), 4); - DCFlushRange((void*)(0xC1000000 + 0x01009658), 4); - ICInvalidateRange((void*)(0xC1000000 + 0x01009658), 4); - - /* Copy loader code in memory */ - /* - virtual address 0xA0000000 is at physical address 0x10000000 (with read/write access) */ - /* - virtual address range 0x01xxxxxx starts at physical address 0x32000000 */ - /* - we want to copy the code at INSTALL_ADDR (0x011de000), this memory range is the for cafeOS app and libraries, but is write protected */ - /* - in order to have the rights to write into memory in this address range we need to use the 0xA0000000 virtual address range */ - /* - so start virtual address is : (0xA0000000 + (0x32000000 - 0x10000000 - 0x01000000)) = 0xC1000000 */ - memcpy((void*)(0xC1000000 + loader_text_addr), loader_text, loader_text_len); - // flush caches and invalidate instruction cache - DCFlushRange((void*)(0xC1000000 + loader_text_addr), loader_text_len); - ICInvalidateRange((void*)(0xC1000000 + loader_text_addr), loader_text_len); - - /* Copy original loader instructions in memory for when we want to restore the loader at his original state */ - // TODO: copy original instructions in order to restore them later to have a clean loader state - // we'll have to hook the "quit" function to restore the original instructions + unsigned int section_offset = get_section(private_data, private_data->data_elf, ".loader_magic", &loader_magic_len, &loader_magic_addr); + unsigned char *loader_magic = private_data->data_elf + section_offset; /* Get our functions */ struct magic_t { - const void * repl_func; // our replacement function which is called - const void * repl_addr; // address where to place the jump to the our function + const unsigned int repl_func; // our replacement function which is called + const unsigned int repl_addr; // address where to place the jump to the our function const unsigned int call_type; // call type, e.g. 0x48000000 for branch and 0x48000001 for bl } *magic = (struct magic_t *)loader_magic; int magic_len = loader_magic_len / sizeof(struct magic_t); @@ -641,18 +694,25 @@ static void InstallLoader(private_data_t *private_data) int i; for (i = 0; i < magic_len; i ++) { - unsigned int repl_func = (unsigned int)magic[i].repl_func; - unsigned int repl_addr = (unsigned int)magic[i].repl_addr; + unsigned int repl_func = magic[i].repl_func; + unsigned int repl_addr = magic[i].repl_addr; unsigned int call_type = magic[i].call_type; // Install function hook only if needed - int jump_addr = (repl_func - repl_addr) & 0x03fffffc; // Compute jump length to jump from current instruction address to our function address - *((volatile uint32_t *)(0xC1000000 + repl_addr)) = call_type | jump_addr; // Replace the instruction in the loader by the jump to our function + unsigned int jump_addr = repl_func & 0x03fffffc; // Compute jump length to jump from current instruction address to our function address + *((volatile uint32_t *)(0xC1000000 + repl_addr)) = call_type | jump_addr; // Replace the instruction in the loader by the jump to our function // flush caches and invalidate instruction cache DCFlushRange((void*)(0xC1000000 + repl_addr), 4); ICInvalidateRange((void*)(0xC1000000 + repl_addr), 4); } + /* Patch to bypass SDK version tests */ + *((volatile uint32_t *)(0xC1000000 + 0x010095b4)) = 0x480000a0; // ble loc_1009654 (0x408100a0) => b loc_1009654 (0x480000a0) + *((volatile uint32_t *)(0xC1000000 + 0x01009658)) = 0x480000e8; // bge loc_1009740 (0x408100a0) => b loc_1009740 (0x480000e8) + DCFlushRange((void*)(0xC1000000 + 0x010095b4), 4); + ICInvalidateRange((void*)(0xC1000000 + 0x010095b4), 4); + DCFlushRange((void*)(0xC1000000 + 0x01009658), 4); + ICInvalidateRange((void*)(0xC1000000 + 0x01009658), 4); } /* ****************************************************************** */ @@ -661,37 +721,27 @@ static void InstallLoader(private_data_t *private_data) /* ****************************************************************** */ static void InstallFS(private_data_t *private_data) { - /* Check if already installed */ - if (*(volatile unsigned int *)(INSTALL_FS_DONE_ADDR + 0xC1000000) == INSTALL_FS_DONE_FLAG) - return; - *(volatile unsigned int *)(INSTALL_FS_DONE_ADDR + 0xC1000000) = INSTALL_FS_DONE_FLAG; - - // get .text section - unsigned int fs_text_addr = 0; - unsigned int fs_text_len = 0; - unsigned int section_offset = get_section(private_data, private_data->data_fs, ELF_TEXT, &fs_text_len, &fs_text_addr); - unsigned char *fs_text = private_data->data_fs + section_offset; - // get .rodata section - unsigned int fs_rodata_addr = 0; - unsigned int fs_rodata_len = 0; - section_offset = get_section(private_data, private_data->data_fs, ELF_RODATA, &fs_rodata_len, &fs_rodata_addr); - unsigned char *fs_rodata = private_data->data_fs + section_offset; // get .magic section unsigned int fs_magic_addr = 0; unsigned int fs_magic_len = 0; - section_offset = get_section(private_data, private_data->data_fs, ".magic", &fs_magic_len, &fs_magic_addr); - unsigned char *fs_magic = private_data->data_fs + section_offset; - - /* Copy fs code section to memory */ - unsigned int cpy_addr = (0xC1000000 + fs_text_addr); - memcpy((void*)cpy_addr, fs_text, fs_text_len); - DCFlushRange((void*)cpy_addr, fs_text_len); - ICInvalidateRange((void*)cpy_addr, fs_text_len); + unsigned int section_offset = get_section(private_data, private_data->data_elf, ".fs_magic", &fs_magic_len, &fs_magic_addr); + unsigned char *fs_magic = private_data->data_elf + section_offset; + // copy the data of the magic for later patching / unpatching + memcpy((void*)(DATA_RW_BASE_OFFSET + fs_magic_addr), fs_magic, fs_magic_len); + DCFlushRange((void*)(DATA_RW_BASE_OFFSET + fs_magic_addr), fs_magic_len); + // get .magic section + unsigned int magicptr_addr = 0; + unsigned int magicptr_len = 0; + section_offset = get_section(private_data, private_data->data_elf, ".magicptr", &magicptr_len, &magicptr_addr); + unsigned char *magicptr = private_data->data_elf + section_offset; + // copy the data of the magic for later patching / unpatching + memcpy((void*)(0xC1000000 + magicptr_addr), magicptr, magicptr_len); + DCFlushRange((void*)(0xC1000000 + magicptr_addr), magicptr_len); - /* Copy fs rodata section to memory */ - cpy_addr = (0xC1000000 + fs_rodata_addr); - memcpy((void*)cpy_addr, fs_rodata, fs_rodata_len); - DCFlushRange((void*)cpy_addr, fs_rodata_len); + // get .magic section + unsigned int fs_method_calls_addr = 0; + unsigned int fs_method_calls_len = 0; + section_offset = get_section(private_data, private_data->data_elf, ".fs_method_calls", &fs_method_calls_len, &fs_method_calls_addr); /* ------------------------------------------------------------------------------------------------------------------------*/ /* patch the FS functions to branch to our functions */ @@ -700,32 +750,35 @@ static void InstallFS(private_data_t *private_data) void *real; void *replacement; void *call; + unsigned int orig_instr; } *magic = (struct magic_t *)fs_magic; unsigned int len = fs_magic_len / sizeof(struct magic_t); /* Patch branches to it. */ - volatile int *space = (volatile int *)(0xC1000000 + fs_text_addr + fs_text_len); + volatile unsigned int *space = (volatile unsigned int *)(CODE_RW_BASE_OFFSET + fs_method_calls_addr); while (len--) { - int real_addr = (int)magic[len].real; - int repl_addr = (int)magic[len].replacement; - int call_addr = (int)magic[len].call; + unsigned int real_addr = (unsigned int)magic[len].real; + unsigned int repl_addr = (unsigned int)magic[len].replacement; + unsigned int call_addr = (unsigned int)magic[len].call; + unsigned int orig_instr =(unsigned int)magic[len].orig_instr; // set pointer to the real function - *(volatile int *)(0xC1000000 + call_addr) = (int)space - 0xC1000000; + *(volatile unsigned int *)(0xC1000000 + call_addr) = (unsigned int)space - CODE_RW_BASE_OFFSET; // fill the pointer of the real function - *space = *(volatile int *)(0xC1000000 + real_addr); + *space = orig_instr; space++; // jump to real function skipping the "mflr r0" instruction *space = 0x48000002 | ((real_addr + 4) & 0x03fffffc); space++; - DCFlushRange((void*)space - 2, 8); - ICInvalidateRange((void*)space - 2, 8); + DCFlushRange((void*)(space - 2), 8); + ICInvalidateRange((void*)(space - 2), 8); + // the installation is done later on during Mii Maker start, only restore here original instruction to "fix" it all up until new patch // in the real function, replace the "mflr r0" instruction by a jump to the replacement function - *(volatile int *)(0xC1000000 + real_addr) = 0x48000002 | (repl_addr & 0x03fffffc); - DCFlushRange((int *)(0xC1000000 + real_addr), 4); - ICInvalidateRange((int *)(0xC1000000 + real_addr), 4); + *(volatile unsigned int *)(0xC1000000 + real_addr) = orig_instr; + DCFlushRange((unsigned int *)(0xC1000000 + real_addr), 4); + ICInvalidateRange((unsigned int *)(0xC1000000 + real_addr), 4); } } diff --git a/installer/launcher.h b/installer/launcher.h index c46e778..6020345 100755 --- a/installer/launcher.h +++ b/installer/launcher.h @@ -2,6 +2,6 @@ #define LAUNCHER_H /* Include base types */ -#include "../common/types.h" +#include "../src/common/types.h" #endif /* LAUNCHER_H */ diff --git a/loader/Makefile b/loader/Makefile deleted file mode 100755 index ce3dc3c..0000000 --- a/loader/Makefile +++ /dev/null @@ -1,76 +0,0 @@ - -PATH := $(DEVKITPPC)/bin:$(PATH) - -PREFIX ?= powerpc-eabi- -LD := $(PREFIX)ld -AS := $(PREFIX)as -CC := $(PREFIX)gcc -OBJDUMP ?= $(PREFIX)objdump -OBJCOPY ?= $(PREFIX)objcopy - -SFLAGS := -mgekko -mregnames - -# -O2: optimise lots -# -Wall: generate lots of warnings -# -x c: compile as C code -# -std=gnu99: use the C99 standard with GNU extensions -# -ffreestanding: we don't have libc; don't expect we do -# -mrvl: enable wii/gamecube compilation -# -mcpu=750: enable processor specific compilation -# -meabi: enable eabi specific compilation -# -mhard-float: enable hardware floating point instructions -# -fshort-wchar: use 16 bit whcar_t type in keeping with Wii executables -# -msdata-none: do not use r2 or r13 as small data areas -# -memb: enable embedded application specific compilation -# -ffunction-sections: split up functions so linker can garbage collect -# -fdata-sections: split up data so linker can garbage collect -#CFLAGS := -O2 -Wall -x c -std=gnu99 \ - -# optimizing to -Os gives still disc error on some games like mario kart 8 and mario maker (even though they dont work anyway) -# gotta check what else the compiler is optimizing (memory realignment?) -# so for now i reverted it as there is still enough space in payload -# adding it back in will give another 600 - 1000 bytes of space -# CFLAGS := -Os -nostdinc -nostdlib -Wall -x c -std=gnu99 -CFLAGS := -Os -nostdinc -nostdlib -Wall -x c -std=gnu99 \ - -ffreestanding \ - -mrvl -mcpu=750 -meabi -mhard-float -fshort-wchar \ - -msdata=none -memb -ffunction-sections -fdata-sections \ - -Wno-unknown-pragmas -Wno-strict-aliasing - -SRC := $(wildcard *.S) $(wildcard *.c) -OBJ := $(patsubst %.S,build/%.o,$(patsubst %.c,build/%.o,$(SRC))) - -# Simulate an order only dependency -BUILD_REQ := $(filter-out $(wildcard build),build) -BIN_REQ := $(filter-out $(wildcard bin),bin) - -all: build/loader532.elf copy - -bin/loader%.h: build/loader%.text.bin build/loader%.magic.bin $(BIN_REQ) - xxd -i build/loader$*.magic.bin | sed "s/unsigned/static const unsigned/g;s/loader$*/loader/g;s/build_//g" > $@ - xxd -i build/loader$*.text.bin | sed "s/unsigned/static const unsigned/g;s/loader$*/loader/g;s/build_//g" >> $@ - -build/loader%.text.bin: build/loader%.elf $(BUILD_REQ) - $(OBJCOPY) -j .text -O binary $< $@ -build/loader%.magic.bin: build/loader%.elf $(BUILD_REQ) - $(OBJCOPY) -j .magic -O binary $< $@ - -build/loader%.elf: loader%.ld $(OBJ) $(BUILD_REQ) -# $(LD) -T $< $(OBJ) - $(LD) -T $< $(OBJ) -s -L"$(DEVKITPPC)/lib/gcc/powerpc-eabi/4.8.2" -lgcc - -build/%.o: %.c $(BUILD_REQ) - $(CC) -c $(CFLAGS) -o $@ $< -build/%.o: %.S $(BUILD_REQ) - $(AS) $(SFLAGS) -o $@ $< - -bin: - mkdir $@ -build: - mkdir $@ - -copy: - cp build/loader532.elf ../installer/bin/ - -clean: - rm -rf $(wildcard build) $(wildcard bin) $(wildcard ../installer/loader532.h) diff --git a/loader/loader532.ld b/loader/loader532.ld deleted file mode 100755 index 0fc607d..0000000 --- a/loader/loader532.ld +++ /dev/null @@ -1,28 +0,0 @@ -OUTPUT(build/loader532.elf); - -SECTIONS { - .text 0x011de400 : { - *(.text._start); - *(.text*); - } - .magic : { - *(.magic*); - } - /DISCARD/ : { - *(*); - } -} - -/* Instructions addresses to replace by "bl 0x???????? - instr addr - original instruction => comment */ -/* This are the real functions that are used for our purpose */ -PROVIDE(LiWaitIopComplete = 0x0100FFA4); -PROVIDE(LiWaitIopCompleteWithInterrupts = 0x0100FE90); -PROVIDE(LiCheckAndHandleInterrupts = 0x010046B0); -PROVIDE(Loader_SysCallGetProcessIndex = 0x010000A8); -PROVIDE(LiLoadAsync = 0x0101005C); -PROVIDE(strncpy = 0x01010690); -PROVIDE(strnlen = 0x010106E0); - -/* This are just addresses to the real functions which we only need as reference */ -PROVIDE(addr_LiWaitOneChunk = 0x010007EC); -PROVIDE(addr_LiBounceOneChunk = 0x010003B0); \ No newline at end of file diff --git a/menu/Makefile b/menu/Makefile deleted file mode 100755 index 9bcf296..0000000 --- a/menu/Makefile +++ /dev/null @@ -1,66 +0,0 @@ - -PATH := $(DEVKITPPC)/bin:$(PATH) - -PREFIX ?= powerpc-eabi- -LD := $(PREFIX)ld -AS := $(PREFIX)as -CC := $(PREFIX)gcc -OBJDUMP ?= $(PREFIX)objdump -OBJCOPY ?= $(PREFIX)objcopy - -SFLAGS := -mgekko -mregnames - -# -O2: optimise lots -# -Wall: generate lots of warnings -# -x c: compile as C code -# -std=gnu99: use the C99 standard with GNU extensions -# -ffreestanding: we don't have libc; don't expect we do -# -mrvl: enable wii/gamecube compilation -# -mcpu=750: enable processor specific compilation -# -meabi: enable eabi specific compilation -# -mhard-float: enable hardware floating point instructions -# -fshort-wchar: use 16 bit whcar_t type in keeping with Wii executables -# -msdata-none: do not use r2 or r13 as small data areas -# -memb: enable embedded application specific compilation -# -ffunction-sections: split up functions so linker can garbage collect -# -fdata-sections: split up data so linker can garbage collect -CFLAGS := -Os -nostdinc -nostdlib -Wall -x c -std=gnu99 \ - -ffreestanding \ - -mrvl -mcpu=750 -meabi -mhard-float -fshort-wchar \ - -msdata=none -memb -ffunction-sections -fdata-sections \ - -Wno-unknown-pragmas -Wno-strict-aliasing - -SRC := $(wildcard *.S) $(wildcard *.c) -OBJ := $(patsubst %.S,build/%.o,$(patsubst %.c,build/%.o,$(SRC))) - -# Simulate an order only dependency -BUILD_REQ := $(filter-out $(wildcard build),build) -BIN_REQ := $(filter-out $(wildcard bin),bin) - -all: build/menu532.elf copy - -bin/menu%.h: build/menu%.text.bin $(BIN_REQ) - xxd -i $< | sed "s/unsigned/static const unsigned/g;s/menu$*/menu/g;s/build_//g" > $@ - -build/menu%.text.bin: build/menu%.elf $(BUILD_REQ) - $(OBJCOPY) -j .text -O binary $< $@ - -build/menu%.elf: menu%.ld $(OBJ) $(BUILD_REQ) - $(LD) -T $< $(OBJ) -s -L"$(DEVKITPPC)/lib/gcc/powerpc-eabi/4.8.2" -lgcc - -build/%.o: %.c $(BUILD_REQ) - $(CC) -c $(CFLAGS) -o $@ $< -build/%.o: %.S $(BUILD_REQ) - $(AS) $(SFLAGS) -o $@ $< - -bin: - mkdir $@ -build: - mkdir $@ - -copy: - cp build/menu532.elf ../installer/bin/ - -clean: - rm -rf $(wildcard build) $(wildcard bin) $(wildcard ../installer/menu532.h) - diff --git a/menu/menu532.ld b/menu/menu532.ld deleted file mode 100755 index 5cb1cde..0000000 --- a/menu/menu532.ld +++ /dev/null @@ -1,65 +0,0 @@ -OUTPUT(build/menu532.elf); - -SECTIONS { - .text 0x011dd000 : { - *(.text._start); - *(.text*); - } - .rodata : { - *(.rodata*); - } - /DISCARD/ : { - *(*); - } -} - -/* Main */ -PROVIDE(entry_point = 0x1005d180); - -/* System */ -PROVIDE(_Exit = 0x0101cd70); -PROVIDE(OSFatal = 0x1031368); -PROVIDE(DCFlushRange = 0x1023ee8); -PROVIDE(memset = 0x1035a54); -PROVIDE(memcpy = 0x1035a68); -PROVIDE(__gh_errno_ptr = 0x1040308); -PROVIDE(GX2WaitForVsync = 0x1151964); -PROVIDE(__os_snprintf = 0x102f09c); -PROVIDE(title_id = 0x100136D0); - -/* Alloc */ -PROVIDE(MEMAllocFromDefaultHeapEx = 0x1004e9c0); -PROVIDE(MEMAllocFromDefaultHeap = 0x100b4878); -PROVIDE(MEMFreeToDefaultHeap = 0x100b487c); - -/* Libs */ -PROVIDE(OSDynLoad_FindExport = 0x102b790); -PROVIDE(OSDynLoad_Acquire = 0x102a31c); - -/* Screen */ -PROVIDE(OSScreenInit = 0x0103a880); -PROVIDE(OSScreenGetBufferSizeEx = 0x0103a91c); -PROVIDE(OSScreenSetBufferEx = 0x0103a934); -PROVIDE(OSScreenClearBufferEx = 0x0103aa90); -PROVIDE(OSScreenFlipBuffersEx = 0x0103a9d0); -PROVIDE(OSScreenPutFontEx = 0x0103af14); - -/* VPAD */ -PROVIDE(VPADRead = 0x011293d0); - -/* FS Functions */ -PROVIDE(FSAddClient = 0x010689fc); -PROVIDE(FSInitCmdBlock = 0x01068c54); -PROVIDE(FSGetMountSource = 0x0106ec24); -PROVIDE(FSMount = 0x0106ed14); -PROVIDE(FSUnmount = 0x0106ed8c); -PROVIDE(FSOpenDir = 0x0106f690); -PROVIDE(FSReadDir = 0x0106f780); -PROVIDE(FSOpenFile = 0x106ef7c); -PROVIDE(FSReadFile = 0x106f108); -PROVIDE(FSChangeDir = 0x0106eefc); -PROVIDE(FSMakeDir = 0x0106f8e0); -PROVIDE(FSCloseDir = 0x0106f700); -PROVIDE(FSCloseFile = 0x106f088); -PROVIDE(FSGetStat = 0x0106fdc8); -PROVIDE(FSMakeDir = 0x0106f8e0); diff --git a/menu/util.h b/menu/util.h deleted file mode 100644 index f37ef27..0000000 --- a/menu/util.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef __UTIL_H_ -#define __UTIL_H_ - -int strcasecmp(const char *s1, const char *s2); -int strlen(const char* str); -//int strlcpy(char *s1, const char *s2, unsigned int max_size); -void qsort(void *ptr, unsigned int count, unsigned int size, int (*compare)(const void*,const void*)); - -#endif diff --git a/common/common.h b/src/common/common.h old mode 100755 new mode 100644 similarity index 93% rename from common/common.h rename to src/common/common.h index 640c207..4bb3016 --- a/common/common.h +++ b/src/common/common.h @@ -33,8 +33,8 @@ #define MEM_BASE ((void*)0xC0800000) #define MEM_SIZE (*(volatile unsigned int*)(MEM_BASE - 0x04)) #define MEM_OFFSET (*(volatile unsigned int*)(MEM_BASE - 0x08)) -#define MEM_AREA (*(volatile unsigned int*)(MEM_BASE - 0x0C)) -#define MEM_PART (*(volatile unsigned int*)(MEM_BASE - 0x10)) +#define SERVER_IP (*(volatile unsigned int*)(MEM_BASE - 0x0C)) +#define RPX_CHECK_NAME (*(volatile unsigned int*)(MEM_BASE - 0x10)) #define GAME_RPX_LOADED (*(volatile unsigned int*)(MEM_BASE - 0x14)) #define GAME_LAUNCHED (*(volatile unsigned int*)(MEM_BASE - 0x18)) #define LOADIINE_MODE (*(volatile unsigned int*)(MEM_BASE - 0x1C)) // loadiine operation mode (0 = smash bros, 1 = mii maker) @@ -50,7 +50,7 @@ /* RPX Name : from which app/game, our rpx is launched */ // 0xEFE00000 contains the rpx name, 0x63726F73 => cros (for smash brox : cross_f.rpx) // 0xEFE00000 contains the rpx name, 0x66666C5F => ffl_ (for mii maker : ffl_app.rpx) -#define RPX_CHECK_NAME ( (LOADIINE_MODE == LOADIINE_MODE_MII_MAKER) ? 0x66666C5F : 0x63726F73 ) +//#define RPX_CHECK_NAME ( (LOADIINE_MODE == LOADIINE_MODE_MII_MAKER) ? 0x66666C5F : 0x63726F73 ) /* Struct used to organize empty memory areas */ typedef struct _s_mem_area diff --git a/common/fs_defs.h b/src/common/fs_defs.h old mode 100755 new mode 100644 similarity index 100% rename from common/fs_defs.h rename to src/common/fs_defs.h diff --git a/src/common/kernel_defs.h b/src/common/kernel_defs.h new file mode 100644 index 0000000..9d1b5de --- /dev/null +++ b/src/common/kernel_defs.h @@ -0,0 +1,98 @@ +#ifndef __KERNEL_DEFS_H_ +#define __KERNEL_DEFS_H_ + +#include "types.h" +#include "fs_defs.h" + +// original structure in the kernel that is originally 0x1270 long +typedef struct +{ + uint32_t version_cos_xml; // version tag from cos.xml + uint64_t os_version; // os_version from app.xml + uint64_t title_id; // title_id tag from app.xml + uint32_t app_type; // app_type tag from app.xml + uint32_t cmdFlags; // unknown tag as it is always 0 (might be cmdFlags from cos.xml but i am not sure) + char rpx_name[0x1000]; // rpx name from cos.xml + uint32_t unknown2; // 0x050B8304 in mii maker and system menu (looks a bit like permissions complex that got masked!?) + uint32_t unknown3[63]; // those were all zeros, but its probably connected with unknown2 + uint32_t max_size; // max_size in cos.xml which defines the maximum amount of memory reserved for the app + uint32_t avail_size; // avail_size or codegen_size in cos.xml (seems to mostly be 0?) + uint32_t codegen_size; // codegen_size or avail_size in cos.xml (seems to mostly be 0?) + uint32_t codegen_core; // codegen_core in cos.xml (seems to mostly be 1?) + uint32_t max_codesize; // max_codesize in cos.xml + uint32_t overlay_arena; // overlay_arena in cos.xml + uint32_t unknown4[59]; // all zeros it seems + uint32_t default_stack0_size; // not sure because always 0 but very likely + uint32_t default_stack1_size; // not sure because always 0 but very likely + uint32_t default_stack2_size; // not sure because always 0 but very likely + uint32_t default_redzone0_size; // not sure because always 0 but very likely + uint32_t default_redzone1_size; // not sure because always 0 but very likely + uint32_t default_redzone2_size; // not sure because always 0 but very likely + uint32_t exception_stack0_size; // from cos.xml, 0x1000 on mii maker + uint32_t exception_stack1_size; // from cos.xml, 0x1000 on mii maker + uint32_t exception_stack2_size; // from cos.xml, 0x1000 on mii maker + uint32_t sdk_version; // from app.xml, 20909 (0x51AD) on mii maker + uint32_t title_version; // from app.xml, 0x32 on mii maker + /* + // --------------------------------------------------------------------------------------------------------------------------------------------- + // the next part might be changing from title to title?! I don't think its important but nice to know maybe.... + // --------------------------------------------------------------------------------------------------------------------------------------------- + char mlc[4]; // string "mlc" on mii maker and sysmenu + uint32_t unknown5[7]; // all zeros on mii maker and sysmenu + uint32_t unknown6_one; // 0x01 on mii maker and sysmenu + // --------------------------------------------------------------------------------------------------------------------------------------------- + char ACP[4]; // string "ACP" on mii maker and sysmenu + uint32_t unknown7[15]; // all zeros on mii maker and sysmenu + uint32_t unknown8_5; // 0x05 on mii maker and sysmenu + uint32_t unknown9_zero; // 0x00 on mii maker and sysmenu + uint32_t unknown10_ptr; // 0xFF23DD0C pointer on mii maker and sysmenu + // --------------------------------------------------------------------------------------------------------------------------------------------- + char UVD[4]; // string "UVD" on mii maker and sysmenu + uint32_t unknown11[15]; // all zeros on mii maker and sysmenu + uint32_t unknown12_5; // 0x05 on mii maker and sysmenu + uint32_t unknown13_zero; // 0x00 on mii maker and sysmenu + uint32_t unknown14_ptr; // 0xFF23EFC8 pointer on mii maker and sysmenu + // --------------------------------------------------------------------------------------------------------------------------------------------- + char SND[4]; // string "SND" on mii maker and sysmenu + uint32_t unknown15[15]; // all zeros on mii maker and sysmenu + uint32_t unknown16_5; // 0x05 on mii maker and sysmenu + uint32_t unknown17_zero; // 0x00 on mii maker and sysmenu + uint32_t unknown18_ptr; // 0xFF23F014 pointer on mii maker and sysmenu + // --------------------------------------------------------------------------------------------------------------------------------------------- + uint32_t unknown19; // 0x02 on miimaker, 0x0F on system menu + */ + // after that only zeros follow +} __attribute__((packed)) CosAppXmlInfo; + + +// Our own cos/app.xml struct which uses only uses as much memory as really needed, since many things are just zeros in the above structure +// This structure is only 0x64 bytes long + RPX name length (dynamic up to 0x1000 theoretically) +typedef struct +{ + uint32_t version_cos_xml; // version tag from cos.xml + uint64_t os_version; // os_version from app.xml + uint64_t title_id; // title_id tag from app.xml + uint32_t app_type; // app_type tag from app.xml + uint32_t cmdFlags; // unknown tag as it is always 0 (might be cmdFlags from cos.xml but i am not sure) + uint32_t max_size; // max_size in cos.xml which defines the maximum amount of memory reserved for the app + uint32_t avail_size; // avail_size or codegen_size in cos.xml (seems to mostly be 0?) + uint32_t codegen_size; // codegen_size or avail_size in cos.xml (seems to mostly be 0?) + uint32_t codegen_core; // codegen_core in cos.xml (seems to mostly be 1?) + uint32_t max_codesize; // max_codesize in cos.xml + uint32_t overlay_arena; // overlay_arena in cos.xml + uint32_t default_stack0_size; // not sure because always 0 but very likely + uint32_t default_stack1_size; // not sure because always 0 but very likely + uint32_t default_stack2_size; // not sure because always 0 but very likely + uint32_t default_redzone0_size; // not sure because always 0 but very likely + uint32_t default_redzone1_size; // not sure because always 0 but very likely + uint32_t default_redzone2_size; // not sure because always 0 but very likely + uint32_t exception_stack0_size; // from cos.xml, 0x1000 on mii maker + uint32_t exception_stack1_size; // from cos.xml, 0x1000 on mii maker + uint32_t exception_stack2_size; // from cos.xml, 0x1000 on mii maker + uint32_t sdk_version; // from app.xml, 20909 (0x51AD) on mii maker + uint32_t title_version; // from app.xml, 0x32 on mii maker + char rpx_name[FS_MAX_ENTNAME_SIZE]; // rpx name from cos.xml, length 256 as it can't get bigger from FS anyway +} __attribute__((packed)) ReducedCosAppXmlInfo; + + +#endif // __KERNEL_DEFS_H_ diff --git a/src/common/loader_defs.h b/src/common/loader_defs.h new file mode 100644 index 0000000..2880d33 --- /dev/null +++ b/src/common/loader_defs.h @@ -0,0 +1,20 @@ +#ifndef __LOADER_DEFS_H_ +#define __LOADER_DEFS_H_ + +#include "types.h" + +// struct holding the globals of the loader (there are actually more but we don't need others) +typedef struct _loader_globals_t +{ + int sgIsLoadingBuffer; + int sgFileType; + int sgProcId; + int sgGotBytes; + int sgFileOffset; + int sgBufferNumber; + int sgBounceError; + char sgLoadName[0x1000]; +} __attribute__((packed)) loader_globals_t; + + +#endif // __LOADER_DEFS_H_ diff --git a/common/types.h b/src/common/types.h old mode 100755 new mode 100644 similarity index 100% rename from common/types.h rename to src/common/types.h diff --git a/fs/fs.c b/src/fs/fs.c old mode 100755 new mode 100644 similarity index 86% rename from fs/fs.c rename to src/fs/fs.c index 3076863..edda3df --- a/fs/fs.c +++ b/src/fs/fs.c @@ -1,6 +1,9 @@ #include "fs.h" -#include "exception_handler.h" #include "../common/common.h" +#include "../utils/exception_handler.h" +#include "../utils/logger.h" +#include "../utils/strings.h" +#include "../utils/utils.h" #define USE_EXTRA_LOG_FUNCTIONS 0 @@ -13,42 +16,6 @@ int FSAddClientEx(void *r3, void *r4, void *r5); int FSDelClient(void *pClient); -/* Log functions */ -//static const struct { -// const int tag; -// const char *name; -//} tag_names[] = { -// { 1, "LOADER_Start" }, -// { 2, "LOADER_Entry" }, -// { 3, "LOADER_Prep" }, -// { 4, "LiLoadRPLBasics_in_1_load" }, -// { 5, "GetNextBounce_1" }, -// { 6, "GetNextBounce_2" }, -// { 7, "After_GetBounce" }, -// { 8, "GetNextBounce result end" }, -//}; -// -//static const char *get_name_for_tag(int tag) { -// int i = 0; -// for(i = 0; i < sizeof(tag_names) / sizeof(tag_names[0]); i++) -// if(tag == tag_names[i].tag) -// return tag_names[i].name; -// -// return "unknown"; -//} - -/* Common useful functions */ -static inline int toupper(int c) { - return (c >= 'a' && c <= 'z') ? (c - 0x20) : c; -} - -static int strlen(const char* path) { - int i = 0; - while (path[i]) - i++; - return i; -} - /* Client functions */ static int client_num_alloc(void *pClient) { int i; @@ -76,7 +43,7 @@ static int client_num(void *pClient) { return -1; } -static int is_gamefile(char *path) { +static int is_gamefile(const char *path) { // In case the path starts by "//" and not "/" (some games do that ... ...) if (path[0] == '/' && path[1] == '/') path = &path[1]; @@ -109,7 +76,7 @@ static int is_gamefile(char *path) { return 1; } -static int is_savefile(char *path) { +static int is_savefile(const char *path) { // In case the path starts by "//" and not "/" (some games do that ... ...) if (path[0] == '/' && path[1] == '/') @@ -141,7 +108,7 @@ static int is_savefile(char *path) { return 1; } -static void compute_new_path(char* new_path, char* path, int len, int is_save) { +static void compute_new_path(char* new_path, const char* path, int len, int is_save) { int i, n, path_offset = 0; // In case the path starts by "//" and not "/" (some games do that ... ...) @@ -153,9 +120,7 @@ static void compute_new_path(char* new_path, char* path, int len, int is_save) { path_offset = -1; if (!is_save) { - for (n = 0; n < sizeof(bss.mount_base) && bss.mount_base[n] != 0; n++) { - new_path[n] = bss.mount_base[n]; - } + n = strlcpy(new_path, bss.mount_base, sizeof(bss.mount_base)); // copy the content file path with slash at the beginning for (i = 0; i < (len - 12 - path_offset); i++) { @@ -170,8 +135,7 @@ static void compute_new_path(char* new_path, char* path, int len, int is_save) { new_path[n++] = '\0'; } else { - for (n = 0; n < sizeof(bss.save_base) && bss.save_base[n] != 0; n++) - new_path[n] = bss.save_base[n]; + n = strlcpy(new_path, bss.save_base, sizeof(bss.save_base)); new_path[n++] = '/'; // Create path for common and user dirs @@ -216,15 +180,17 @@ static int GetCurClient(void *pClient) { } return -1; } - -/* ***************************************************************************** - * Base functions - * ****************************************************************************/ - +#include "../common/kernel_defs.h" DECL(int, FSInit, void) { if ((int)bss_ptr == 0x0a000000) { - bss_ptr = memalign(sizeof(struct bss_t), 0x40); + // allocate memory for our stuff + void *mem_ptr = memalign(sizeof(struct bss_t), 0x40); + if(!mem_ptr) + return real_FSInit(); + + // copy pointer + bss_ptr = mem_ptr; memset(bss_ptr, 0, sizeof(struct bss_t)); // setup exceptions @@ -235,8 +201,8 @@ DECL(int, FSInit, void) { // create game save path prefix __os_snprintf(bss.save_base, sizeof(bss.save_base), "%s%s/%s", CAFE_OS_SD_PATH, SD_SAVES_PATH, GAME_DIR_NAME); - fs_connect(&bss.global_sock); - + logger_connect(&bss.global_sock); + // Call real FSInit int result = real_FSInit(); @@ -256,18 +222,20 @@ DECL(int, FSInit, void) { FSAddClientEx(pClient, 0, FS_RET_NO_ERROR); fs_mount_sd(bss.global_sock, pClient, pCmd); - + FSDelClient(pClient); MEMFreeToDefaultHeap(pCmd); MEMFreeToDefaultHeap(pClient); - + return result; } return real_FSInit(); } + DECL(int, FSShutdown, void) { if ((int)bss_ptr != 0x0a000000) { - fs_disconnect(bss.global_sock); + logger_disconnect(bss.global_sock); + bss.global_sock = -1; } return real_FSShutdown(); } @@ -279,7 +247,7 @@ DECL(int, FSAddClientEx, void *r3, void *r4, void *r5) { if (GAME_RPX_LOADED != 0) { int client = client_num_alloc(r3); if (client >= 0) { - if (fs_connect(&bss.socket_fs[client]) != 0) + if (logger_connect(&bss.socket_fs[client]) != 0) client_num_free(client); } } @@ -287,11 +255,12 @@ DECL(int, FSAddClientEx, void *r3, void *r4, void *r5) { return res; } + DECL(int, FSDelClient, void *pClient) { if ((int)bss_ptr != 0x0a000000) { int client = client_num(pClient); if (client >= 0) { - fs_disconnect(bss.socket_fs[client]); + logger_disconnect(bss.socket_fs[client]); client_num_free(client); } } @@ -304,7 +273,7 @@ DECL(int, FSDelClient, void *pClient) { /* ***************************************************************************** * Replacement functions * ****************************************************************************/ -DECL(int, FSGetStat, void *pClient, void *pCmd, char *path, void *stats, int error) { +DECL(int, FSGetStat, void *pClient, void *pCmd, const char *path, void *stats, int error) { int client = GetCurClient(pClient); if (client != -1) { // log @@ -326,7 +295,7 @@ DECL(int, FSGetStat, void *pClient, void *pCmd, char *path, void *stats, int err return real_FSGetStat(pClient, pCmd, path, stats, error); } -DECL(int, FSGetStatAsync, void *pClient, void *pCmd, char *path, void *stats, int error, FSAsyncParams *asyncParams) { +DECL(int, FSGetStatAsync, void *pClient, void *pCmd, const char *path, void *stats, int error, FSAsyncParams *asyncParams) { int client = GetCurClient(pClient); if (client != -1) { // log @@ -347,7 +316,8 @@ DECL(int, FSGetStatAsync, void *pClient, void *pCmd, char *path, void *stats, in return real_FSGetStatAsync(pClient, pCmd, path, stats, error, asyncParams); } -DECL(int, FSOpenFile, void *pClient, void *pCmd, char *path, char *mode, int *handle, int error) { +DECL(int, FSOpenFile, void *pClient, void *pCmd, const char *path, const char *mode, int *handle, int error) { +/* int client = GetCurClient(pClient); if (client != -1) { // log @@ -365,11 +335,11 @@ DECL(int, FSOpenFile, void *pClient, void *pCmd, char *path, char *mode, int *ha return real_FSOpenFile(pClient, pCmd, new_path, mode, handle, error); } } - +*/ return real_FSOpenFile(pClient, pCmd, path, mode, handle, error); } -DECL(int, FSOpenFileAsync, void *pClient, void *pCmd, char *path, char *mode, int *handle, int error, FSAsyncParams *asyncParams) { +DECL(int, FSOpenFileAsync, void *pClient, void *pCmd, const char *path, const char *mode, int *handle, int error, const FSAsyncParams *asyncParams) { int client = GetCurClient(pClient); if (client != -1) { // log @@ -389,7 +359,7 @@ DECL(int, FSOpenFileAsync, void *pClient, void *pCmd, char *path, char *mode, in return real_FSOpenFileAsync(pClient, pCmd, path, mode, handle, error, asyncParams); } -DECL(int, FSOpenDir, void *pClient, void* pCmd, char *path, int *handle, int error) { +DECL(int, FSOpenDir, void *pClient, void* pCmd, const char *path, int *handle, int error) { int client = GetCurClient(pClient); if (client != -1) { // log @@ -409,7 +379,7 @@ DECL(int, FSOpenDir, void *pClient, void* pCmd, char *path, int *handle, int err return real_FSOpenDir(pClient, pCmd, path, handle, error); } -DECL(int, FSOpenDirAsync, void *pClient, void* pCmd, char *path, int *handle, int error, FSAsyncParams *asyncParams) { +DECL(int, FSOpenDirAsync, void *pClient, void* pCmd, const char *path, int *handle, int error, FSAsyncParams *asyncParams) { int client = GetCurClient(pClient); if (client != -1) { // log @@ -449,7 +419,7 @@ DECL(int, FSChangeDir, void *pClient, void *pCmd, char *path, int error) { return real_FSChangeDir(pClient, pCmd, path, error); } -DECL(int, FSChangeDirAsync, void *pClient, void *pCmd, char *path, int error, FSAsyncParams *asyncParams) { +DECL(int, FSChangeDirAsync, void *pClient, void *pCmd, const char *path, int error, FSAsyncParams *asyncParams) { int client = GetCurClient(pClient); if (client != -1) { // log @@ -470,7 +440,7 @@ DECL(int, FSChangeDirAsync, void *pClient, void *pCmd, char *path, int error, FS } // only for saves on sdcard -DECL(int, FSMakeDir, void *pClient, void *pCmd, char *path, int error) { +DECL(int, FSMakeDir, void *pClient, void *pCmd, const char *path, int error) { int client = GetCurClient(pClient); if (client != -1) { // log @@ -493,7 +463,7 @@ DECL(int, FSMakeDir, void *pClient, void *pCmd, char *path, int error) { } // only for saves on sdcard -DECL(int, FSMakeDirAsync, void *pClient, void *pCmd, char *path, int error, FSAsyncParams *asyncParams) { +DECL(int, FSMakeDirAsync, void *pClient, void *pCmd, const char *path, int error, FSAsyncParams *asyncParams) { int client = GetCurClient(pClient); if (client != -1) { // log @@ -516,7 +486,7 @@ DECL(int, FSMakeDirAsync, void *pClient, void *pCmd, char *path, int error, FSAs } // only for saves on sdcard -DECL(int, FSRename, void *pClient, void *pCmd, char *oldPath, char *newPath, int error) { +DECL(int, FSRename, void *pClient, void *pCmd, const char *oldPath, const char *newPath, int error) { int client = GetCurClient(pClient); if (client != -1) { // log @@ -547,7 +517,7 @@ DECL(int, FSRename, void *pClient, void *pCmd, char *oldPath, char *newPath, int } // only for saves on sdcard -DECL(int, FSRenameAsync, void *pClient, void *pCmd, char *oldPath, char *newPath, int error, FSAsyncParams *asyncParams) { +DECL(int, FSRenameAsync, void *pClient, void *pCmd, const char *oldPath, const char *newPath, int error, FSAsyncParams *asyncParams) { int client = GetCurClient(pClient); if (client != -1) { // log @@ -578,7 +548,7 @@ DECL(int, FSRenameAsync, void *pClient, void *pCmd, char *oldPath, char *newPath } // only for saves on sdcard -DECL(int, FSRemove, void *pClient, void *pCmd, char *path, int error) { +DECL(int, FSRemove, void *pClient, void *pCmd, const char *path, int error) { int client = GetCurClient(pClient); if (client != -1) { // log @@ -601,7 +571,7 @@ DECL(int, FSRemove, void *pClient, void *pCmd, char *path, int error) { } // only for saves on sdcard -DECL(int, FSRemoveAsync, void *pClient, void *pCmd, char *path, int error, FSAsyncParams *asyncParams) { +DECL(int, FSRemoveAsync, void *pClient, void *pCmd, const char *path, int error, FSAsyncParams *asyncParams) { int client = GetCurClient(pClient); if (client != -1) { // log @@ -623,7 +593,7 @@ DECL(int, FSRemoveAsync, void *pClient, void *pCmd, char *path, int error, FSAsy return real_FSRemoveAsync(pClient, pCmd, path, error, asyncParams); } -DECL(int, FSFlushQuota, void *pClient, void *pCmd, char* path, int error) { +DECL(int, FSFlushQuota, void *pClient, void *pCmd, const char* path, int error) { int client = GetCurClient(pClient); if (client != -1) { // log @@ -647,7 +617,7 @@ DECL(int, FSFlushQuota, void *pClient, void *pCmd, char* path, int error) { } return real_FSFlushQuota(pClient, pCmd, path, error); } -DECL(int, FSFlushQuotaAsync, void *pClient, void *pCmd, char *path, int error, FSAsyncParams *asyncParams) { +DECL(int, FSFlushQuotaAsync, void *pClient, void *pCmd, const char *path, int error, FSAsyncParams *asyncParams) { int client = GetCurClient(pClient); if (client != -1) { // log @@ -672,7 +642,7 @@ DECL(int, FSFlushQuotaAsync, void *pClient, void *pCmd, char *path, int error, F return real_FSFlushQuotaAsync(pClient, pCmd, path, error, asyncParams); } -DECL(int, FSGetFreeSpaceSize, void *pClient, void *pCmd, char *path, uint64_t *returnedFreeSize, int error) { +DECL(int, FSGetFreeSpaceSize, void *pClient, void *pCmd, const char *path, uint64_t *returnedFreeSize, int error) { int client = GetCurClient(pClient); if (client != -1) { // log @@ -696,7 +666,7 @@ DECL(int, FSGetFreeSpaceSize, void *pClient, void *pCmd, char *path, uint64_t *r } return real_FSGetFreeSpaceSize(pClient, pCmd, path, returnedFreeSize, error); } -DECL(int, FSGetFreeSpaceSizeAsync, void *pClient, void *pCmd, char *path, uint64_t *returnedFreeSize, int error, FSAsyncParams *asyncParams) { +DECL(int, FSGetFreeSpaceSizeAsync, void *pClient, void *pCmd, const char *path, uint64_t *returnedFreeSize, int error, FSAsyncParams *asyncParams) { int client = GetCurClient(pClient); if (client != -1) { // log @@ -721,7 +691,7 @@ DECL(int, FSGetFreeSpaceSizeAsync, void *pClient, void *pCmd, char *path, uint64 return real_FSGetFreeSpaceSizeAsync(pClient, pCmd, path, returnedFreeSize, error, asyncParams); } -DECL(int, FSRollbackQuota, void *pClient, void *pCmd, char *path, int error) { +DECL(int, FSRollbackQuota, void *pClient, void *pCmd, const char *path, int error) { int client = GetCurClient(pClient); if (client != -1) { // log @@ -744,7 +714,7 @@ DECL(int, FSRollbackQuota, void *pClient, void *pCmd, char *path, int error) { } return real_FSRollbackQuota(pClient, pCmd, path, error); } -DECL(int, FSRollbackQuotaAsync, void *pClient, void *pCmd, char *path, int error, FSAsyncParams *asyncParams) { +DECL(int, FSRollbackQuotaAsync, void *pClient, void *pCmd, const char *path, int error, FSAsyncParams *asyncParams) { int client = GetCurClient(pClient); if (client != -1) { // log @@ -903,17 +873,7 @@ static int CheckAndLoadRPL(const char *rpl) { } // compare name string case insensitive and without ".rpl" extension - int found = 1; - for (int x = 0; x < len; x++) - { - if (toupper(rpl_entry->name[x]) != toupper(rpl[x])) - { - found = 0; - break; - } - } - - if (found) + if (strncasecmp(rpl_entry->name, rpl, len) == 0) return LoadRPLToMemory(rpl_entry); } while((rpl_entry = rpl_entry->next) != 0); @@ -1068,47 +1028,47 @@ DECL(int, FSGetVolumeState_log, void *pClient) { } #endif - /* ***************************************************************************** * Creates function pointer array * ****************************************************************************/ -#define MAKE_MAGIC(x) { x, my_ ## x, &real_ ## x } +#define MAKE_MAGIC(x, orig_instr) { x, my_ ## x, &real_ ## x, orig_instr } -struct magic_t { +const struct fs_magic_t { const void *real; const void *replacement; const void *call; -} methods[] __attribute__((section(".magic"))) = { + const unsigned int orig_instr; +} fs_methods[] __attribute__((section(".fs_magic"))) = { // Common FS functions - MAKE_MAGIC(FSInit), - MAKE_MAGIC(FSShutdown), - MAKE_MAGIC(FSAddClientEx), - MAKE_MAGIC(FSDelClient), + MAKE_MAGIC(FSInit, 0x7C0802A6), + MAKE_MAGIC(FSShutdown, 0x4E800020), + MAKE_MAGIC(FSAddClientEx, 0x9421FFD8), + MAKE_MAGIC(FSDelClient, 0x7C0802A6), // Replacement functions - MAKE_MAGIC(FSGetStat), - MAKE_MAGIC(FSGetStatAsync), - MAKE_MAGIC(FSOpenFile), - MAKE_MAGIC(FSOpenFileAsync), - MAKE_MAGIC(FSOpenDir), - MAKE_MAGIC(FSOpenDirAsync), - MAKE_MAGIC(FSChangeDir), - MAKE_MAGIC(FSChangeDirAsync), - MAKE_MAGIC(FSMakeDir), - MAKE_MAGIC(FSMakeDirAsync), - MAKE_MAGIC(FSRename), - MAKE_MAGIC(FSRenameAsync), - MAKE_MAGIC(FSRemove), - MAKE_MAGIC(FSRemoveAsync), - MAKE_MAGIC(FSFlushQuota), - MAKE_MAGIC(FSFlushQuotaAsync), - MAKE_MAGIC(FSGetFreeSpaceSize), - MAKE_MAGIC(FSGetFreeSpaceSizeAsync), - MAKE_MAGIC(FSRollbackQuota), - MAKE_MAGIC(FSRollbackQuotaAsync), + MAKE_MAGIC(FSGetStat, 0x9421FFD8), + MAKE_MAGIC(FSGetStatAsync, 0x7D094378), + MAKE_MAGIC(FSOpenFile, 0x9421FFD0), + MAKE_MAGIC(FSOpenFileAsync, 0x7C0802A6), + MAKE_MAGIC(FSOpenDir, 0x9421FFD8), + MAKE_MAGIC(FSOpenDirAsync, 0x9421FFE0), + MAKE_MAGIC(FSChangeDir, 0x7C0802A6), + MAKE_MAGIC(FSChangeDirAsync, 0x7C0802A6), + MAKE_MAGIC(FSMakeDir, 0x7C0802A6), + MAKE_MAGIC(FSMakeDirAsync, 0x7C0802A6), + MAKE_MAGIC(FSRename, 0x9421FFD8), + MAKE_MAGIC(FSRenameAsync, 0x9421FFE0), + MAKE_MAGIC(FSRemove, 0x7C0802A6), + MAKE_MAGIC(FSRemoveAsync, 0x7C0802A6), + MAKE_MAGIC(FSFlushQuota, 0x7C0802A6), + MAKE_MAGIC(FSFlushQuotaAsync, 0x7C0802A6), + MAKE_MAGIC(FSGetFreeSpaceSize, 0x9421FFD8), + MAKE_MAGIC(FSGetFreeSpaceSizeAsync, 0x7D094378), + MAKE_MAGIC(FSRollbackQuota, 0x7C0802A6), + MAKE_MAGIC(FSRollbackQuotaAsync, 0x7C0802A6), // Dynamic RPL loading functions - MAKE_MAGIC(OSDynLoad_Acquire), + MAKE_MAGIC(OSDynLoad_Acquire, 0x38A00000), #if (USE_EXTRA_LOG_FUNCTIONS == 1) MAKE_MAGIC(OSDynLoad_GetModuleName), MAKE_MAGIC(OSDynLoad_IsModuleLoaded), @@ -1135,3 +1095,29 @@ struct magic_t { MAKE_MAGIC(FSGetVolumeState_log), #endif }; + +/* a buffer to place all the replaced instructions and calls to the real functions */ +const unsigned char fs_methods_calls[sizeof(fs_methods)] __attribute__((section(".fs_method_calls"))); + +void PatchFsMethods(void) +{ + static uint8_t ucFsMethodsPatched = 0; + if(!ucFsMethodsPatched) + { + ucFsMethodsPatched = 1; + /* Patch branches to it. */ + int len = sizeof(fs_methods) / sizeof(struct fs_magic_t); + while (len--) { + unsigned int real_addr = (unsigned int)fs_methods[len].real; + unsigned int repl_addr = (unsigned int)fs_methods[len].replacement; + + unsigned int replace_instr = 0x48000002 | (repl_addr & 0x03fffffc); + + if(*(volatile unsigned int *)(0xC1000000 + real_addr) != replace_instr) { + // in the real function, replace the "mflr r0" instruction by a jump to the replacement function + *(volatile unsigned int *)(0xC1000000 + real_addr) = replace_instr; + FlushBlock((0xC1000000 + real_addr)); + } + } + } +} diff --git a/src/fs/fs.h b/src/fs/fs.h new file mode 100644 index 0000000..876a7c2 --- /dev/null +++ b/src/fs/fs.h @@ -0,0 +1,44 @@ +#ifndef _FS_H_ +#define _FS_H_ + +#include "../common/fs_defs.h" + +extern void GX2WaitForVsync(void); + +/* OS stuff */ +extern void DCFlushRange(const void *p, unsigned int s); + +/* SDCard functions */ +extern FSStatus FSGetMountSource(void *pClient, void *pCmd, FSSourceType type, FSMountSource *source, FSRetFlag errHandling); +extern FSStatus FSMount(void *pClient, void *pCmd, FSMountSource *source, char *target, uint bytes, FSRetFlag errHandling); +extern FSStatus FSReadFile(FSClient *pClient, FSCmdBlock *pCmd, void *buffer, int size, int count, int fd, FSFlag flag, FSRetFlag errHandling); +extern void FSInitCmdBlock(FSCmdBlock *pCmd); +extern FSStatus FSCloseFile(FSClient *pClient, FSCmdBlock *pCmd, int fd, int error); + +/* Async callback definition */ +typedef void (*FSAsyncCallback)(void *pClient, void *pCmd, int result, void *context); +typedef struct +{ + FSAsyncCallback userCallback; + void *userContext; + void *ioMsgQueue; +} FSAsyncParams; + +/* Forward declarations */ +#define MAX_CLIENT 32 + +struct bss_t { + int global_sock; + int socket_fs[MAX_CLIENT]; + void *pClient_fs[MAX_CLIENT]; + volatile int lock; + char mount_base[255]; + char save_base[255]; +}; + +#define bss_ptr (*(struct bss_t **)0x100000e4) +#define bss (*bss_ptr) + +void PatchFsMethods(void); + +#endif /* _FS_H */ diff --git a/src/kernel/kernel_functions.c b/src/kernel/kernel_functions.c new file mode 100644 index 0000000..5c997b4 --- /dev/null +++ b/src/kernel/kernel_functions.c @@ -0,0 +1,69 @@ +#include "../common/common.h" +#include "../common/kernel_defs.h" +#include "../utils/strings.h" + +/* our BSS struct */ +extern ReducedCosAppXmlInfo cosAppXmlInfoStruct; + +/* + * This function is a kernel hook function. It is called directly from kernel code at position 0xFFF18558. + */ +void my_PrepareTitle(CosAppXmlInfo *xmlKernelInfo) +{ + /* + * Setup a DBAT access for our 0xC0800000 area and our 0xBC000000 area which hold our variables like GAME_LAUNCHED and our BSS/rodata section + */ + register int dbatu0, dbatl0, dbatu1, dbatl1; + // save the original DBAT value + asm volatile("mfdbatu %0, 0" : "=r" (dbatu0)); + asm volatile("mfdbatl %0, 0" : "=r" (dbatl0)); + asm volatile("mfdbatu %0, 1" : "=r" (dbatu1)); + asm volatile("mfdbatl %0, 1" : "=r" (dbatl1)); + // write our own DBATs into the array + asm volatile("mtdbatu 0, %0" : : "r" (0xC0001FFF)); + asm volatile("mtdbatl 0, %0" : : "r" (0x30000012)); + asm volatile("mtdbatu 1, %0" : : "r" (0xB0801FFF)); + asm volatile("mtdbatl 1, %0" : : "r" (0x20800012)); + asm volatile("eieio; isync"); + + + //! TODO: add Smash Bros IDs for the check? + // check for Mii Maker ID (region independent check) + if(GAME_LAUNCHED && (xmlKernelInfo->title_id & 0xFFFFFFFFFFFFF0FF) == 0x000500101004A000) + { + //! Copy all data from the parsed XML info + strlcpy(xmlKernelInfo->rpx_name, cosAppXmlInfoStruct.rpx_name, sizeof(cosAppXmlInfoStruct.rpx_name)); + + xmlKernelInfo->version_cos_xml = cosAppXmlInfoStruct.version_cos_xml; + xmlKernelInfo->os_version = cosAppXmlInfoStruct.os_version; + xmlKernelInfo->title_id = cosAppXmlInfoStruct.title_id; + xmlKernelInfo->app_type = cosAppXmlInfoStruct.app_type; + xmlKernelInfo->cmdFlags = cosAppXmlInfoStruct.cmdFlags; + xmlKernelInfo->max_size = cosAppXmlInfoStruct.max_size; + xmlKernelInfo->avail_size = cosAppXmlInfoStruct.avail_size; + xmlKernelInfo->codegen_size = cosAppXmlInfoStruct.codegen_size; + xmlKernelInfo->codegen_core = cosAppXmlInfoStruct.codegen_core; + xmlKernelInfo->max_codesize = cosAppXmlInfoStruct.max_codesize; + xmlKernelInfo->overlay_arena = cosAppXmlInfoStruct.overlay_arena; + xmlKernelInfo->default_stack0_size = cosAppXmlInfoStruct.default_stack0_size; + xmlKernelInfo->default_stack1_size = cosAppXmlInfoStruct.default_stack1_size; + xmlKernelInfo->default_stack2_size = cosAppXmlInfoStruct.default_stack2_size; + xmlKernelInfo->default_redzone0_size = cosAppXmlInfoStruct.default_redzone0_size; + xmlKernelInfo->default_redzone1_size = cosAppXmlInfoStruct.default_redzone1_size; + xmlKernelInfo->default_redzone2_size = cosAppXmlInfoStruct.default_redzone2_size; + xmlKernelInfo->exception_stack0_size = cosAppXmlInfoStruct.exception_stack0_size; + xmlKernelInfo->exception_stack1_size = cosAppXmlInfoStruct.exception_stack1_size; + xmlKernelInfo->exception_stack2_size = cosAppXmlInfoStruct.exception_stack2_size; + xmlKernelInfo->sdk_version = cosAppXmlInfoStruct.sdk_version; + xmlKernelInfo->title_version = cosAppXmlInfoStruct.title_version; + } + + /* + * Restore original DBAT value + */ + asm volatile("mfdbatu %0, 0" : "=r" (dbatu0)); + asm volatile("mfdbatl %0, 0" : "=r" (dbatl0)); + asm volatile("mfdbatu %0, 1" : "=r" (dbatu1)); + asm volatile("mfdbatl %0, 1" : "=r" (dbatl1)); + asm volatile("eieio; isync"); +} diff --git a/src/kernel/kernel_hooks.S b/src/kernel/kernel_hooks.S new file mode 100644 index 0000000..6d38ba6 --- /dev/null +++ b/src/kernel/kernel_hooks.S @@ -0,0 +1,110 @@ + +.section ".kernel_code" + .globl SaveAndResetDataBATs_And_SRs_hook +SaveAndResetDataBATs_And_SRs_hook: + # restore the kernel instructions that we replaced + li r7, 1 + mtsr 0, r7 + mtsr 1, r7 + mtsr 2, r7 + mtsr 3, r7 + mtsr 4, r7 + mtsr 5, r7 + mtsr 6, r7 + mtsr 7, r7 + mtsr 8, r7 + mtsr 9, r7 + mtsr 10, r7 + mtsr 11, r7 + mtsr 12, r7 + mtsr 13, r7 + mtsr 14, r7 + mtsr 15, r4 + isync + + # call back the position in kernel after our patch + lis r7, 0xFFF1 + ori r7, r7, 0xD78C + mtctr r7 + bctr + +.extern my_PrepareTitle +.section ".kernel_code" + .globl my_PrepareTitle_hook +my_PrepareTitle_hook: + # store all registers on stack to avoid issues with the call to C functions + stwu r1, -0x90(r1) + # registers for our own usage + # only need r31 and rest is from tests before, just leaving it for later tests + stw r28, 0x20(r1) + stw r29, 0x24(r1) + stw r30, 0x28(r1) + stw r31, 0x2C(r1) + + stw r3, 0x30(r1) + stw r4, 0x34(r1) + stw r5, 0x38(r1) + stw r6, 0x3C(r1) + stw r7, 0x40(r1) + stw r8, 0x44(r1) + stw r9, 0x48(r1) + stw r10, 0x4C(r1) + stw r11, 0x50(r1) + stw r12, 0x54(r1) + stw r13, 0x58(r1) + stw r14, 0x5C(r1) + stw r15, 0x60(r1) + stw r16, 0x64(r1) + stw r17, 0x68(r1) + stw r18, 0x6C(r1) + stw r19, 0x70(r1) + stw r20, 0x74(r1) + stw r21, 0x78(r1) + stw r22, 0x7C(r1) + + # the cos.xml/app.xml structure is at the location 0x68 of r11 + # there are actually many places that can be hooked for it + # e.g. 0xFFF16130 and r27 points to this structure + addi r3, r11, 0x68 + bl my_PrepareTitle + + # setup CTR to jump back to kernel code + lis r31, 0xFFF1 + ori r31, r31, 0x855C + mtspr CTR, r31 + + # restore all original values of registers from stack + lwz r28, 0x20(r1) + lwz r29, 0x24(r1) + lwz r30, 0x28(r1) + lwz r31, 0x2C(r1) + + lwz r3, 0x30(r1) + lwz r4, 0x34(r1) + lwz r5, 0x38(r1) + lwz r6, 0x3C(r1) + lwz r7, 0x40(r1) + lwz r8, 0x44(r1) + lwz r9, 0x48(r1) + lwz r10, 0x4C(r1) + lwz r11, 0x50(r1) + lwz r12, 0x54(r1) + lwz r13, 0x58(r1) + lwz r14, 0x5C(r1) + lwz r15, 0x60(r1) + lwz r16, 0x64(r1) + lwz r17, 0x68(r1) + lwz r18, 0x6C(r1) + lwz r19, 0x70(r1) + lwz r20, 0x74(r1) + lwz r21, 0x78(r1) + lwz r22, 0x7C(r1) + + # restore the stack + addi r1, r1, 0x90 + + # restore original instruction that we replaced in the kernel + clrlwi r7, r12, 0 + + # jump back + bctr diff --git a/fs/fs532.ld b/src/link.ld old mode 100755 new mode 100644 similarity index 61% rename from fs/fs532.ld rename to src/link.ld index 6cd4488..0b36cd9 --- a/fs/fs532.ld +++ b/src/link.ld @@ -1,24 +1,42 @@ -OUTPUT(fs532.elf); +OUTPUT(loadiine.elf); SECTIONS { - .text 0x011df800 : { - server_ip = .; - . = . + 4; - *(.text._start); + .kernel_code 0x00AE1000 : { + *(.kernel_code*); + } + . = .; + .text : { *(.text*); - *(.magicptr*); } - .magic : { - *(.magic*); + .menu_magic : { + *(.menu_magic*); + } + .loader_magic : { + *(.loader_magic*); + } + .fs_method_calls : { + *(.fs_method_calls*); } + . = . + 0xBC000000; .rodata : { *(.rodata*); } + .bss : { + *(.bss*); + } + .fs_magic : { + *(.fs_magic*); + } + .magicptr 0x011DD000 : { + *(.magicptr*); + } /DISCARD/ : { *(*); } } +/******************************************************** FS ********************************************************/ +/* coreinit.rpl difference in addresses 0xFE3C00 */ /* FSA methods */ PROVIDE(FSAInit = 0x10608ac); PROVIDE(FSAShutdown = 0x1060974); @@ -29,6 +47,7 @@ PROVIDE(FSAOpenFile = 0x10621f8); /* FS base methods */ PROVIDE(FSInit = 0x10683c8); PROVIDE(FSShutdown = 0x1068538); +PROVIDE(FSAddClient = 0x010689fc); PROVIDE(FSAddClientEx = 0x10685fc); PROVIDE(FSDelClient = 0x1068a08); PROVIDE(FSInitCmdBlock = 0x01068c54); @@ -55,9 +74,11 @@ PROVIDE(FSGetFreeSpaceSizeAsync = 0x0106c008); PROVIDE(FSRollbackQuota = 0x0106fc48); PROVIDE(FSRollbackQuotaAsync = 0x0106bb50); -/* FS methods - Used by Dynamic RPL loading */ +/* FS methods - not replaced */ +PROVIDE(FSReadDir = 0x0106f780); PROVIDE(FSReadFile = 0x106f108); PROVIDE(FSCloseFile = 0x106f088); +PROVIDE(FSCloseDir = 0x0106f700); /* FS methods - log */ PROVIDE(FSCloseFile_log = 0x106f088); @@ -87,6 +108,7 @@ PROVIDE(FSGetVolumeState_log = 0x01068e20); /* FS methods for sd card */ PROVIDE(FSGetMountSource = 0x0106ec24); PROVIDE(FSMount = 0x0106ed14); +PROVIDE(FSUnmount = 0x0106ed8c); /* GX2 methods */ PROVIDE(GX2WaitForVsync = 0x1151964); @@ -98,9 +120,27 @@ PROVIDE(socketclose = 0x10c2314); PROVIDE(connect = 0x10c0828); PROVIDE(send = 0x10c16ac); PROVIDE(recv = 0x10c0aec); +PROVIDE(setsockopt = 0x10C2E0C); + +/* Standard library methods */ +PROVIDE(MEMAllocFromDefaultHeapEx = 0x1004e9c0); +PROVIDE(MEMAllocFromDefaultHeap = 0x100b4878); +PROVIDE(MEMFreeToDefaultHeap = 0x100b487c); + +/* Screen */ +PROVIDE(OSScreenInit = 0x0103a880); +PROVIDE(OSScreenGetBufferSizeEx = 0x0103a91c); +PROVIDE(OSScreenSetBufferEx = 0x0103a934); +PROVIDE(OSScreenClearBufferEx = 0x0103aa90); +PROVIDE(OSScreenFlipBuffersEx = 0x0103a9d0); +PROVIDE(OSScreenPutFontEx = 0x0103af14); + +/* OS data */ +PROVIDE(title_id = 0x100136D0); /* OS methods */ PROVIDE(OSDynLoad_Acquire = 0x0102A31C); +PROVIDE(OSDynLoad_FindExport = 0x102b790); PROVIDE(OSDynLoad_IsModuleLoaded = 0x0102A504); PROVIDE(OSDynLoad_GetModuleName = 0x0102B960); PROVIDE(__os_snprintf = 0x102f09c); @@ -108,12 +148,21 @@ PROVIDE(OSFatal = 0x1031368); PROVIDE(OSSetExceptionCallbackEx = 0x010443f8); PROVIDE(DCFlushRange = 0x1023ee8); -/* Standard library methods */ -PROVIDE(memcpy = 0x1035a68); -PROVIDE(memset = 0x1035a54); -PROVIDE(MEMAllocFromDefaultHeapEx = 0x1004e9c0); -PROVIDE(MEMAllocFromDefaultHeap = 0x100b4878); -PROVIDE(MEMFreeToDefaultHeap = 0x100b487c); +/* VPAD */ +PROVIDE(VPADRead = 0x011293d0); -/* OS data */ -PROVIDE(title_id = 0x100136D0); +/******************************************************** Menu ********************************************************/ +/* Mii Maker */ +PROVIDE(MiiMaker_main = 0x1005d180); + +/******************************************************* Loader *******************************************************/ +/* This are the real functions that are used for our purpose */ +PROVIDE(LiWaitIopComplete = 0x0100FFA4); +PROVIDE(LiWaitIopCompleteWithInterrupts = 0x0100FE90); +PROVIDE(LiCheckAndHandleInterrupts = 0x010046B0); +PROVIDE(Loader_SysCallGetProcessIndex = 0x010000A8); +PROVIDE(LiLoadAsync = 0x0101005C); + +/* This are just addresses to the real functions which we only need as reference */ +PROVIDE(addr_LiWaitOneChunk = 0x010007EC); +PROVIDE(addr_LiBounceOneChunk = 0x010003B0); \ No newline at end of file diff --git a/loader/loader.c b/src/loader/loader.c old mode 100755 new mode 100644 similarity index 89% rename from loader/loader.c rename to src/loader/loader.c index 6c1e92d..059ea8c --- a/loader/loader.c +++ b/src/loader/loader.c @@ -1,26 +1,10 @@ #include "loader.h" #include "../common/common.h" +#include "../common/loader_defs.h" +#include "../utils/strings.h" #define REPLACE_LIBOUNCEONECHUNK 0 -// struct holding the globals of the loader (there are actually more but we don't need others) -typedef struct _loader_globals_t -{ - int sgIsLoadingBuffer; - int sgFileType; - int sgProcId; - int sgGotBytes; - int sgFileOffset; - int sgBufferNumber; - int sgBounceError; - char sgLoadName[0x1000]; -} __attribute__((packed)) loader_globals_t; - -/* Common useful functions */ -static inline int toupper(int c) { - return (c >= 'a' && c <= 'z') ? (c - 0x20) : c; -} - // Comment (Dimok): // we don't need to replace this function as i never seen it fail actually // it doesn't even fail when fileOffset is above the actually RPX/RPL fileSize @@ -119,6 +103,7 @@ static int LiWaitOneChunk(unsigned int * iRemainingBytes, const char *filename, result = LiWaitIopComplete(0x2160EC0, &remaining_bytes); } + // Comment (Dimok): // time measurement at this position for logger -> we don't need it right now except maybe for debugging //unsigned long long systemTime2 = Loader_GetSystemTime(); @@ -127,7 +112,7 @@ static int LiWaitOneChunk(unsigned int * iRemainingBytes, const char *filename, // Start of our function intrusion: // After IOSU is done writing the data into the 0xF6000000/0xF6400000 address, // we overwrite it with our data before setting the global flag for IsLoadingBuffer to 0 - // Do this only if we are in Smash Bros or Mii Maker and the game was launched by our method + // Do this only if we are in the game that was launched by our method if (*(volatile unsigned int*)0xEFE00000 == RPX_CHECK_NAME && (GAME_LAUNCHED == 1)) { s_rpx_rpl *rpl_struct = (s_rpx_rpl*)(RPX_RPL_ARRAY); @@ -149,13 +134,8 @@ static int LiWaitOneChunk(unsigned int * iRemainingBytes, const char *filename, if ((len != len2) && (len != (len2 - 4))) continue; - for (int x = 0; x < len; x++) - { - if (toupper(rpl_struct->name[x]) != toupper(filename[x])) - { - found = 0; - break; - } + if(strncasecmp(filename, rpl_struct->name, len) != 0) { + found = 0; } } @@ -205,7 +185,9 @@ static int LiWaitOneChunk(unsigned int * iRemainingBytes, const char *filename, { GAME_RPX_LOADED = 0; GAME_LAUNCHED = 0; + RPX_CHECK_NAME = 0xDEADBEAF; } + // end of our little intrusion into this function //------------------------------------------------------------------------------------------------------------------ @@ -234,17 +216,17 @@ static int LiWaitOneChunk(unsigned int * iRemainingBytes, const char *filename, } // this macro generates the the magic entry -#define MAKE_MAGIC(x, call_type) { x, addr_ ## x, call_type} +#define MAKE_MAGIC(x, call_type) { x, addr_ ## x, call_type } // A kind of magic used to store : // - replace instruction address // - replace instruction -struct magic_t { +const struct magic_t { const void * repl_func; // our replacement function which is called const void * repl_addr; // address where to place the jump to the our function const unsigned int call_type; // call type, e.g. 0x48000000 for branch and 0x48000001 for branch link -} const methods[] __attribute__((section(".magic"))) = { +} loader_methods[] __attribute__((section(".loader_magic"))) = { #if REPLACE_LIBOUNCEONECHUNK - MAKE_MAGIC(LiBounceOneChunk, 0x48000000), // simple branch to our function as we replace it completely and don't want LR to be replaced by bl + MAKE_MAGIC(LiBounceOneChunk, 0x48000002), // simple branch to our function as we replace it completely and don't want LR to be replaced by bl #endif - MAKE_MAGIC(LiWaitOneChunk, 0x48000000), // simple branch to our function as we replace it completely and don't want LR to be replaced by bl + MAKE_MAGIC(LiWaitOneChunk, 0x48000002), // simple branch to our function as we replace it completely and don't want LR to be replaced by bl }; diff --git a/loader/loader.h b/src/loader/loader.h old mode 100755 new mode 100644 similarity index 88% rename from loader/loader.h rename to src/loader/loader.h index 3a1f97b..4d4bb8f --- a/loader/loader.h +++ b/src/loader/loader.h @@ -8,9 +8,6 @@ extern int LiLoadAsync(const char *filename, unsigned int address, unsigned int extern int LiWaitIopComplete(int unknown_syscall_arg_r3, int * remaining_bytes); extern int LiWaitIopCompleteWithInterrupts(int unknown_syscall_arg_r3, int * remaining_bytes); -extern int strncpy(char *dst, const char *src, int max_len); -extern int strnlen(const char *src, int max_len); - /* This are just addresses to the real functions which we only need as reference */ extern int addr_LiBounceOneChunk(const char * filename, int fileType, int procId, int * hunkBytes, int fileOffset, int bufferNumber, int * dst_address); extern int addr_LiWaitOneChunk(unsigned int * iRemainingBytes, const char *filename, int fileType); diff --git a/menu/menu.c b/src/menu/menu.c old mode 100755 new mode 100644 similarity index 90% rename from menu/menu.c rename to src/menu/menu.c index 39199ce..0f1c07c --- a/menu/menu.c +++ b/src/menu/menu.c @@ -1,5 +1,7 @@ #include "menu.h" -#include "util.h" +#include "../utils/strings.h" +#include "../utils/utils.h" +#include "../utils/xml.h" #include "../common/common.h" /* Utils for the display */ @@ -7,16 +9,22 @@ #define PRINT_TEXT2(x, y, _fmt, ...) { __os_snprintf(msg, 80, _fmt, __VA_ARGS__); OSScreenPutFontEx(1, x, y, msg); } #define BTN_PRESSED (BUTTON_LEFT | BUTTON_RIGHT | BUTTON_UP | BUTTON_DOWN | BUTTON_A | BUTTON_B | BUTTON_X) +/* Function prototype to patch FS methods */ +void PatchFsMethods(void); + /* Static function definitions */ static int IsRPX(FSDirEntry *dir_entry); -static int Copy_RPX_RPL(FSClient *pClient, FSCmdBlock *pCmd, FSDirEntry *dir_entry, char *path, int path_index, int is_rpx, int entry_index); +static int Copy_RPX_RPL(FSClient *pClient, FSCmdBlock *pCmd, FSDirEntry *dir_entry, char *path, int is_rpx, int entry_index); static void CreateGameSaveDir(FSClient *pClient, FSCmdBlock *pCmd, const char *dir_entry, char* mount_path); static void GenerateMemoryAreasTable(); static void AddMemoryArea(int start, int end, int cur_index); static int game_name_cmp(const void *game1, const void *game2); +/* global variable for CosAppXml struct that is forced to bss section */ +ReducedCosAppXmlInfo cosAppXmlInfoStruct __attribute__((section(".bss"))); + /* Entry point */ -int _start(int argc, char *argv[]) { +int Menu_Main(int argc, char *argv[]) { /* ****************************************************************** */ /* MENU CHECK */ /* ****************************************************************** */ @@ -44,7 +52,7 @@ int _start(int argc, char *argv[]) { int(*SYSRelaunchTitle)(uint argc, char* argv) = 0; OSDynLoad_FindExport(sysapp_handle, 0, "SYSRelaunchTitle", &SYSRelaunchTitle); - + int(*SYSLaunchMenu)() = 0; OSDynLoad_FindExport(sysapp_handle, 0, "SYSLaunchMenu", &SYSLaunchMenu); @@ -66,6 +74,11 @@ int _start(int argc, char *argv[]) { for(j = 0; j < MAX_GAME_COUNT; j++) game_dir[j] = NULL; + /* ****************************************************************** */ + /* Patch FS Functions */ + /* ****************************************************************** */ + PatchFsMethods(); + /* ****************************************************************** */ /* Init Screen */ /* ****************************************************************** */ @@ -110,6 +123,8 @@ int _start(int argc, char *argv[]) { if (pClient && pCmd) { + // Do an FSInit first + FSInit(); // Add client to FS. FSAddClient(pClient, FS_RET_NO_ERROR); @@ -139,7 +154,7 @@ int _start(int argc, char *argv[]) { while (FSReadDir(pClient, pCmd, game_dh, &dir_entry, FS_RET_ALL_ERROR) == FS_STATUS_OK && game_count < MAX_GAME_COUNT) { // allocate string length + 0 termination bytes - int name_len = strlen(dir_entry.name); + int name_len = strnlen(dir_entry.name, 0x1000); game_dir[game_count] = (char *)malloc(name_len + 1); if(!game_dir[game_count]) continue; @@ -167,6 +182,9 @@ int _start(int argc, char *argv[]) { uint8_t ready = 0; int error; + //*(volatile unsigned int *)0xBD900000 = 0xDEADBEAF; + //*(volatile unsigned int *)0xBD900004 = 0xDEADBABE; + // sort game name pointers by name case insensitive qsort(game_dir, game_count, sizeof(char*), game_name_cmp); @@ -235,9 +253,7 @@ int _start(int argc, char *argv[]) { // Create game folder path char *cur_game_dir = game_dir[game_sel]; - int len = 0; - while (cur_game_dir[len]) - len++; + int len = strnlen(cur_game_dir, 0x1000); // initialize the RPL/RPX table first entry to zero + 1 byte for name zero termination // just in case no RPL/RPX are found, though it wont boot then anyway @@ -262,12 +278,12 @@ int _start(int argc, char *argv[]) { if (is_rpx) { - is_okay = Copy_RPX_RPL(pClient, pCmd, &dir_entry, path, path_index, 1, cur_entry++); + is_okay = Copy_RPX_RPL(pClient, pCmd, &dir_entry, path, 1, cur_entry++); CreateGameSaveDir(pClient, pCmd, game_dir[game_sel], mount_path); } else { - is_okay = Copy_RPX_RPL(pClient, pCmd, &dir_entry, path, path_index, 0, cur_entry++); + is_okay = Copy_RPX_RPL(pClient, pCmd, &dir_entry, path, 0, cur_entry++); } if (is_okay == 0) break; @@ -282,6 +298,10 @@ int _start(int argc, char *argv[]) { // Close dir FSCloseDir(pClient, pCmd, game_dh, FS_RET_NO_ERROR); + + if(ready){ + LoadXmlParameters(&cosAppXmlInfoStruct, pClient, pCmd, path, path_index); + } } } } @@ -379,7 +399,7 @@ static int IsRPX(FSDirEntry *dir_entry) return -1; } -static void Add_RPX_RPL_Entry(const char *name, int size, int is_rpx, int entry_index){ +static void Add_RPX_RPL_Entry(const char *name, int size, int is_rpx, int entry_index, s_mem_area* area){ // fill rpx/rpl entry s_rpx_rpl * rpx_rpl_data = (s_rpx_rpl *)(RPX_RPL_ARRAY); // get to last entry @@ -389,48 +409,38 @@ static void Add_RPX_RPL_Entry(const char *name, int size, int is_rpx, int entry_ // setup next entry on the previous one (only if it is not the first entry) if(entry_index > 0) { - rpx_rpl_data->next = (s_rpx_rpl *)( ((unsigned int)rpx_rpl_data) + sizeof(s_rpx_rpl) + strlen(rpx_rpl_data->name) + 1 ); + rpx_rpl_data->next = (s_rpx_rpl *)( ((unsigned int)rpx_rpl_data) + sizeof(s_rpx_rpl) + strnlen(rpx_rpl_data->name, 0x1000) + 1 ); rpx_rpl_data = rpx_rpl_data->next; } // setup current entry - rpx_rpl_data->area = (s_mem_area*)(MEM_AREA_ARRAY); + rpx_rpl_data->area = area; rpx_rpl_data->size = size; rpx_rpl_data->offset = 0; rpx_rpl_data->is_rpx = is_rpx; rpx_rpl_data->next = 0; // copy string length + 0 termination - memcpy(rpx_rpl_data->name, name, strlen(name) + 1); + memcpy(rpx_rpl_data->name, name, strnlen(name, 0x1000) + 1); } /* Copy_RPX_RPL */ -static int Copy_RPX_RPL(FSClient *pClient, FSCmdBlock *pCmd, FSDirEntry *dir_entry, char *path, int path_index, int is_rpx, int entry_index) +static int Copy_RPX_RPL(FSClient *pClient, FSCmdBlock *pCmd, FSDirEntry *dir_entry, char *path, int is_rpx, int entry_index) { // Open rpl file int fd = 0; - char buf_mode[3] = {'r', '\0' }; char* path_game = (char*)malloc(FS_MAX_MOUNTPATH_SIZE); if (!path_game) return 0; - // Copy path - memcpy(path_game, path, FS_MAX_MOUNTPATH_SIZE); - - // Get rpx/rpl filename length - int len = strlen(dir_entry->name); - // Concatenate rpl filename - path_game[path_index++] = '/'; - memcpy(&(path_game[path_index]), dir_entry->name, len); - path_index += len; - path_game[path_index++] = '\0'; + __os_snprintf(path_game, FS_MAX_MOUNTPATH_SIZE, "%s/%s", path, dir_entry->name); // For RPLs : if(!is_rpx) { // fill rpl entry - Add_RPX_RPL_Entry(dir_entry->name, 0, is_rpx, entry_index); + Add_RPX_RPL_Entry(dir_entry->name, 0, is_rpx, entry_index, (s_mem_area*)(MEM_AREA_ARRAY)); // free path free(path_game); @@ -438,7 +448,7 @@ static int Copy_RPX_RPL(FSClient *pClient, FSCmdBlock *pCmd, FSDirEntry *dir_ent } // For RPX : load file from sdcard and fill memory with the rpx data - if (FSOpenFile(pClient, pCmd, path_game, buf_mode, &fd, FS_RET_ALL_ERROR) == FS_STATUS_OK) + if (FSOpenFile(pClient, pCmd, path_game, "r", &fd, FS_RET_ALL_ERROR) == FS_STATUS_OK) { int cur_size = 0; int ret = 0; @@ -448,6 +458,7 @@ static int Copy_RPX_RPL(FSClient *pClient, FSCmdBlock *pCmd, FSDirEntry *dir_ent // Get current memory area limits s_mem_area* mem_area = (s_mem_area*)(MEM_AREA_ARRAY); + s_mem_area* mem_area_rpl = mem_area; int mem_area_addr_start = mem_area->address; int mem_area_addr_end = mem_area->address + mem_area->size; int mem_area_offset = 0; @@ -472,8 +483,12 @@ static int Copy_RPX_RPL(FSClient *pClient, FSCmdBlock *pCmd, FSDirEntry *dir_ent cur_size += ret; } + if(is_rpx) { + RPX_CHECK_NAME = *(unsigned int*)dir_entry->name; + } + // fill rpx entry - Add_RPX_RPL_Entry(dir_entry->name, cur_size, is_rpx, entry_index); + Add_RPX_RPL_Entry(dir_entry->name, cur_size, is_rpx, entry_index, mem_area_rpl); // close file and free memory FSCloseFile(pClient, pCmd, fd, FS_RET_NO_ERROR); @@ -644,7 +659,7 @@ static void GenerateMemoryAreasTable() {0xB8000000 + 0x01030800, 0xB8000000 + 0x013F69A0}, // 3864 kB {0xB8000000 + 0x008EEC30, 0xB8000000 + 0x00B06E98}, // 2144 kB {0xB8000000 + 0x053B966C, 0xB8000000 + 0x058943C4}, // 4971 kB - {0xB8000000 + 0x04ADE370, 0xB8000000 + 0x0520EAB8}, // 7361 kB + {0xB8000000 + 0x04ADE370, 0xB8000000 + 0x0520EAB8}, // 7361 kB {0, 0} }; // total : 66mB + 25mB @@ -679,5 +694,19 @@ static int game_name_cmp(const void *game1, const void *game2) const char *game_name_1 = *((const char **)game1); const char *game_name_2 = *((const char **)game2); // compare strings case insensitive - return strcasecmp(game_name_1, game_name_2); + return strncasecmp(game_name_1, game_name_2, FS_MAX_FULLPATH_SIZE); } + +// this macro generates the the magic entry +#define MAKE_MAGIC(x, y, call_type) { x, y, call_type } +// A kind of magic used to store : +// - replace instruction address +// - replace instruction +const struct magic_t { + const void * repl_func; // our replacement function which is called + const unsigned int repl_addr; // address where to place the jump to the our function + const unsigned int call_type; // call type, e.g. 0x48000000 for branch and 0x48000001 for branch link +} menu_methods[] __attribute__((section(".menu_magic"))) = { + /* Patch coreinit - on 5.3.2 coreinit.rpl starts at 0x101c400 */ + { Menu_Main, 0x0101c55c, 0x48000003 }, // bla Branch Link Adress +}; diff --git a/menu/menu.h b/src/menu/menu.h old mode 100755 new mode 100644 similarity index 78% rename from menu/menu.h rename to src/menu/menu.h index 536ade3..7a86320 --- a/menu/menu.h +++ b/src/menu/menu.h @@ -10,29 +10,15 @@ #define MAX_GAME_ON_PAGE 11 /* Main */ -extern int (* const entry_point)(int argc, char *argv[]); -#define main (*entry_point) +extern int (* const MiiMaker_main)(int argc, char *argv[]); +#define main (*MiiMaker_main) /* System */ extern void _Exit (void); extern void OSFatal(char* msg); extern void DCFlushRange(const void *addr, uint length); -extern void *memset(void *dst, int val, int bytes); -extern void *memcpy(void *dst, const void *src, int bytes); -extern int *__gh_errno_ptr(void); -#define errno (*__gh_errno_ptr()) -extern void GX2WaitForVsync(void); -extern int __os_snprintf(char* s, int n, const char * format, ...); -extern const long long title_id; - -/* Alloc */ -extern void *(* const MEMAllocFromDefaultHeapEx)(int size, int align); -extern void *(* const MEMAllocFromDefaultHeap)(int size); -extern void *(* const MEMFreeToDefaultHeap)(void *ptr); -#define memalign (*MEMAllocFromDefaultHeapEx) -#define malloc (*MEMAllocFromDefaultHeap) -#define free (*MEMFreeToDefaultHeap) +extern void GX2WaitForVsync(void); /* Libs */ extern int OSDynLoad_Acquire(char* rpl, uint *handle); @@ -50,6 +36,7 @@ extern uint OSScreenPutFontEx(uint bufferNum, uint posX, uint posY, const void * extern int VPADRead(int controller, VPADData *buffer, uint num, int *error); /* FS Functions */ +extern FSStatus FSInit(void); extern FSStatus FSAddClient(FSClient *pClient, FSRetFlag errHandling); extern void FSInitCmdBlock(FSCmdBlock *pCmd); extern FSStatus FSGetMountSource(FSClient *pClient, FSCmdBlock *pCmd, FSSourceType type, FSMountSource *source, FSRetFlag errHandling); diff --git a/fs/exception_handler.c b/src/utils/exception_handler.c similarity index 100% rename from fs/exception_handler.c rename to src/utils/exception_handler.c diff --git a/fs/exception_handler.h b/src/utils/exception_handler.h similarity index 100% rename from fs/exception_handler.h rename to src/utils/exception_handler.h diff --git a/src/utils/logger.c b/src/utils/logger.c new file mode 100644 index 0000000..6d1201b --- /dev/null +++ b/src/utils/logger.c @@ -0,0 +1,93 @@ +#include "../common/common.h" +#include "../fs/fs.h" +#include "logger.h" +#include "socket.h" + +#define CHECK_ERROR(cond) if (cond) { goto error; } + +int logger_connect(int *psock) { + struct sockaddr_in addr; + int sock, ret; + + // No ip means that we don't have any server running, so no logs + if (SERVER_IP == 0) { + *psock = -1; + return 0; + } + + socket_lib_init(); + + sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + CHECK_ERROR(sock == -1); + + addr.sin_family = AF_INET; + addr.sin_port = 7332; + addr.sin_addr.s_addr = SERVER_IP; + + ret = connect(sock, (void *)&addr, sizeof(addr)); + CHECK_ERROR(ret < 0); + + int enable = 1; + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&enable, sizeof(enable)); + + ret = recvbyte(sock); + CHECK_ERROR(ret < 0); + + *psock = sock; + return 0; + +error: + if (sock != -1) + socketclose(sock); + + *psock = -1; + return -1; +} + +void logger_disconnect(int sock) { + CHECK_ERROR(sock == -1); + + char byte = BYTE_DISCONNECT; + sendwait(sock, &byte, 1); + + socketclose(sock); +error: + return; +} + +void log_string(int sock, const char* str, char flag_byte) { + if(sock == -1) { + return; + } + while (bss.lock) GX2WaitForVsync(); + bss.lock = 1; + + int i; + int len_str = 0; + while (str[len_str++]); + + // + { + char buffer[1 + 4 + len_str]; + buffer[0] = flag_byte; + *(int *)(buffer + 1) = len_str; + for (i = 0; i < len_str; i++) + buffer[5 + i] = str[i]; + + buffer[5 + i] = 0; + + sendwait(sock, buffer, 1 + 4 + len_str); + } + + bss.lock = 0; +} + +void log_byte(int sock, char byte) { + while (bss.lock) GX2WaitForVsync(); + bss.lock = 1; + + if(sock != -1) { + sendwait(sock, &byte, 1); + } + bss.lock = 0; +} diff --git a/fs/fs.h b/src/utils/logger.h old mode 100755 new mode 100644 similarity index 51% rename from fs/fs.h rename to src/utils/logger.h index 672ad86..593ca97 --- a/fs/fs.h +++ b/src/utils/logger.h @@ -1,81 +1,5 @@ -#ifndef _FS_H_ -#define _FS_H_ - -#include "../common/fs_defs.h" - -/* mem functions */ -void *memcpy(void *dst, const void *src, int bytes); -void *memset(void *dst, int val, int bytes); -extern void *(* const MEMAllocFromDefaultHeapEx)(int size, int align); -extern void *(* const MEMAllocFromDefaultHeap)(int size); -extern void (* const MEMFreeToDefaultHeap)(void *p); -#define memalign (*MEMAllocFromDefaultHeapEx) - -/* socket.h */ -#define AF_INET 2 -#define SOCK_STREAM 1 -#define IPPROTO_TCP 6 - -extern void socket_lib_init(); -extern int socket(int domain, int type, int protocol); -extern int socketclose(int socket); -extern int connect(int socket, void *addr, int addrlen); -extern int send(int socket, const void *buffer, int size, int flags); -extern int recv(int socket, void *buffer, int size, int flags); - -extern void GX2WaitForVsync(void); - - -struct in_addr { - unsigned int s_addr; -}; -struct sockaddr_in { - short sin_family; - unsigned short sin_port; - struct in_addr sin_addr; - char sin_zero[8]; -}; - -/* OS stuff */ -extern const long long title_id; -extern void DCFlushRange(const void *p, unsigned int s); - -/* SDCard functions */ -extern FSStatus FSGetMountSource(void *pClient, void *pCmd, FSSourceType type, FSMountSource *source, FSRetFlag errHandling); -extern FSStatus FSMount(void *pClient, void *pCmd, FSMountSource *source, char *target, uint bytes, FSRetFlag errHandling); -extern FSStatus FSReadFile(FSClient *pClient, FSCmdBlock *pCmd, void *buffer, int size, int count, int fd, FSFlag flag, FSRetFlag errHandling); -extern void FSInitCmdBlock(FSCmdBlock *pCmd); -extern FSStatus FSCloseFile(FSClient *pClient, FSCmdBlock *pCmd, int fd, int error); - -/* Async callback definition */ -typedef void (*FSAsyncCallback)(void *pClient, void *pCmd, int result, void *context); -typedef struct -{ - FSAsyncCallback userCallback; - void *userContext; - void *ioMsgQueue; -} FSAsyncParams; - -/* Forward declarations */ -#define MAX_CLIENT 32 - -struct bss_t { - int global_sock; - int socket_fs[MAX_CLIENT]; - void *pClient_fs[MAX_CLIENT]; - volatile int lock; - char mount_base[255]; - char save_base[255]; -}; - -#define bss_ptr (*(struct bss_t **)0x100000e4) -#define bss (*bss_ptr) - -int fs_connect(int *socket); -void fs_disconnect(int socket); -int fs_mount_sd(int sock, void* pClient, void* pCmd); -void log_string(int sock, const char* str, char byte); -void log_byte(int sock, char byte); +#ifndef __LOGGER_H_ +#define __LOGGER_H_ /* Communication bytes with the server */ // Com @@ -141,4 +65,11 @@ void log_byte(int sock, char byte); #define BYTE_CREATE_THREAD 0x60 -#endif /* _FS_H */ + +int logger_connect(int *socket); +void logger_disconnect(int socket); +void log_string(int sock, const char* str, char byte); +void log_byte(int sock, char byte); + + +#endif diff --git a/src/utils/socket.c b/src/utils/socket.c new file mode 100644 index 0000000..1cd0bbb --- /dev/null +++ b/src/utils/socket.c @@ -0,0 +1,38 @@ +#include "socket.h" + +int recvwait(int sock, void *buffer, int len) { + int ret; + while (len > 0) { + ret = recv(sock, buffer, len, 0); + if(ret < 0) + return ret; + + len -= ret; + buffer += ret; + } + return len; +} + +int recvbyte(int sock) { + unsigned char buffer[1]; + int ret; + + ret = recvwait(sock, buffer, 1); + if (ret < 0) + return ret; + + return buffer[0]; +} + +int sendwait(int sock, const void *buffer, int len) { + int ret; + while (len > 0) { + ret = send(sock, buffer, len, 0); + if(ret < 0) + return ret; + + len -= ret; + buffer += ret; + } + return len; +} diff --git a/src/utils/socket.h b/src/utils/socket.h new file mode 100644 index 0000000..b6ef41c --- /dev/null +++ b/src/utils/socket.h @@ -0,0 +1,33 @@ +#ifndef __SOCKET_H_ +#define __SOCKET_H_ + +/* socket.h */ +#define AF_INET 2 +#define SOCK_STREAM 1 +#define IPPROTO_TCP 6 +#define TCP_NODELAY 0x2004 + +extern void socket_lib_init(); +extern int socket(int domain, int type, int protocol); +extern int socketclose(int socket); +extern int connect(int socket, void *addr, int addrlen); +extern int send(int socket, const void *buffer, int size, int flags); +extern int recv(int socket, void *buffer, int size, int flags); +extern int setsockopt(int fd, int level, int optname, void *optval, int optlen); + +struct in_addr { + unsigned int s_addr; +}; +struct sockaddr_in { + short sin_family; + unsigned short sin_port; + struct in_addr sin_addr; + char sin_zero[8]; +}; + +int recvwait(int sock, void *buffer, int len); +int recvbyte(int sock); +int sendwait(int sock, const void *buffer, int len); + + +#endif diff --git a/src/utils/strings.c b/src/utils/strings.c new file mode 100644 index 0000000..3aba907 --- /dev/null +++ b/src/utils/strings.c @@ -0,0 +1,169 @@ +#include "strings.h" + +void* memcpy(void *dst, const void *src, unsigned int len) +{ + const unsigned char *src_ptr = (const unsigned char *)src; + unsigned char *dst_ptr = (unsigned char *)dst; + + while(len) + { + *dst_ptr++ = *src_ptr++; + --len; + } + return dst; +} + +void* memset(void *dst, int val, unsigned int bytes) +{ + unsigned char *dst_ptr = (unsigned char *)dst; + unsigned int i = 0; + while(i < bytes) + { + dst_ptr[i] = val; + ++i; + } + return dst; +} + +int memcmp(const void * ptr1, const void * ptr2, unsigned int num) +{ + const unsigned char *ptr1_cpy = (const unsigned char *)ptr1; + const unsigned char *ptr2_cpy = (const unsigned char *)ptr2; + + while(num) + { + int diff = (int)*ptr1_cpy - (int)*ptr2_cpy; + if(diff != 0) { + return diff; + } + ptr1_cpy++; + ptr2_cpy++; + --num; + } + return 0; +} + +int strnlen(const char* str, unsigned int max_len) { + unsigned int i = 0; + while (str[i] && (i < max_len)) { + i++; + } + return i; +} + +int strlen(const char* str) { + unsigned int i = 0; + while (str[i]) { + i++; + } + return i; +} + +int strlcpy(char *s1, const char *s2, unsigned int max_size) +{ + if(!s1 || !s2 || !max_size) + return 0; + + unsigned int len = 0; + while(s2[len] && (len < (max_size-1))) + { + s1[len] = s2[len]; + len++; + } + s1[len] = 0; + return len; +} + +int strncpy(char *dst, const char *src, unsigned int max_size) +{ + return strlcpy(dst, src, max_size); // this is not correct, but mostly we need a terminating zero +} + + +int strncasecmp(const char *s1, const char *s2, unsigned int max_len) { + if(!s1 || !s2) { + return -1; + } + + unsigned int len = 0; + while(*s1 && *s2 && len < max_len) + { + int diff = toupper(*s1) - toupper(*s2); + if(diff != 0) { + return diff; + } + + s1++; + s2++; + len++; + } + + if(len == max_len) { + return 0; + } + + int diff = toupper(*s1) - toupper(*s2); + if(diff != 0) { + return diff; + } + return 0; +} + + +const char *strcasestr(const char *str, const char *pattern) +{ + if(!str || !pattern) { + return 0; + } + + int len = strnlen(pattern, 0x1000); + + while(*str) + { + if(strncasecmp(str, pattern, len) == 0) { + return str; + } + str++; + } + return 0; +} + +int64_t strtoll(const char *str, char **end, int base) +{ + int64_t value = 0; + int sign = 1; + + // skip initial spaces only + while(*str == ' ') + str++; + + if(*str == '-') { + sign = -1; + str++; + } + + while(*str) + { + if(base == 16 && toupper(*str) == 'X') { + str++; + continue; + } + + if(!(*str >= '0' && *str <= '9') && !(base == 16 && toupper(*str) >= 'A' && toupper(*str) <= 'F')) + break; + + value *= base; + + if(toupper(*str) >= 'A' && toupper(*str) <= 'F') + value += toupper(*str) - 'A' + 10; + else + value += *str - '0'; + + str++; + } + + if(end) + *end = (char*) str; + + return value * sign; +} diff --git a/src/utils/strings.h b/src/utils/strings.h new file mode 100644 index 0000000..de6f3f7 --- /dev/null +++ b/src/utils/strings.h @@ -0,0 +1,26 @@ +#ifndef __STRINGS_H_ +#define __STRINGS_H_ + +#include "../common/types.h" + +static inline int toupper(int c) { + return (c >= 'a' && c <= 'z') ? (c - 0x20) : c; +} + +void* memcpy(void *dst, const void *src, unsigned int len); +void* memset(void *dst, int val, unsigned int len); +int memcmp (const void * ptr1, const void * ptr2, unsigned int num); + +/* string functions */ +int strncasecmp(const char *s1, const char *s2, unsigned int max_len); +int strncpy(char *dst, const char *src, unsigned int max_size); +int strlcpy(char *s1, const char *s2, unsigned int max_size); +int strnlen(const char* str, unsigned int max_size); +int strlen(const char* str); +const char *strcasestr(const char *str, const char *pattern); +int64_t strtoll(const char *str, char **end, int base); + +/* functions from OS, don't put standard functions here without any prefix marking it as an OS function */ +extern int __os_snprintf(char* s, int n, const char * format, ...); + +#endif // __STRINGS_H_ diff --git a/menu/util.c b/src/utils/utils.c similarity index 60% rename from menu/util.c rename to src/utils/utils.c index faafb65..2d925e8 100644 --- a/menu/util.c +++ b/src/utils/utils.c @@ -1,54 +1,41 @@ -#include "util.h" - -static inline int toupper(int c) { - return (c >= 'a' && c <= 'z') ? (c - 0x20) : c; -} - -int strlen(const char* str) { - int i = 0; - while (str[i]) - i++; - return i; -} - -/* not used yet, maybe later -int strlcpy(char *s1, const char *s2, unsigned int max_size) -{ - if(!s1 || !s2 || !max_size) - return 0; - - int len = 0; - while(s2[len] && (len < (max_size-1))) - { - s1[len] = s2[len]; - len++; - } - s1[len] = 0; - return len; -} -*/ -int strcasecmp(const char *s1, const char *s2) { - if(!s1 || !s2) { - return -1; +#include "../common/common.h" +#include "../fs/fs.h" +#include "socket.h" +#include "logger.h" +#include "utils.h" + +int fs_mount_sd(int sock, void* pClient, void* pCmd) { + int is_mounted = 0; + char buffer[1]; + + if (sock != -1) { + buffer[0] = BYTE_MOUNT_SD; + sendwait(sock, buffer, 1); } - while(*s1 && *s2) + // mount sdcard + FSMountSource mountSrc; + char mountPath[FS_MAX_MOUNTPATH_SIZE]; + int status = FSGetMountSource(pClient, pCmd, FS_SOURCETYPE_EXTERNAL, &mountSrc, FS_RET_NO_ERROR); + if (status == FS_STATUS_OK) { - int diff = toupper(*s1) - toupper(*s2); - if(diff != 0) { - return diff; + status = FSMount(pClient, pCmd, &mountSrc, mountPath, sizeof(mountPath), FS_RET_UNSUPPORTED_CMD); + if (status == FS_STATUS_OK) + { + // set as mounted + is_mounted = 1; } - - s1++; - s2++; } - int diff = toupper(*s1) - toupper(*s2); - if(diff != 0) { - return diff; + + if (sock != -1) { + buffer[0] = is_mounted ? BYTE_MOUNT_SD_OK : BYTE_MOUNT_SD_BAD; + sendwait(sock, buffer, 1); } - return 0; + + return is_mounted; } + static void swap(void *e1, void *e2, unsigned int length) { unsigned char tmp; diff --git a/src/utils/utils.h b/src/utils/utils.h new file mode 100644 index 0000000..d0ba475 --- /dev/null +++ b/src/utils/utils.h @@ -0,0 +1,31 @@ +#ifndef __UTILS_H_ +#define __UTILS_H_ + +#include "../common/types.h" + +#define FlushBlock(addr) asm volatile("dcbf %0, %1\n" \ + "icbi %0, %1\n" \ + "sync\n" \ + "eieio\n" \ + "isync\n" \ + : \ + :"r"(0), "r"(((addr) & ~31)) \ + :"memory", "ctr", "lr", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12" \ + ); + + +/* Alloc */ +extern void *(* const MEMAllocFromDefaultHeapEx)(int size, int align); +extern void *(* const MEMAllocFromDefaultHeap)(int size); +extern void *(* const MEMFreeToDefaultHeap)(void *ptr); + +#define memalign (*MEMAllocFromDefaultHeapEx) +#define malloc (*MEMAllocFromDefaultHeap) +#define free (*MEMFreeToDefaultHeap) + +extern const uint64_t title_id; + +int fs_mount_sd(int sock, void* pClient, void* pCmd); +void qsort(void *ptr, unsigned int count, unsigned int size, int (*compare)(const void*,const void*)); + +#endif // __UTILS_H_ diff --git a/src/utils/xml.c b/src/utils/xml.c new file mode 100644 index 0000000..d06cd97 --- /dev/null +++ b/src/utils/xml.c @@ -0,0 +1,258 @@ +#include "../Common/fs_defs.h" +#include "../Common/kernel_defs.h" +#include "../menu/menu.h" +#include "utils.h" +#include "strings.h" +#include "logger.h" + +#define XML_BUFFER_SIZE 8192 + +char * XML_GetNodeText(const char *xml_part, const char * nodename, char * output, int output_size) +{ + // create '<' + nodename + char buffer[strlen(nodename) + 3]; + buffer[0] = '<'; + strlcpy(&buffer[1], nodename, sizeof(buffer)); + + const char *start = strcasestr(xml_part, buffer); + if(!start) + return 0; + + // find closing tag + while(*start && *start != '>') + start++; + // skip '>' + if(*start == '>') + start++; + + // create 'version_cos_xml = 18; // default for most games + xmlInfo->os_version = 0x000500101000400A; // default for most games + xmlInfo->title_id = title_id; // use mii maker ID + xmlInfo->app_type = 0x80000000; // default for most games + xmlInfo->cmdFlags = 0; // default for most games + strlcpy(xmlInfo->rpx_name, "ffl_app.rpx", sizeof(xmlInfo->rpx_name)); + xmlInfo->max_size = 0x40000000; // default for most games + xmlInfo->avail_size = 0; // default for most games + xmlInfo->codegen_size = 0; // default for most games + xmlInfo->codegen_core = 1; // default for most games + xmlInfo->max_codesize = 0x03000000; // i think this is the best for most games + xmlInfo->overlay_arena = 0; // only very few have that set to 1 + xmlInfo->exception_stack0_size = 0x1000; // default for most games + xmlInfo->exception_stack1_size = 0x1000; // default for most games + xmlInfo->exception_stack2_size = 0x1000; // default for most games + xmlInfo->sdk_version = 20909; // game dependent, lets take the one from mii maker + xmlInfo->title_version = 0; // game dependent, we say its 0 + //-------------------------------------------------------------------------------------------- + + int fd = 0; + char* path_copy = (char*)malloc(FS_MAX_MOUNTPATH_SIZE); + if (!path_copy) + return -1; + + char* xmlData = (char*)malloc(XML_BUFFER_SIZE); + if(!xmlData) { + free(path_copy); + return -2; + } + + char* xmlNodeData = (char*)malloc(XML_BUFFER_SIZE); + if(!xmlNodeData) { + free(xmlData); + free(path_copy); + return -3; + } + + memset(xmlData, 0, XML_BUFFER_SIZE); + + // create path + __os_snprintf(path_copy, FS_MAX_MOUNTPATH_SIZE, "%s/cos.xml", path); + + if (FSOpenFile(pClient, pCmd, path_copy, "r", &fd, FS_RET_ALL_ERROR) == FS_STATUS_OK) + { + // read first up to XML_BUFFER_SIZE available bytes and close file + int result = FSReadFile(pClient, pCmd, xmlData, 0x1, XML_BUFFER_SIZE, fd, 0, FS_RET_ALL_ERROR); + FSCloseFile(pClient, pCmd, fd, FS_RET_NO_ERROR); + // lets start parsing + if(result > 0) + { + // ensure 0 termination + xmlData[XML_BUFFER_SIZE-1] = 0; + + + if(XML_GetNodeText(xmlData, "version", xmlNodeData, XML_BUFFER_SIZE)) + { + unsigned int value = strtoll(xmlNodeData, 0, 10); + xmlInfo->version_cos_xml = value; + } + if(XML_GetNodeText(xmlData, "cmdFlags", xmlNodeData, XML_BUFFER_SIZE)) + { + unsigned int value = strtoll(xmlNodeData, 0, 10); + xmlInfo->cmdFlags = value; + } + if(XML_GetNodeText(xmlData, "argstr", xmlNodeData, XML_BUFFER_SIZE)) + { + strlcpy(xmlInfo->rpx_name, xmlNodeData, sizeof(xmlInfo->rpx_name)); + } + if(XML_GetNodeText(xmlData, "avail_size", xmlNodeData, XML_BUFFER_SIZE)) + { + unsigned int value = strtoll(xmlNodeData, 0, 16); + xmlInfo->avail_size = value; + } + if(XML_GetNodeText(xmlData, "codegen_size", xmlNodeData, XML_BUFFER_SIZE)) + { + unsigned int value = strtoll(xmlNodeData, 0, 16); + xmlInfo->codegen_size = value; + } + if(XML_GetNodeText(xmlData, "codegen_core", xmlNodeData, XML_BUFFER_SIZE)) + { + unsigned int value = strtoll(xmlNodeData, 0, 16); + xmlInfo->codegen_core = value; + } + if(XML_GetNodeText(xmlData, "max_size", xmlNodeData, XML_BUFFER_SIZE)) + { + unsigned int value = strtoll(xmlNodeData, 0, 16); + xmlInfo->max_size = value; + } + if(XML_GetNodeText(xmlData, "max_codesize", xmlNodeData, XML_BUFFER_SIZE)) + { + unsigned int value = strtoll(xmlNodeData, 0, 16); + xmlInfo->max_codesize = value; + } + if(XML_GetNodeText(xmlData, "overlay_arena", xmlNodeData, XML_BUFFER_SIZE)) + { + unsigned int value = strtoll(xmlNodeData, 0, 16); + xmlInfo->overlay_arena = value; + } + if(XML_GetNodeText(xmlData, "default_stack0_size", xmlNodeData, XML_BUFFER_SIZE)) + { + unsigned int value = strtoll(xmlNodeData, 0, 16); + xmlInfo->default_stack0_size = value; + } + if(XML_GetNodeText(xmlData, "default_stack1_size", xmlNodeData, XML_BUFFER_SIZE)) + { + unsigned int value = strtoll(xmlNodeData, 0, 16); + xmlInfo->default_stack1_size = value; + } + if(XML_GetNodeText(xmlData, "default_stack2_size", xmlNodeData, XML_BUFFER_SIZE)) + { + unsigned int value = strtoll(xmlNodeData, 0, 16); + xmlInfo->default_stack2_size = value; + } + if(XML_GetNodeText(xmlData, "default_redzone0_size", xmlNodeData, XML_BUFFER_SIZE)) + { + unsigned int value = strtoll(xmlNodeData, 0, 16); + xmlInfo->default_redzone0_size = value; + } + if(XML_GetNodeText(xmlData, "default_redzone1_size", xmlNodeData, XML_BUFFER_SIZE)) + { + unsigned int value = strtoll(xmlNodeData, 0, 16); + xmlInfo->default_redzone1_size = value; + } + if(XML_GetNodeText(xmlData, "default_redzone2_size", xmlNodeData, XML_BUFFER_SIZE)) + { + unsigned int value = strtoll(xmlNodeData, 0, 16); + xmlInfo->default_redzone2_size = value; + } + if(XML_GetNodeText(xmlData, "exception_stack0_size", xmlNodeData, XML_BUFFER_SIZE)) + { + unsigned int value = strtoll(xmlNodeData, 0, 16); + xmlInfo->exception_stack0_size = value; + } + if(XML_GetNodeText(xmlData, "exception_stack1_size", xmlNodeData, XML_BUFFER_SIZE)) + { + unsigned int value = strtoll(xmlNodeData, 0, 16); + xmlInfo->exception_stack0_size = value; + } + if(XML_GetNodeText(xmlData, "exception_stack2_size", xmlNodeData, XML_BUFFER_SIZE)) + { + unsigned int value = strtoll(xmlNodeData, 0, 16); + xmlInfo->exception_stack0_size = value; + } + } + } + + // reset buffer + memset(xmlData, 0, XML_BUFFER_SIZE); + + // create path + __os_snprintf(path_copy, FS_MAX_MOUNTPATH_SIZE, "%s/app.xml", path); + + if (FSOpenFile(pClient, pCmd, path_copy, "r", &fd, FS_RET_ALL_ERROR) == FS_STATUS_OK) + { + // read first up to XML_BUFFER_SIZE available bytes and close file + int result = FSReadFile(pClient, pCmd, xmlData, 0x1, XML_BUFFER_SIZE, fd, 0, FS_RET_ALL_ERROR); + FSCloseFile(pClient, pCmd, fd, FS_RET_NO_ERROR); + // lets start parsing + if(result > 0) + { + // ensure 0 termination + xmlData[XML_BUFFER_SIZE-1] = 0; + + //-------------------------------------------------------------------------------------------- + // version tag is still unknown where it is used + //-------------------------------------------------------------------------------------------- + if(XML_GetNodeText(xmlData, "os_version", xmlNodeData, XML_BUFFER_SIZE)) + { + uint64_t value = strtoll(xmlNodeData, 0, 16); + xmlInfo->os_version = value; + } + if(XML_GetNodeText(xmlData, "title_id", xmlNodeData, XML_BUFFER_SIZE)) + { + uint64_t value = strtoll(xmlNodeData, 0, 16); + xmlInfo->title_id = value; + + } + if(XML_GetNodeText(xmlData, "title_version", xmlNodeData, XML_BUFFER_SIZE)) + { + uint32_t value = strtoll(xmlNodeData, 0, 16); + xmlInfo->title_version = value; + } + if(XML_GetNodeText(xmlData, "sdk_version", xmlNodeData, XML_BUFFER_SIZE)) + { + uint32_t value = strtoll(xmlNodeData, 0, 10); + xmlInfo->sdk_version = value; + } + if(XML_GetNodeText(xmlData, "app_type", xmlNodeData, XML_BUFFER_SIZE)) + { + uint32_t value = strtoll(xmlNodeData, 0, 16); + xmlInfo->app_type = value; + } + //-------------------------------------------------------------------------------------------- + // group_id tag is still unknown where it is used + //-------------------------------------------------------------------------------------------- + } + } + + free(xmlData); + free(xmlNodeData); + free(path_copy); + + return 0; +} diff --git a/src/utils/xml.h b/src/utils/xml.h new file mode 100644 index 0000000..ae61620 --- /dev/null +++ b/src/utils/xml.h @@ -0,0 +1,9 @@ +#ifndef __XML_H_ +#define __XML_H_ + +#include "../common/kernel_defs.h" + +char * XML_GetNodeText(const char *xml_part, const char * nodename, char * output, int output_size); +int LoadXmlParameters(ReducedCosAppXmlInfo * xmlInfo, FSClient *pClient, FSCmdBlock *pCmd, const char *path, int path_index); + +#endif // __XML_H_ diff --git a/www/fs532.elf b/www/fs532.elf deleted file mode 100755 index 57daa025a4dc868527da73f185a9aba7ca41a7b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77572 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

;p~IavBZ|p`7rjeS`j}7 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