diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..99bac16 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*~ +*.3ds +*.cia +*.smdh +*.3dsx +*.elf +*.bat +*.layout +Thumbs.db +build/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d4935eb --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +The MIT License (MIT) + +Copyright (c) 2016 Plailect + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c1ed45c --- /dev/null +++ b/Makefile @@ -0,0 +1,232 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITARM)/3ds_rules + +#--------------------------------------------------------------------------------- +# 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 +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# +# NO_SMDH: if set to anything, no SMDH file is generated. +# ROMFS is the directory which contains the RomFS, relative to the Makefile (Optional) +# APP_TITLE is the name of the app stored in the SMDH file (Optional) +# APP_DESCRIPTION is the description of the app stored in the SMDH file (Optional) +# APP_AUTHOR is the author of the app stored in the SMDH file (Optional) +# ICON is the filename of the icon (.png), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .png +# - icon.png +# - /default_icon.png +#--------------------------------------------------------------------------------- +TARGET := nimSM +BUILD := build +SOURCES := source +DATA := data +INCLUDES := include +APP_AUTHOR := Plailect +APP_DESCRIPTION := Uses PxiFS0 to manage the NIM savegame. Requires reboot after running! +ICON := app/icon48x48.png +BNR_IMAGE := app/banner.png +BNR_AUDIO := app/BannerAudio.bcwav +RSF_FILE := app/build-cia.rsf + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft + +CFLAGS := -g -Wall -O2 -mword-relocations \ + -ffunction-sections \ + $(ARCH) + +CFLAGS += $(INCLUDE) -DARM11 -D_3DS + +CXXFLAGS := $(CFLAGS) -fno-rtti -std=gnu++11 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lctru -lm + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(CTRULIB) + + +#--------------------------------------------------------------------------------- +# 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 OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +PICAFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.v.pica))) +SHLISTFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.shlist))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(PICAFILES:.v.pica=.shbin.o) $(SHLISTFILES:.shlist=.shbin.o) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.png) + ifneq (,$(findstring $(TARGET).png,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).png + else + ifneq (,$(findstring icon.png,$(icons))) + export APP_ICON := $(TOPDIR)/icon.png + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_SMDH)),) + export _3DSXFLAGS += --smdh=$(CURDIR)/$(TARGET).smdh +endif + +ifneq ($(ROMFS),) + export _3DSXFLAGS += --romfs=$(CURDIR)/$(ROMFS) +endif + +.PHONY: $(BUILD) clean all cia + +#--------------------------------------------------------------------------------- +all: $(BUILD) + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).3dsx $(TARGET).smdh $(TARGET).cia $(TARGET).elf + + +#--------------------------------------------------------------------------------- +cia: $(BUILD) + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile cia + +#--------------------------------------------------------------------------------- +3dsx: $(BUILD) + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile 3dsx + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all: $(OUTPUT).cia $(OUTPUT).3dsx + +3dsx: $(OUTPUT).3dsx + +cia: $(OUTPUT).cia + +ifeq ($(strip $(NO_SMDH)),) +$(OUTPUT).3dsx : $(OUTPUT).elf $(OUTPUT).smdh +$(OUTPUT).smdh : $(TOPDIR)/Makefile +else +$(OUTPUT).3dsx : $(OUTPUT).elf +endif + +$(OUTPUT).elf : $(OFILES) + +$(OUTPUT).smdh : $(APP_ICON) + @bannertool makesmdh -s "$(APP_TITLE)" -l "$(APP_DESCRIPTION)" -p "$(APP_AUTHOR)" -i $(APP_ICON) -o $@ + @echo "built ... $(notdir $@)" + +$(OUTPUT).cia : $(OUTPUT).elf $(OUTPUT).smdh $(TARGET).bnr + @makerom -f cia -target t -exefslogo -o $@ \ + -elf $(OUTPUT).elf -rsf $(TOPDIR)/$(RSF_FILE) \ + -banner $(TARGET).bnr \ + -icon $(OUTPUT).smdh + @echo "built ... $(notdir $@)" + +$(TARGET).bnr : $(TOPDIR)/$(BNR_IMAGE) $(TOPDIR)/$(BNR_AUDIO) + @bannertool makebanner -o $@ -i $(TOPDIR)/$(BNR_IMAGE) -ca $(TOPDIR)/$(BNR_AUDIO) + @echo "built ... $@" + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +#--------------------------------------------------------------------------------- +# rules for assembling GPU shaders +#--------------------------------------------------------------------------------- +define shader-as + $(eval CURBIN := $(patsubst %.shbin.o,%.shbin,$(notdir $@))) + picasso -o $(CURBIN) $1 + bin2s $(CURBIN) | $(AS) -o $@ + echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(CURBIN) | tr . _)`.h + echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(CURBIN) | tr . _)`.h + echo "extern const u32" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(CURBIN) | tr . _)`.h +endef + +%.shbin.o : %.v.pica %.g.pica + @echo $(notdir $^) + @$(call shader-as,$^) + +%.shbin.o : %.v.pica + @echo $(notdir $<) + @$(call shader-as,$<) + +%.shbin.o : %.shlist + @echo $(notdir $<) + @$(call shader-as,$(foreach file,$(shell cat $<),$(dir $<)/$(file))) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/README.md b/README.md new file mode 100644 index 0000000..9e52e50 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# nimSM + +This is a nim save manager that uses PxiFS0 to backup, restore, or zero out the nim savegame. Requires arm11 kernel privileges, which it currently gets with svchax. + +This can be installed as a CIA, but if one can install CIAs, an equally valid method would be to just set the required exheader bitmasls to access the savegame with FS:USER. + +This app's intended use is as a `.3dsx` running under \*hax with arm11 kernel privileges. + +Backups of the nim savegame are saved to `sdmc://nimsavegame.bin` + +Zeroing out or deleting the savegame will cause it to be recreated by nim. Doing this before a downgrade will fix the "[soft brick](https://github.com/Plailect/sysDowngrader/issues/1)" that occurs for some devices. + +In its current form, it cannot delete the file because nim keeps an active handle to its savegame at all times, but it can zero it out which works fine for our purposes (triggering a regeneration). + +Note that because fs must be terminated to get a PxiFS0 handle, this app requires a reboot after running! + +## Building + +Run `make`; requires ctrulib, makerom, and bannertool. + +## Credits + ++ Plailect ++ Neobrain diff --git a/app/BannerAudio.bcwav b/app/BannerAudio.bcwav new file mode 100644 index 0000000..fd185d2 Binary files /dev/null and b/app/BannerAudio.bcwav differ diff --git a/app/banner.png b/app/banner.png new file mode 100644 index 0000000..cbea2a2 Binary files /dev/null and b/app/banner.png differ diff --git a/app/build-cia.rsf b/app/build-cia.rsf new file mode 100644 index 0000000..8e21472 --- /dev/null +++ b/app/build-cia.rsf @@ -0,0 +1,137 @@ +BasicInfo: + Title : "udtr" + CompanyCode : "00" + ProductCode : "CTR-N-UDTR" + ContentType : Application # Application / SystemUpdate / Manual / Child / Trial + Logo : Nintendo # Nintendo / Licensed / Distributed / iQue / iQueForSystem + +RomFs: + # Specifies the root path of the file system to include in the ROM. + # HostRoot : "$(ROMFS_ROOT)" + + +TitleInfo: + UniqueId : 0x7f000 + Category : Application # Application / SystemApplication / Applet / Firmware / Base / DlpChild / Demo / Contents / SystemContents / SharedContents / AddOnContents / Patch / AutoUpdateContents + +CardInfo: + MediaSize : 128MB # 128MB / 256MB / 512MB / 1GB / 2GB / 4GB / 8GB / 16GB / 32GB + MediaType : Card1 # Card1 / Card2 + CardDevice : None # NorFlash / None + + +Option: + UseOnSD : true # true if App is to be installed to SD + EnableCompress : true # Compresses exefs code + FreeProductCode : false # Removes limitations on ProductCode + EnableCrypt : false # Enables encryption for NCCH and CIA + MediaFootPadding : false # If true CCI files are created with padding + +AccessControlInfo: + FileSystemAccess: + - CategorySystemApplication + - CategoryFileSystemTool + - DirectSdmc + IoAccessControl: + + IdealProcessor : 0 + AffinityMask : 1 + + Priority : 16 + + MaxCpu : 0x9E # Default + + DisableDebug : true + EnableForceDebug : false + CanWriteSharedPage : false + CanUsePrivilegedPriority : false + CanUseNonAlphabetAndNumber : false + PermitMainFunctionArgument : false + CanShareDeviceMemory : false + RunnableOnSleep : false + SpecialMemoryArrange : false + + CoreVersion : 2 + DescVersion : 2 + + ReleaseKernelMajor : "02" + ReleaseKernelMinor : "33" + MemoryType : Application # Application / System / Base + HandleTableSize: 512 + IORegisterMapping: + - 1ff50000-1ff57fff + - 1ff70000-1ff77fff + MemoryMapping: + - 1f000000-1f5fffff:r + SystemCallAccess: + ArbitrateAddress: 34 + Break: 60 + CancelTimer: 28 + ClearEvent: 25 + ClearTimer: 29 + CloseHandle: 35 + ConnectToPort: 45 + ControlMemory: 1 + CreateAddressArbiter: 33 + CreateEvent: 23 + CreateMemoryBlock: 30 + CreateMutex: 19 + CreateSemaphore: 21 + CreateThread: 8 + CreateTimer: 26 + DuplicateHandle: 39 + ExitProcess: 3 + ExitThread: 9 + GetCurrentProcessorNumber: 17 + GetHandleInfo: 41 + GetProcessId: 53 + GetProcessIdOfThread: 54 + GetProcessIdealProcessor: 6 + GetProcessInfo: 43 + GetResourceLimit: 56 + GetResourceLimitCurrentValues: 58 + GetResourceLimitLimitValues: 57 + GetSystemInfo: 42 + GetSystemTick: 40 + GetThreadContext: 59 + GetThreadId: 55 + GetThreadIdealProcessor: 15 + GetThreadInfo: 44 + GetThreadPriority: 11 + MapMemoryBlock: 31 + OutputDebugString: 61 + QueryMemory: 2 + ReleaseMutex: 20 + ReleaseSemaphore: 22 + SendSyncRequest1: 46 + SendSyncRequest2: 47 + SendSyncRequest3: 48 + SendSyncRequest4: 49 + SendSyncRequest: 50 + SetThreadPriority: 12 + SetTimer: 27 + SignalEvent: 24 + SleepThread: 10 + UnmapMemoryBlock: 32 + WaitSynchronization1: 36 + WaitSynchronizationN: 37 + InterruptNumbers: + ServiceAccessControl: + - APT:U + - cfg:u + - fs:USER + - gsp::Gpu + - hid:USER + - am:u + + +SystemControlInfo: + SaveDataSize: 0KB + RemasterVersion: 0 + StackSize: 0x1000 + # JumpId: 0 + Dependency: + am: 0x0004013000001502L + cfg: 0x0004013000001702L + hid: 0x0004013000001d02L + ps: 0x0004013000003102L diff --git a/app/icon48x48.png b/app/icon48x48.png new file mode 100644 index 0000000..552b24e Binary files /dev/null and b/app/icon48x48.png differ diff --git a/include/svchax.h b/include/svchax.h new file mode 100644 index 0000000..c52d8ff --- /dev/null +++ b/include/svchax.h @@ -0,0 +1,47 @@ +#ifndef __SVCHAX_H__ +#define _SVCHAX_H__ + +/* + * for 3DSX builds, svchax_init expects that: + * + * - gfxInit was already called. + * - new 3DS higher clockrate and L2 cache are disabled. + * - there is at least 64 KBytes (16 pages) of unallocated linear memory. + * ( the current 3dsx loaders and ctrulib's default allocator will keep 1MB + * of unallocated linear memory, so this is only relevant when using + * a custom allocator) + * + * + * svchax_init will grant full svc access to the calling thread and process + * up to system version 10.7 (kernel version 2.50-11), by using: + * - memchunkhax1 for kernel version <= 2.46-0 + * - memchunkhax2 for 2.46-0 < kernel version <= 2.50-11 + * + * access to privileged services can also be obtained by calling + * svchax_init with patch_srv set to true. + * + * __ctr_svchax and __ctr_svchax_srv will reflect the current + * status of the privileged access for svc calls and services respectively. + * + * svchax assumes that CIA builds already have access to svcBackdoor + * and will skip running memchunkhax there. + * + */ + +#include <3ds/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +Result svchax_init(bool patch_srv); + +extern u32 __ctr_svchax; +extern u32 __ctr_svchax_srv; + +#ifdef __cplusplus +} +#endif + + +#endif //_SVCHAX_H__ diff --git a/source/main.c b/source/main.c new file mode 100644 index 0000000..7d93e32 --- /dev/null +++ b/source/main.c @@ -0,0 +1,836 @@ +#include <3ds.h> +#include +#include +#include +#include "svchax.h" + +void finished() { + printf("\nPress (Start) to reboot...\n"); + while (aptMainLoop()) + { + gspWaitForVBlank(); + hidScanInput(); + + u32 kDown = hidKeysDown(); + if (kDown & KEY_START) break; + } + consoleClear(); + gfxExit(); + svcKernelSetState(7); // after killing fs, APT_HardwareResetAsync() will errdisp so we use svcKernelSetState(7) instead +} + +void manageSave(bool restore){ + Result res = 0; + printf("Terminating the fs module...\n"); + Handle fshandle; + + res = svcOpenProcess(&fshandle, 0); // fs is always pid 0 + if(R_FAILED(res)){ + printf("\x1b[31mFailed to open fs process\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = svcTerminateProcess(fshandle); // ns:s cannot terminate firm modules + if(R_FAILED(res)){ + printf("\x1b[31mFailed to terminate fs process\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = svcCloseHandle(fshandle); + if(R_FAILED(res)){ + printf("\x1b[31mFailed to close fs handle\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + svcSleepThread(100000000LL); + + printf("Waiting for fs to terminate...\n"); + svcSleepThread(100000000LL); + while(1){ + res = svcOpenProcess(&fshandle, 0); // fs is always pid 0 + if(R_FAILED(res)){ + printf("Terminated fs!\n\n"); + break; + } + res = svcCloseHandle(fshandle); + if(R_FAILED(res)){ + printf("\x1b[31mFailed to close fs handle\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + svcSleepThread(100000000LL); + } + + u32 sdmcFileHandleLower; + u32 sdmcFileHandleUpper; + u32 sysdataFileHandleLower; + u32 sysdataFileHandleUpper; + + u32 sdmcArchiveHandleLower; + u32 sdmcArchiveHandleUpper; + u32 sysdataArchiveHandleLower; + u32 sysdataArchiveHandleUpper; + + const char sysdata_archive_path[] = { 0, 0, 0, 0 }; //sysdata save archive + + const char sdmc_file_path[] = "/nimsavegame.bin"; + const char sysdata_file_path[] = { 0x2c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }; // `0001002C/00000000/`, nim savegame file; binary path, reversed byte order because endians + //const char sysdata_file_path[] = { 0x11, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }; // `00010011/00000000/`, fs savegame file; binary path, reversed byte order because endians + + u64 sdmcFileSize; + u64 sdmcFileOffset; + u8* sdmcFileBuffer; + + u64 sysdataFileSize; + u64 sysdataFileOffset; + u8* sysdataFileBuffer; + + printf("Getting PxiFS0 handle...\n\n"); + Handle pxifs0handle; + + res = srvGetServiceHandle(&pxifs0handle, "PxiFS0"); + if(R_FAILED(res)){ + printf("\x1b[31mFailed to get PxiFS0 handle\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + // open sysdata archive with fspxi + printf("Opening sysdata archive...\n"); + + u32 *cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0x12,3,2); // 0x1200C2, FSPXI:OpenArchive according to 3dbrew + cmdbuf[1] = ARCHIVE_SYSTEM_SAVEDATA2; + cmdbuf[2] = PATH_BINARY; + cmdbuf[3] = sizeof(sysdata_archive_path); + cmdbuf[4] = IPC_Desc_PXIBuffer(sizeof(sysdata_archive_path), 0, 1); + cmdbuf[5] = (uintptr_t) sysdata_archive_path; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mFailed to open sysdata archive\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + sysdataArchiveHandleLower = cmdbuf[2]; + sysdataArchiveHandleUpper = cmdbuf[3]; + + cmdbuf = getThreadCommandBuffer(); + + printf("Opening sdmc archive...\n\n"); + + cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0x12,3,2); // 0x1200C2, FSPXI:OpenArchive according to 3dbrew + cmdbuf[1] = ARCHIVE_SDMC; + cmdbuf[2] = PATH_EMPTY; + cmdbuf[3] = 1; + cmdbuf[4] = IPC_Desc_PXIBuffer(1, 0, 1); + cmdbuf[5] = (uintptr_t) ""; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mFailed to open sdmc archive\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + sdmcArchiveHandleLower = cmdbuf[2]; + sdmcArchiveHandleUpper = cmdbuf[3]; + + cmdbuf = getThreadCommandBuffer(); + + if(restore){ + printf("Restoring nim savegame...\n\n"); + printf("From: nand://data//sysdata/0001002c/00000000\n"); + printf("To: smdc://nimsavegame.bin\n\n"); + + printf("Opening nim savegame backup...\n"); + + cmdbuf[0] = IPC_MakeHeader(0x1,7,2); // 0x101C2, FSPXI:OpenFile according to 3dbrew + cmdbuf[1] = 0; + cmdbuf[2] = sdmcArchiveHandleLower; + cmdbuf[3] = sdmcArchiveHandleUpper; + cmdbuf[4] = PATH_ASCII; + cmdbuf[5] = strlen(sdmc_file_path)+1; + cmdbuf[6] = 0x01; // read + cmdbuf[7] = 0; + cmdbuf[8] = IPC_Desc_PXIBuffer(strlen(sdmc_file_path)+1, 0, 1); + cmdbuf[9] = (uintptr_t) sdmc_file_path; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mCould not open nim savegame backup\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + sdmcFileHandleLower = cmdbuf[2]; + sdmcFileHandleUpper = cmdbuf[3]; + + cmdbuf = getThreadCommandBuffer(); + + printf("Opening nim savegame...\n"); + + // nim creates this file if it does not exist + // we should never have to create it ourselves + + cmdbuf[0] = IPC_MakeHeader(0x1,7,2); // 0x101C2, FSPXI:OpenFile according to 3dbrew + cmdbuf[1] = 0; + cmdbuf[2] = sysdataArchiveHandleLower; + cmdbuf[3] = sysdataArchiveHandleUpper; + cmdbuf[4] = PATH_BINARY; + cmdbuf[5] = sizeof(sysdata_file_path); + cmdbuf[6] = 0x02; // write + cmdbuf[7] = 0; + cmdbuf[8] = IPC_Desc_PXIBuffer(sizeof(sysdata_file_path), 0, 1); + cmdbuf[9] = (uintptr_t) sysdata_file_path; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mCould not open nim savegame\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + sysdataFileHandleLower = cmdbuf[2]; + sysdataFileHandleUpper = cmdbuf[3]; + + cmdbuf = getThreadCommandBuffer(); + + printf("Getting nim savegame backup size...\n"); + + cmdbuf[0] = IPC_MakeHeader(0xD,2,0); // 0xD0080, FSPXI:GetFileSize according to 3dbrew + cmdbuf[1] = sdmcFileHandleLower; + cmdbuf[2] = sdmcFileHandleUpper; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mCould not get nim savegame backup size\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + sdmcFileSize = cmdbuf[2] | ((u64) cmdbuf[3] << 32); + + cmdbuf = getThreadCommandBuffer(); + + printf("Reading nim savegame backup to buffer...\n\n"); + + sdmcFileOffset = 0; + + sdmcFileBuffer = (u8 *) malloc(sdmcFileSize); + memset(sdmcFileBuffer, 0, sdmcFileSize); + + cmdbuf[0] = IPC_MakeHeader(0x9,5,2); // 0x90142, FSPXI:ReadFile according to 3dbrew + cmdbuf[1] = sdmcFileHandleLower; + cmdbuf[2] = sdmcFileHandleUpper; + cmdbuf[3] = (u32) sdmcFileOffset; + cmdbuf[4] = (u32) (sdmcFileOffset >> 32); + cmdbuf[5] = sdmcFileSize; + cmdbuf[6] = IPC_Desc_PXIBuffer(sdmcFileSize, 0, 0); + cmdbuf[7] = (uintptr_t) sdmcFileBuffer; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mCould not read nim savegame backup to buffer\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + cmdbuf = getThreadCommandBuffer(); + + printf("Writing nim savegame backup to nand...\n"); + + cmdbuf[0] = IPC_MakeHeader(0xB,6,2); // 0xB0182, FSPXI:WriteFile according to 3dbrew + cmdbuf[1] = sysdataFileHandleLower; + cmdbuf[2] = sysdataFileHandleUpper; + cmdbuf[3] = (u32) sdmcFileOffset; + cmdbuf[4] = (u32) (sdmcFileOffset >> 32); + cmdbuf[5] = 0; // FLUSH_FLAGS + cmdbuf[6] = sdmcFileSize; + cmdbuf[7] = IPC_Desc_PXIBuffer(sdmcFileSize, 0, 1); + cmdbuf[8] = (uintptr_t) sdmcFileBuffer; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mCould not write nim savegame backup to nand\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + if(cmdbuf[2] != sdmcFileSize){ + printf("\x1b[31mSize written does not match nim savegame backup size\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + free(sdmcFileBuffer); + + cmdbuf = getThreadCommandBuffer(); + + printf("Closing nim savegame...\n"); + + cmdbuf[0] = IPC_MakeHeader(0xF,2,0); // 0xF0080, FSPXI:CloseFile according to 3dbrew + cmdbuf[1] = sysdataFileHandleLower; + cmdbuf[2] = sysdataFileHandleUpper; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mCould not close nim savegame\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + cmdbuf = getThreadCommandBuffer(); + + printf("Closing nim savegame backup...\n"); + + cmdbuf[0] = IPC_MakeHeader(0xF,2,0); // 0xF0080, FSPXI:CloseFile according to 3dbrew + cmdbuf[1] = sdmcFileHandleLower; + cmdbuf[2] = sdmcFileHandleUpper; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mCould not close nim savegame backup\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + cmdbuf = getThreadCommandBuffer(); + + printf("Closing sysdata archive...\n\n"); + + cmdbuf[0] = IPC_MakeHeader(0x16,2,0); // 0x160080, FSPXI:CloseArchive according to 3dbrew + cmdbuf[1] = sysdataArchiveHandleLower; + cmdbuf[2] = sysdataArchiveHandleUpper; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mFailed to close sysdata archive\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + printf("Closing sdmc archive...\n\n"); + + cmdbuf[0] = IPC_MakeHeader(0x16,2,0); // 0x160080, FSPXI:OpenArchive according to 3dbrew + cmdbuf[1] = sdmcArchiveHandleLower; + cmdbuf[2] = sdmcArchiveHandleUpper; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mFailed to close sdmc archive\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + printf("\x1b[32mSuccessfully restored nim savegame.\x1b[0m\n"); + + } else { + + printf("Backing up nim savegame...\n\n"); + printf("From: nand://data//sysdata/0001002c/00000000\n"); + printf("To: smdc://nimsavegame.bin\n\n"); + + cmdbuf = getThreadCommandBuffer(); + + printf("Opening nim savegame...\n"); + + cmdbuf[0] = IPC_MakeHeader(0x1,7,2); // 0x101C2, FSPXI:OpenFile according to 3dbrew + cmdbuf[1] = 0; + cmdbuf[2] = sysdataArchiveHandleLower; + cmdbuf[3] = sysdataArchiveHandleUpper; + cmdbuf[4] = PATH_BINARY; + cmdbuf[5] = sizeof(sysdata_file_path); + cmdbuf[6] = 0x03;//0x01; // read + cmdbuf[7] = 0; + cmdbuf[8] = IPC_Desc_PXIBuffer(sizeof(sysdata_file_path), 0, 1); + cmdbuf[9] = (uintptr_t) sysdata_file_path; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mCould not open nim savegame\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + sysdataFileHandleLower = cmdbuf[2]; + sysdataFileHandleUpper = cmdbuf[3]; + + cmdbuf = getThreadCommandBuffer(); + + printf("Getting nim savegame size...\n"); + + cmdbuf[0] = IPC_MakeHeader(0xD,2,0); // 0xD0080, FSPXI:GetFileSize according to 3dbrew + cmdbuf[1] = sysdataFileHandleLower; + cmdbuf[2] = sysdataFileHandleUpper; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mCould not get nim savegame size\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + sysdataFileSize = cmdbuf[2] | ((u64) cmdbuf[3] << 32); + + printf("Creating nim savegame backup...\n"); + + cmdbuf[0] = IPC_MakeHeader(0x5,8,2); // 0x50202, FSPXI:CreateFile according to 3dbrew + cmdbuf[1] = 0; + cmdbuf[2] = sdmcArchiveHandleLower; + cmdbuf[3] = sdmcArchiveHandleUpper; + cmdbuf[4] = PATH_ASCII; + cmdbuf[5] = strlen(sdmc_file_path)+1; + cmdbuf[6] = 0; + cmdbuf[7] = (u32) sysdataFileSize; + cmdbuf[8] = (u32) (sysdataFileSize >> 32); + cmdbuf[9] = IPC_Desc_PXIBuffer(strlen(sdmc_file_path)+1, 0, 1); + cmdbuf[10] = (uintptr_t) sdmc_file_path; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + + if(res == 0xC82044BE){ + printf("\n\x1b[31mFound existing nim savegame backup.\x1b[0m\n"); + + printf("Delete? (A) Yes (B) No\n\n"); + while (aptMainLoop()){ + + hidScanInput(); + u32 kDown = hidKeysDown(); + if (kDown & KEY_A) { + cmdbuf = getThreadCommandBuffer(); + + // delete nim save with fspxi + printf("Deleting existing nim savegame backup...\n\n"); + + cmdbuf[0] = IPC_MakeHeader(0x2,5,2); // 0x20142, delete file according to 3dbrew; confirmed correct by neobrain + cmdbuf[1] = 0; + cmdbuf[2] = sdmcArchiveHandleLower; + cmdbuf[3] = sdmcArchiveHandleUpper; + cmdbuf[4] = PATH_ASCII; + cmdbuf[5] = strlen(sdmc_file_path)+1; + cmdbuf[6] = IPC_Desc_PXIBuffer(strlen(sdmc_file_path)+1, 0, 1); + cmdbuf[7] = (uintptr_t) sdmc_file_path; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mFailed to delete existing nim savegame backup\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + printf("Creating nim savegame backup...\n"); + + cmdbuf[0] = IPC_MakeHeader(0x5,8,2); // 0x50202, FSPXI:CreateFile according to 3dbrew + cmdbuf[1] = 0; + cmdbuf[2] = sdmcArchiveHandleLower; + cmdbuf[3] = sdmcArchiveHandleUpper; + cmdbuf[4] = PATH_ASCII; + cmdbuf[5] = strlen(sdmc_file_path)+1; + cmdbuf[6] = 0; + cmdbuf[7] = (u32) sysdataFileSize; + cmdbuf[8] = (u32) (sysdataFileSize >> 32); + cmdbuf[9] = IPC_Desc_PXIBuffer(strlen(sdmc_file_path)+1, 0, 1); + cmdbuf[10] = (uintptr_t) sdmc_file_path; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mCould not create nim savegame backup\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + break; + + } + if (kDown & KEY_B) { + return; + } + + } + + } + + if(R_FAILED(res)){ + printf("\x1b[31mCould not create nim savegame backup\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + sdmcFileHandleLower = cmdbuf[2]; + sdmcFileHandleUpper = cmdbuf[3]; + + cmdbuf = getThreadCommandBuffer(); + + printf("Opening nim savegame backup...\n"); + + cmdbuf[0] = IPC_MakeHeader(0x1,7,2); // 0x101C2, FSPXI:OpenFile according to 3dbrew + cmdbuf[1] = 0; + cmdbuf[2] = sdmcArchiveHandleLower; + cmdbuf[3] = sdmcArchiveHandleUpper; + cmdbuf[4] = PATH_ASCII; + cmdbuf[5] = strlen(sdmc_file_path)+1; + cmdbuf[6] = 0x03; + cmdbuf[7] = 0; + cmdbuf[8] = IPC_Desc_PXIBuffer(strlen(sdmc_file_path)+1, 0, 1); + cmdbuf[9] = (uintptr_t) sdmc_file_path; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mCould not open nim savegame backup\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + sdmcFileHandleLower = cmdbuf[2]; + sdmcFileHandleUpper = cmdbuf[3]; + + cmdbuf = getThreadCommandBuffer(); + + printf("Reading nim savegame to buffer...\n"); + + sysdataFileOffset = 0; + + sysdataFileBuffer = (u8 *) malloc(sysdataFileSize); + memset(sysdataFileBuffer, 0, sysdataFileSize); + + cmdbuf[0] = IPC_MakeHeader(0x9,5,2); // 0x90142, FSPXI:ReadFile according to 3dbrew + cmdbuf[1] = sysdataFileHandleLower; + cmdbuf[2] = sysdataFileHandleUpper; + cmdbuf[3] = (u32) sysdataFileOffset; + cmdbuf[4] = (u32) (sysdataFileOffset >> 32); + cmdbuf[5] = sysdataFileSize; + cmdbuf[6] = IPC_Desc_PXIBuffer(sysdataFileSize, 0, 0); + cmdbuf[7] = (uintptr_t) sysdataFileBuffer; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mCould not read nim savegame to buffer\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + cmdbuf = getThreadCommandBuffer(); + + printf("Writing nim savegame backup to sdmc...\n\n"); + + cmdbuf[0] = IPC_MakeHeader(0xB,6,2); // 0xB0182, FSPXI:WriteFile according to 3dbrew + cmdbuf[1] = sdmcFileHandleLower; + cmdbuf[2] = sdmcFileHandleUpper; + cmdbuf[3] = (u32) sysdataFileOffset; + cmdbuf[4] = (u32) (sysdataFileOffset >> 32); + cmdbuf[5] = 0; // FLUSH_FLAGS + cmdbuf[6] = sysdataFileSize; + cmdbuf[7] = IPC_Desc_PXIBuffer(sysdataFileSize, 0, 1); + cmdbuf[8] = (uintptr_t) sysdataFileBuffer; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mCould not write nim savegame backup to sdmc\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + if(cmdbuf[2] != sysdataFileSize){ + printf("\x1b[31mSize written does not match nim savegame size\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + free(sysdataFileBuffer); + + printf("\x1b[32mSuccessfully backed up nim savegame.\x1b[0m\n\n"); + + cmdbuf = getThreadCommandBuffer(); + + printf("Closing nim savegame backup...\n"); + + cmdbuf[0] = IPC_MakeHeader(0xF,2,0); // 0xF0080, FSPXI:CloseFile according to 3dbrew + cmdbuf[1] = sdmcFileHandleLower; + cmdbuf[2] = sdmcFileHandleUpper; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mCould not close nim savegame backup\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + cmdbuf = getThreadCommandBuffer(); + + printf("Zeroing out nim savegame...\n\n"); + + sysdataFileBuffer = (u8 *) malloc(sysdataFileSize); + memset(sysdataFileBuffer, 0, sysdataFileSize); + + cmdbuf[0] = IPC_MakeHeader(0xB,6,2); // 0xB0182, FSPXI:WriteFile according to 3dbrew + cmdbuf[1] = sysdataFileHandleLower; + cmdbuf[2] = sysdataFileHandleUpper; + cmdbuf[3] = (u32) sysdataFileOffset; + cmdbuf[4] = (u32) (sysdataFileOffset >> 32); + cmdbuf[5] = 0; // FLUSH_FLAGS + cmdbuf[6] = sysdataFileSize; + cmdbuf[7] = IPC_Desc_PXIBuffer(sysdataFileSize, 0, 1); + cmdbuf[8] = (uintptr_t) sysdataFileBuffer; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mCould zero out nim savegame\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + if(cmdbuf[2] != sysdataFileSize){ + printf("\x1b[31mSize zeroed out does not match nim savegame size\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + free(sysdataFileBuffer); + + cmdbuf = getThreadCommandBuffer(); + + printf("Closing nim savegame...\n"); + + cmdbuf[0] = IPC_MakeHeader(0xF,2,0); // 0xF0080, FSPXI:CloseFile according to 3dbrew + cmdbuf[1] = sysdataFileHandleLower; + cmdbuf[2] = sysdataFileHandleUpper; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mCould not close nim savegame\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + cmdbuf = getThreadCommandBuffer(); + + printf("Closing sdmc archive...\n\n"); + + cmdbuf[0] = IPC_MakeHeader(0x16,2,0); // 0x160080, FSPXI:OpenArchive according to 3dbrew + cmdbuf[1] = sdmcArchiveHandleLower; + cmdbuf[2] = sdmcArchiveHandleUpper; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mFailed to close sdmc archive\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + /* + + // File cannot be deleted because nim keeps an active handle to its savegame at all times + // Works fine with fs savegame, nim returns 0xc92044fa + // DESCRIPTION_FAT_OPERATION_DENIED + + cmdbuf = getThreadCommandBuffer(); + + printf("Deleting nim savegame...\n\n"); + + cmdbuf[0] = IPC_MakeHeader(0x2,5,2); // 0x20142, delete file according to 3dbrew; confirmed correct by neobrain + cmdbuf[1] = 0; + cmdbuf[2] = sysdataArchiveHandleLower; + cmdbuf[3] = sysdataArchiveHandleUpper; + cmdbuf[4] = PATH_BINARY; + cmdbuf[5] = sizeof(sysdata_file_path); + cmdbuf[6] = IPC_Desc_PXIBuffer(sizeof(sysdata_file_path), 0, 1); + cmdbuf[7] = (uintptr_t) sysdata_file_path; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mFailed to delete nim savegame\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + printf("\x1b[32mSuccessfully deleted nim savegame.\x1b[0m\n\n"); + + */ + + cmdbuf = getThreadCommandBuffer(); + + printf("Closing sysdata archive...\n"); + + cmdbuf[0] = IPC_MakeHeader(0x16,2,0); // 0x160080, FSPXI:CloseArchive according to 3dbrew + cmdbuf[1] = sysdataArchiveHandleLower; + cmdbuf[2] = sysdataArchiveHandleUpper; + + res = svcSendSyncRequest(pxifs0handle); + if(R_FAILED(res)){ + printf("\x1b[31msvcSendSyncRequest() failed\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + res = cmdbuf[1]; + if(R_FAILED(res)){ + printf("\x1b[31mFailed to close sysdata archive\x1b[0m: 0x%08x.\n", (unsigned int)res); + return; + } + + } + +} + +int main(int argc, char **argv) { + gfxInitDefault(); + consoleInit(GFX_TOP, NULL); + printf("nimSM by Plailect\n\n"); + printf("(X) Restore nim savegame from backup\n"); + printf("(Y) Backup and zero out nim savegame\n"); + printf("(B) Exit\n"); + + while (aptMainLoop()){ + + hidScanInput(); + u32 kDown = hidKeysDown(); + + if (kDown & KEY_X) { + consoleClear(); + printf("Attempting svchax...\n\n"); + svchax_init(true); + aptInit(); + printf("Restoring nim savegame from backup...\n\n"); + manageSave(true); + finished(); + } + if (kDown & KEY_Y) { + consoleClear(); + printf("Attempting svchax...\n\n"); + svchax_init(true); + aptInit(); + printf("Backing up and deleting nim savegame...\n\n"); + manageSave(false); + finished(); + } + if (kDown & KEY_B) { + printf("Exiting...\n"); + break; + } + + } + + gfxFlushBuffers(); + gfxSwapBuffers(); + gspWaitForVBlank(); + + gfxExit(); + return 0; +} diff --git a/source/svchax.c b/source/svchax.c new file mode 100644 index 0000000..1146945 --- /dev/null +++ b/source/svchax.c @@ -0,0 +1,498 @@ +#include <3ds.h> +#include +#include +#include +#include "svchax.h" + +#define CURRENT_KTHREAD 0xFFFF9000 +#define CURRENT_KPROCESS 0xFFFF9004 +#define CURRENT_KPROCESS_HANDLE 0xFFFF8001 +#define RESOURCE_LIMIT_THREADS 0x2 + +#define MCH2_THREAD_COUNT_MAX 0x20 +#define MCH2_THREAD_STACKS_SIZE 0x1000 + +#define SVC_ACL_OFFSET(svc_id) (((svc_id) >> 5) << 2) +#define SVC_ACL_MASK(svc_id) (0x1 << ((svc_id) & 0x1F)) +#define THREAD_PAGE_ACL_OFFSET 0xF38 + +u32 __ctr_svchax = 0; +u32 __ctr_svchax_srv = 0; + +extern void* __service_ptr; + +typedef u32(*backdoor_fn)(u32 arg0, u32 arg1); + +__attribute((naked)) +static u32 svc_7b(backdoor_fn entry_fn, ...) // can pass up to two arguments to entry_fn(...) +{ + __asm__ volatile( + "push {r0, r1, r2} \n\t" + "mov r3, sp \n\t" + "add r0, pc, #12 \n\t" + "svc 0x7B \n\t" + "add sp, sp, #8 \n\t" + "ldr r0, [sp], #4 \n\t" + "bx lr \n\t" + "cpsid aif \n\t" + "ldr r2, [r3], #4 \n\t" + "ldmfd r3!, {r0, r1} \n\t" + "push {r3, lr} \n\t" + "blx r2 \n\t" + "pop {r3, lr} \n\t" + "str r0, [r3, #-4]! \n\t" + "bx lr \n\t"); + return 0; +} + +static void k_enable_all_svcs(u32 isNew3DS) +{ + u32* thread_ACL = *(*(u32***)CURRENT_KTHREAD + 0x22) - 0x6; + u32* process_ACL = *(u32**)CURRENT_KPROCESS + (isNew3DS ? 0x24 : 0x22); + + memset(thread_ACL, 0xFF, 0x10); + memset(process_ACL, 0xFF, 0x10); +} + +static u32 k_read_kaddr(u32* kaddr) +{ + return *kaddr; +} + +static u32 read_kaddr(u32 kaddr) +{ + return svc_7b((backdoor_fn)k_read_kaddr, kaddr); +} + +static u32 k_write_kaddr(u32* kaddr, u32 val) +{ + *kaddr = val; + return 0; +} + +static void write_kaddr(u32 kaddr, u32 val) +{ + svc_7b((backdoor_fn)k_write_kaddr, kaddr, val); +} + +__attribute__((naked)) +static u32 get_thread_page(void) +{ + __asm__ volatile( + "sub r0, sp, #8 \n\t" + "mov r1, #1 \n\t" + "mov r2, #0 \n\t" + "svc 0x2A \n\t" + "mov r0, r1, LSR#12 \n\t" + "mov r0, r0, LSL#12 \n\t" + "bx lr \n\t"); + return 0; +} + +typedef struct +{ + Handle started_event; + Handle lock; + volatile u32 target_kaddr; + volatile u32 target_val; +} mch2_thread_args_t; + +typedef struct +{ + u32* stack_top; + Handle handle; + bool keep; + mch2_thread_args_t args; +} mch2_thread_t; + +typedef struct +{ + u32 old_cpu_time_limit; + bool isNew3DS; + u32 kernel_fcram_mapping_offset; + + Handle arbiter; + volatile u32 alloc_address; + volatile u32 alloc_size; + u8* flush_buffer; + + Handle dummy_threads_lock; + Handle target_threads_lock; + Handle main_thread_lock; + u32* thread_page_va; + u32 thread_page_kva; + + u32 threads_limit; + Handle alloc_thread; + Handle poll_thread; + mch2_thread_t threads[MCH2_THREAD_COUNT_MAX]; +} mch2_vars_t; + +static void alloc_thread_entry(mch2_vars_t* mch2) +{ + u32 tmp; + + svcControlMemory(&tmp, mch2->alloc_address, 0x0, mch2->alloc_size, MEMOP_ALLOC, MEMPERM_READ | MEMPERM_WRITE); + svcExitThread(); +} + +static void dummy_thread_entry(Handle lock) +{ + svcWaitSynchronization(lock, U64_MAX); + svcExitThread(); +} + +static void check_tls_thread_entry(bool* keep) +{ + *keep = !((u32)getThreadLocalStorage() & 0xFFF); + svcExitThread(); +} + +static void target_thread_entry(mch2_thread_args_t* args) +{ + svcSignalEvent(args->started_event); + svcWaitSynchronization(args->lock, U64_MAX); + + if (args->target_kaddr) + write_kaddr(args->target_kaddr, args->target_val); + + svcExitThread(); +} + +static u32 get_first_free_basemem_page(bool isNew3DS) +{ + s64 v1; + int memused_base; + int memused_base_linear; // guessed + + memused_base = osGetMemRegionUsed(MEMREGION_BASE); + + svcGetSystemInfo(&v1, 2, 0); + memused_base_linear = 0x6C000 + v1 + + (osGetKernelVersion() > SYSTEM_VERSION(2, 49, 0) ? (isNew3DS ? 0x2000 : 0x1000) : 0x0); + + return (osGetKernelVersion() > SYSTEM_VERSION(2, 40, 0) ? 0xE0000000 : 0xF0000000) // kernel FCRAM mapping + + (isNew3DS ? 0x10000000 : 0x08000000) // FCRAM size + - (memused_base - memused_base_linear) // memory usage for pages allocated without the MEMOP_LINEAR flag + - 0x1000; // skip to the start addr of the next free page + +} + +static u32 get_threads_limit(void) +{ + Handle resource_limit_handle; + s64 thread_limit_current; + s64 thread_limit_max; + u32 thread_limit_name = RESOURCE_LIMIT_THREADS; + + svcGetResourceLimit(&resource_limit_handle, CURRENT_KPROCESS_HANDLE); + svcGetResourceLimitCurrentValues(&thread_limit_current, resource_limit_handle, &thread_limit_name, 1); + svcGetResourceLimitLimitValues(&thread_limit_max, resource_limit_handle, &thread_limit_name, 1); + svcCloseHandle(resource_limit_handle); + + if (thread_limit_max > MCH2_THREAD_COUNT_MAX) + thread_limit_max = MCH2_THREAD_COUNT_MAX; + + return thread_limit_max - thread_limit_current; +} + +static void do_memchunkhax2(void) +{ + static u8 flush_buffer[0x8000]; + static u8 thread_stacks[MCH2_THREAD_STACKS_SIZE]; + + int i; + u32 tmp; + mch2_vars_t mch2 = {0}; + + mch2.flush_buffer = flush_buffer; + mch2.threads_limit = get_threads_limit(); + mch2.kernel_fcram_mapping_offset = (osGetKernelVersion() > SYSTEM_VERSION(2, 40, 0)) ? 0xC0000000 : 0xD0000000; + + for (i = 0; i < MCH2_THREAD_COUNT_MAX; i++) + mch2.threads[i].stack_top = (u32*)((u32)thread_stacks + (i + 1) * (MCH2_THREAD_STACKS_SIZE / MCH2_THREAD_COUNT_MAX)); + + APT_CheckNew3DS(&mch2.isNew3DS); + APT_GetAppCpuTimeLimit(&mch2.old_cpu_time_limit); + APT_SetAppCpuTimeLimit(5); + + for (i = 0; i < mch2.threads_limit; i++) + { + svcCreateThread(&mch2.threads[i].handle, (ThreadFunc)check_tls_thread_entry, (u32)&mch2.threads[i].keep, + mch2.threads[i].stack_top, 0x18, 0); + svcWaitSynchronization(mch2.threads[i].handle, U64_MAX); + } + + for (i = 0; i < mch2.threads_limit; i++) + if (!mch2.threads[i].keep) + svcCloseHandle(mch2.threads[i].handle); + + svcCreateEvent(&mch2.dummy_threads_lock, RESET_STICKY); + svcClearEvent(mch2.dummy_threads_lock); + + for (i = 0; i < mch2.threads_limit; i++) + if (!mch2.threads[i].keep) + svcCreateThread(&mch2.threads[i].handle, (ThreadFunc)dummy_thread_entry, mch2.dummy_threads_lock, + mch2.threads[i].stack_top, 0x3F - i, 0); + + svcSignalEvent(mch2.dummy_threads_lock); + + for (i = mch2.threads_limit - 1; i >= 0; i--) + if (!mch2.threads[i].keep) + { + svcWaitSynchronization(mch2.threads[i].handle, U64_MAX); + svcCloseHandle(mch2.threads[i].handle); + mch2.threads[i].handle = 0; + } + + svcSleepThread(600000000LL); + svcCloseHandle(mch2.dummy_threads_lock); + + u32 fragmented_address = 0; + + mch2.arbiter = __sync_get_arbiter(); + + u32 linear_buffer; + svcControlMemory(&linear_buffer, 0, 0, 0x1000, MEMOP_ALLOC_LINEAR, MEMPERM_READ | MEMPERM_WRITE); + + u32 linear_size = 0xF000; + u32 skip_pages = 2; + mch2.alloc_size = ((((linear_size - (skip_pages << 12)) + 0x1000) >> 13) << 12); + u32 mem_free = osGetMemRegionFree(MEMREGION_APPLICATION); + + u32 fragmented_size = mem_free - linear_size; + extern u32 __ctru_heap; + extern u32 __ctru_heap_size; + fragmented_address = __ctru_heap + __ctru_heap_size; + u32 linear_address; + mch2.alloc_address = fragmented_address + fragmented_size; + + svcControlMemory(&linear_address, 0x0, 0x0, linear_size, MEMOP_ALLOC_LINEAR, + MEMPERM_READ | MEMPERM_WRITE); + + if (fragmented_size) + svcControlMemory(&tmp, (u32)fragmented_address, 0x0, fragmented_size, MEMOP_ALLOC, + MEMPERM_READ | MEMPERM_WRITE); + + if (skip_pages) + svcControlMemory(&tmp, (u32)linear_address, 0x0, (skip_pages << 12), MEMOP_FREE, MEMPERM_DONTCARE); + + for (i = skip_pages; i < (linear_size >> 12) ; i += 2) + svcControlMemory(&tmp, (u32)linear_address + (i << 12), 0x0, 0x1000, MEMOP_FREE, MEMPERM_DONTCARE); + + u32 alloc_address_kaddr = osConvertVirtToPhys((void*)linear_address) + mch2.kernel_fcram_mapping_offset; + + mch2.thread_page_kva = get_first_free_basemem_page(mch2.isNew3DS) - 0x10000; // skip down 16 pages + ((u32*)linear_buffer)[0] = 1; + ((u32*)linear_buffer)[1] = mch2.thread_page_kva; + ((u32*)linear_buffer)[2] = alloc_address_kaddr + (((mch2.alloc_size >> 12) - 3) << 13) + (skip_pages << 12); + + u32 dst_memchunk = linear_address + (((mch2.alloc_size >> 12) - 2) << 13) + (skip_pages << 12); + + memcpy(flush_buffer, flush_buffer + 0x4000, 0x4000); + + GSPGPU_InvalidateDataCache((void*)dst_memchunk, 16); + GSPGPU_FlushDataCache((void*)linear_buffer, 16); + memcpy(flush_buffer, flush_buffer + 0x4000, 0x4000); + + svcCreateThread(&mch2.alloc_thread, (ThreadFunc)alloc_thread_entry, (u32)&mch2, + mch2.threads[MCH2_THREAD_COUNT_MAX - 1].stack_top, 0x3F, 1); + + while ((u32) svcArbitrateAddress(mch2.arbiter, mch2.alloc_address, ARBITRATION_WAIT_IF_LESS_THAN_TIMEOUT, 0, + 0) == 0xD9001814); + + GX_TextureCopy((void*)linear_buffer, 0, (void*)dst_memchunk, 0, 16, 8); + memcpy(flush_buffer, flush_buffer + 0x4000, 0x4000); + gspWaitForPPF(); + + svcWaitSynchronization(mch2.alloc_thread, U64_MAX); + svcCloseHandle(mch2.alloc_thread); + + u32* mapped_page = (u32*)(mch2.alloc_address + mch2.alloc_size - 0x1000); + + volatile u32* thread_ACL = &mapped_page[THREAD_PAGE_ACL_OFFSET >> 2]; + + svcCreateEvent(&mch2.main_thread_lock, RESET_ONESHOT); + svcCreateEvent(&mch2.target_threads_lock, RESET_STICKY); + svcClearEvent(mch2.target_threads_lock); + + for (i = 0; i < mch2.threads_limit; i++) + { + if (mch2.threads[i].keep) + continue; + + mch2.threads[i].args.started_event = mch2.main_thread_lock; + mch2.threads[i].args.lock = mch2.target_threads_lock; + mch2.threads[i].args.target_kaddr = 0; + + thread_ACL[0] = 0; + GSPGPU_FlushDataCache((void*)thread_ACL, 16); + GSPGPU_InvalidateDataCache((void*)thread_ACL, 16); + + svcClearEvent(mch2.main_thread_lock); + svcCreateThread(&mch2.threads[i].handle, (ThreadFunc)target_thread_entry, (u32)&mch2.threads[i].args, + mch2.threads[i].stack_top, 0x18, 0); + svcWaitSynchronization(mch2.main_thread_lock, U64_MAX); + + if (thread_ACL[0]) + { + thread_ACL[SVC_ACL_OFFSET(0x7B) >> 2] = SVC_ACL_MASK(0x7B); + GSPGPU_FlushDataCache((void*)thread_ACL, 16); + GSPGPU_InvalidateDataCache((void*)thread_ACL, 16); + mch2.threads[i].args.target_kaddr = get_thread_page() + THREAD_PAGE_ACL_OFFSET + SVC_ACL_OFFSET(0x7B); + mch2.threads[i].args.target_val = SVC_ACL_MASK(0x7B); + break; + } + + } + + svcSignalEvent(mch2.target_threads_lock); + + for (i = 0; i < mch2.threads_limit; i++) + { + if (!mch2.threads[i].handle) + continue; + + if (!mch2.threads[i].keep) + svcWaitSynchronization(mch2.threads[i].handle, U64_MAX); + + svcCloseHandle(mch2.threads[i].handle); + } + + svcCloseHandle(mch2.target_threads_lock); + svcCloseHandle(mch2.main_thread_lock); + + svcControlMemory(&tmp, mch2.alloc_address, 0, mch2.alloc_size, MEMOP_FREE, MEMPERM_DONTCARE); + write_kaddr(alloc_address_kaddr + linear_size - 0x3000 + 0x4, alloc_address_kaddr + linear_size - 0x1000); + svcControlMemory(&tmp, (u32)fragmented_address, 0x0, fragmented_size, MEMOP_FREE, MEMPERM_DONTCARE); + + for (i = 1 + skip_pages; i < (linear_size >> 12) ; i += 2) + svcControlMemory(&tmp, (u32)linear_address + (i << 12), 0x0, 0x1000, MEMOP_FREE, MEMPERM_DONTCARE); + + svcControlMemory(&tmp, linear_buffer, 0, 0x1000, MEMOP_FREE, MEMPERM_DONTCARE); + + APT_SetAppCpuTimeLimit(mch2.old_cpu_time_limit); +} + + +static void gspwn(u32 dst, u32 src, u32 size, u8* flush_buffer) +{ + memcpy(flush_buffer, flush_buffer + 0x4000, 0x4000); + GSPGPU_InvalidateDataCache((void*)dst, size); + GSPGPU_FlushDataCache((void*)src, size); + memcpy(flush_buffer, flush_buffer + 0x4000, 0x4000); + + GX_TextureCopy((void*)src, 0, (void*)dst, 0, size, 8); + gspWaitForPPF(); + + memcpy(flush_buffer, flush_buffer + 0x4000, 0x4000); +} + +/* pseudo-code: + * if(val2) + * { + * *(u32*)val1 = val2; + * *(u32*)(val2 + 8) = (val1 - 4); + * } + * else + * *(u32*)val1 = 0x0; + */ + +// X-X--X-X +// X-XXXX-X + +static void memchunkhax1_write_pair(u32 val1, u32 val2) +{ + u32 linear_buffer; + u8* flush_buffer; + u32 tmp; + + u32* next_ptr3; + u32* prev_ptr3; + + u32* next_ptr1; + u32* prev_ptr6; + + svcControlMemory(&linear_buffer, 0, 0, 0x10000, MEMOP_ALLOC_LINEAR, MEMPERM_READ | MEMPERM_WRITE); + + flush_buffer = (u8*)(linear_buffer + 0x8000); + + svcControlMemory(&tmp, linear_buffer + 0x1000, 0, 0x1000, MEMOP_FREE, 0); + svcControlMemory(&tmp, linear_buffer + 0x3000, 0, 0x2000, MEMOP_FREE, 0); + svcControlMemory(&tmp, linear_buffer + 0x6000, 0, 0x1000, MEMOP_FREE, 0); + + next_ptr1 = (u32*)(linear_buffer + 0x0004); + gspwn(linear_buffer + 0x0000, linear_buffer + 0x1000, 16, flush_buffer); + + next_ptr3 = (u32*)(linear_buffer + 0x2004); + prev_ptr3 = (u32*)(linear_buffer + 0x2008); + gspwn(linear_buffer + 0x2000, linear_buffer + 0x3000, 16, flush_buffer); + + prev_ptr6 = (u32*)(linear_buffer + 0x5008); + gspwn(linear_buffer + 0x5000, linear_buffer + 0x6000, 16, flush_buffer); + + *next_ptr1 = *next_ptr3; + *prev_ptr6 = *prev_ptr3; + + *prev_ptr3 = val1 - 4; + *next_ptr3 = val2; + gspwn(linear_buffer + 0x3000, linear_buffer + 0x2000, 16, flush_buffer); + svcControlMemory(&tmp, 0, 0, 0x2000, MEMOP_ALLOC_LINEAR, MEMPERM_READ | MEMPERM_WRITE); + + gspwn(linear_buffer + 0x1000, linear_buffer + 0x0000, 16, flush_buffer); + gspwn(linear_buffer + 0x6000, linear_buffer + 0x5000, 16, flush_buffer); + + svcControlMemory(&tmp, linear_buffer + 0x0000, 0, 0x1000, MEMOP_FREE, 0); + svcControlMemory(&tmp, linear_buffer + 0x2000, 0, 0x4000, MEMOP_FREE, 0); + svcControlMemory(&tmp, linear_buffer + 0x7000, 0, 0x9000, MEMOP_FREE, 0); + +} + +static void do_memchunkhax1(void) +{ + u32 saved_vram_value = *(u32*)0x1F000008; + + // 0x1F000000 contains the enable bit for svc 0x7B + memchunkhax1_write_pair(get_thread_page() + THREAD_PAGE_ACL_OFFSET + SVC_ACL_OFFSET(0x7B), 0x1F000000); + + write_kaddr(0x1F000008, saved_vram_value); +} + +Result svchax_init(bool patch_srv) +{ + bool isNew3DS; + APT_CheckNew3DS(&isNew3DS); + + u32 kver = osGetKernelVersion(); + + if (!__ctr_svchax) + { + if (__service_ptr) + { + if (kver > SYSTEM_VERSION(2, 50, 11)) + return -1; + else if (kver > SYSTEM_VERSION(2, 46, 0)) + do_memchunkhax2(); + else + do_memchunkhax1(); + } + + svc_7b((backdoor_fn)k_enable_all_svcs, isNew3DS); + + __ctr_svchax = 1; + } + + if (patch_srv && !__ctr_svchax_srv) + { + u32 PID_kaddr = read_kaddr(CURRENT_KPROCESS) + (isNew3DS ? 0xBC : (kver > SYSTEM_VERSION(2, 40, 0)) ? 0xB4 : 0xAC); + u32 old_PID = read_kaddr(PID_kaddr); + write_kaddr(PID_kaddr, 0); + srvExit(); + srvInit(); + write_kaddr(PID_kaddr, old_PID); + + __ctr_svchax_srv = 1; + } + + return 0; +}