├── sysmod ├── toolbox.json ├── sys-patch.json ├── Makefile └── src │ └── main.cpp ├── .gitmodules ├── .gitignore ├── .github └── workflows │ └── build_depoly.yml ├── .vscode └── c_cpp_properties.json ├── common └── minIni │ ├── minGlue.h │ ├── minGlue.c │ ├── minIni.h │ └── minIni.c ├── LICENSE ├── Makefile ├── README.md └── overlay ├── Makefile └── src └── main.cpp /sysmod/toolbox.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "sys-patch", 3 | "tid" : "420000000000000B", 4 | "requires_reboot": false 5 | } 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "overlay/libtesla"] 2 | path = overlay/libtesla 3 | url = https://github.com/ITotalJustice/libtesla.git 4 | branch = tj 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | Firmware 3 | firmware 4 | 420000000000000B 5 | *.txt 6 | *.elf 7 | *.npdm 8 | *.nso 9 | *.nsp 10 | *.zip 11 | *.nacp 12 | *.ovl 13 | *.nca 14 | out 15 | ignoreme 16 | .vscode/settings.json 17 | -------------------------------------------------------------------------------- /.github/workflows/build_depoly.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | container: devkitpro/devkita64:latest 8 | 9 | steps: 10 | - name: Checkout 🛎️ 11 | uses: actions/checkout@master 12 | with: 13 | submodules: recursive 14 | 15 | - name: Build 16 | run: make dist -j2 17 | 18 | - uses: actions/upload-artifact@master 19 | with: 20 | name: sys-patch 21 | path: sys-patch.zip 22 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "switch", 5 | "includePath": [ 6 | "${default}", 7 | "${workspaceFolder}/**", 8 | "${DEVKITPRO}/libnx/include/", 9 | "${DEVKITPRO}/portlibs/switch/include/", 10 | "${workspaceFolder}/overlay/libtesla/include", 11 | "${workspaceFolder}/common" 12 | ], 13 | "defines": [], 14 | "cStandard": "c17", 15 | "cppStandard": "c++23", 16 | "compilerPath": "${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-gcc" 17 | } 18 | ], 19 | "version": 4 20 | } 21 | -------------------------------------------------------------------------------- /common/minIni/minGlue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if defined __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include 8 | 9 | struct NxFile { 10 | FsFile file; 11 | FsFileSystem system; 12 | s64 offset; 13 | }; 14 | 15 | #define INI_FILETYPE struct NxFile 16 | #define INI_FILEPOS s64 17 | #define INI_OPENREWRITE 18 | #define INI_REMOVE 19 | 20 | bool ini_openread(const char* filename, struct NxFile* nxfile); 21 | bool ini_openwrite(const char* filename, struct NxFile* nxfile); 22 | bool ini_openrewrite(const char* filename, struct NxFile* nxfile); 23 | bool ini_close(struct NxFile* nxfile); 24 | bool ini_read(char* buffer, u64 size, struct NxFile* nxfile); 25 | bool ini_write(const char* buffer, struct NxFile* nxfile); 26 | bool ini_tell(struct NxFile* nxfile, s64* pos); 27 | bool ini_seek(struct NxFile* nxfile, s64* pos); 28 | bool ini_rename(const char* src, const char* dst); 29 | bool ini_remove(const char* filename); 30 | 31 | #if defined __cplusplus 32 | } // extern "C" { 33 | #endif 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 ITotalJustice 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MAKEFILES := sysmod overlay 2 | TARGETS := $(foreach dir,$(MAKEFILES),$(CURDIR)/$(dir)) 3 | 4 | # the below was taken from atmosphere + switch-examples makefile 5 | export VERSION := 1.4.2 6 | export GIT_BRANCH := $(shell git symbolic-ref --short HEAD) 7 | 8 | ifeq ($(strip $(shell git status --porcelain 2>/dev/null)),) 9 | export GIT_REVISION := $(GIT_BRANCH)-$(shell git rev-parse --short HEAD) 10 | export VERSION_DIRTY := $(VERSION) 11 | export VERSION_WITH_HASH := $(VERSION)-$(shell git rev-parse --short HEAD) 12 | else 13 | export GIT_REVISION := $(GIT_BRANCH)-$(shell git rev-parse --short HEAD)-dirty 14 | export VERSION_DIRTY := $(VERSION)-dirty 15 | export VERSION_WITH_HASH := $(VERSION)-$(shell git rev-parse --short HEAD)-dirty 16 | endif 17 | 18 | export BUILD_DATE := -DDATE_YEAR=\"$(shell date +%Y)\" \ 19 | -DDATE_MONTH=\"$(shell date +%m)\" \ 20 | -DDATE_DAY=\"$(shell date +%d)\" \ 21 | -DDATE_HOUR=\"$(shell date +%H)\" \ 22 | -DDATE_MIN=\"$(shell date +%M)\" \ 23 | -DDATE_SEC=\"$(shell date +%S)\" \ 24 | 25 | export CUSTOM_DEFINES := -DVERSION=\"v$(VERSION)\" \ 26 | -DGIT_BRANCH=\"$(GIT_BRANCH)\" \ 27 | -DGIT_REVISION=\"$(GIT_REVISION)\" \ 28 | -DVERSION_DIRTY=\"$(VERSION_DIRTY)\" \ 29 | -DVERSION_WITH_HASH=\"$(VERSION_WITH_HASH)\" \ 30 | $(BUILD_DATE) 31 | 32 | all: $(TARGETS) 33 | @mkdir -p out/ 34 | @cp -R sysmod/out/* out/ 35 | @cp -R overlay/out/* out/ 36 | 37 | .PHONY: $(TARGETS) 38 | 39 | $(TARGETS): 40 | @$(MAKE) -C $@ 41 | 42 | clean: 43 | @rm -rf out 44 | @for i in $(TARGETS); do $(MAKE) -C $$i clean || exit 1; done; 45 | 46 | dist: all 47 | @for i in $(TARGETS); do $(MAKE) -C $$i dist || exit 1; done; 48 | @echo making dist ... 49 | 50 | @rm -f sys-patch.zip 51 | @cd out; zip -r ../sys-patch.zip ./*; cd ../ 52 | -------------------------------------------------------------------------------- /common/minIni/minGlue.c: -------------------------------------------------------------------------------- 1 | #include "minGlue.h" 2 | #include 3 | 4 | static bool ini_open(const char* filename, struct NxFile* nxfile, u32 mode) { 5 | Result rc = {0}; 6 | char filename_buf[FS_MAX_PATH] = {0}; 7 | 8 | if (R_FAILED(rc = fsOpenSdCardFileSystem(&nxfile->system))) { 9 | return false; 10 | } 11 | 12 | strcpy(filename_buf, filename); 13 | 14 | if (R_FAILED(rc = fsFsOpenFile(&nxfile->system, filename_buf, mode, &nxfile->file))) { 15 | if (mode & FsOpenMode_Write) { 16 | if (R_FAILED(rc = fsFsCreateFile(&nxfile->system, filename_buf, 0, 0))) { 17 | fsFsClose(&nxfile->system); 18 | return false; 19 | } else { 20 | if (R_FAILED(rc = fsFsOpenFile(&nxfile->system, filename_buf, mode, &nxfile->file))) { 21 | fsFsClose(&nxfile->system); 22 | return false; 23 | } 24 | } 25 | } else { 26 | fsFsClose(&nxfile->system); 27 | return false; 28 | } 29 | } 30 | 31 | nxfile->offset = 0; 32 | return true; 33 | } 34 | 35 | bool ini_openread(const char* filename, struct NxFile* nxfile) { 36 | return ini_open(filename, nxfile, FsOpenMode_Read); 37 | } 38 | 39 | bool ini_openwrite(const char* filename, struct NxFile* nxfile) { 40 | return ini_open(filename, nxfile, FsOpenMode_Write|FsOpenMode_Append); 41 | } 42 | 43 | bool ini_openrewrite(const char* filename, struct NxFile* nxfile) { 44 | return ini_open(filename, nxfile, FsOpenMode_Read|FsOpenMode_Write|FsOpenMode_Append); 45 | } 46 | 47 | bool ini_close(struct NxFile* nxfile) { 48 | fsFileClose(&nxfile->file); 49 | fsFsClose(&nxfile->system); 50 | return true; 51 | } 52 | 53 | bool ini_read(char* buffer, u64 size, struct NxFile* nxfile) { 54 | u64 bytes_read = {0}; 55 | if (R_FAILED(fsFileRead(&nxfile->file, nxfile->offset, buffer, size, FsReadOption_None, &bytes_read))) { 56 | return false; 57 | } 58 | 59 | if (!bytes_read) { 60 | return false; 61 | } 62 | 63 | char *eol = {0}; 64 | 65 | if ((eol = strchr(buffer, '\n')) == NULL) { 66 | eol = strchr(buffer, '\r'); 67 | } 68 | 69 | if (eol != NULL) { 70 | *++eol = '\0'; 71 | bytes_read = eol - buffer; 72 | } 73 | 74 | nxfile->offset += bytes_read; 75 | return true; 76 | } 77 | 78 | bool ini_write(const char* buffer, struct NxFile* nxfile) { 79 | const size_t size = strlen(buffer); 80 | if (R_FAILED(fsFileWrite(&nxfile->file, nxfile->offset, buffer, size, FsWriteOption_None))) { 81 | return false; 82 | } 83 | nxfile->offset += size; 84 | return true; 85 | } 86 | 87 | bool ini_tell(struct NxFile* nxfile, s64* pos) { 88 | *pos = nxfile->offset; 89 | return true; 90 | } 91 | 92 | bool ini_seek(struct NxFile* nxfile, s64* pos) { 93 | nxfile->offset = *pos; 94 | return true; 95 | } 96 | 97 | bool ini_rename(const char* src, const char* dst) { 98 | Result rc = {0}; 99 | FsFileSystem fs = {0}; 100 | char src_buf[FS_MAX_PATH] = {0}; 101 | char dst_buf[FS_MAX_PATH] = {0}; 102 | 103 | if (R_FAILED(rc = fsOpenSdCardFileSystem(&fs))) { 104 | return false; 105 | } 106 | 107 | strcpy(src_buf, src); 108 | strcpy(dst_buf, dst); 109 | rc = fsFsRenameFile(&fs, src_buf, dst_buf); 110 | fsFsClose(&fs); 111 | return R_SUCCEEDED(rc); 112 | } 113 | 114 | bool ini_remove(const char* filename) { 115 | Result rc = {0}; 116 | FsFileSystem fs = {0}; 117 | char filename_buf[FS_MAX_PATH] = {0}; 118 | 119 | if (R_FAILED(rc = fsOpenSdCardFileSystem(&fs))) { 120 | return false; 121 | } 122 | 123 | strcpy(filename_buf, filename); 124 | rc = fsFsDeleteFile(&fs, filename_buf); 125 | fsFsClose(&fs); 126 | return R_SUCCEEDED(rc); 127 | } 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sys-patch 2 | 3 | A script-like system module that patches **fs**, **es**, **ldr** and **nifm** on boot. 4 | 5 | --- 6 | 7 | ## Config 8 | 9 | **sys-patch** features a simple config. This can be manually edited or updated using the overlay. 10 | 11 | The configuration file can be found in `/config/sys-patch/config.ini`. The file is generated once the module is ran for the first time. 12 | 13 | ```ini 14 | [options] 15 | patch_sysmmc=1 ; 1=(default) patch sysmmc, 0=don't patch sysmmc 16 | patch_emummc=1 ; 1=(default) patch emummc, 0=don't patch emummc 17 | enable_logging=1 ; 1=(default) output /config/sys-patch/log.ini 0=no log 18 | version_skip=1 ; 1=(default) skips out of date patterns, 0=search all patterns 19 | ``` 20 | 21 | --- 22 | 23 | ## Overlay 24 | 25 | The overlay can be used to change the config options and to see what patches are applied. 26 | 27 | - Unpatched means the patch wasn't applied (likely not found). 28 | - Patched (green) means it was patched by sys-patch. 29 | - Patched (yellow) means it was already patched, likely by sigpatches or a custom Atmosphere build. 30 | 31 |

32 | 33 | 34 | 35 | 36 |

37 | 38 | --- 39 | 40 | ## Building 41 | 42 | ### prerequisites 43 | - Install [devkitpro](https://devkitpro.org/wiki/Getting_Started) 44 | - Run the following: 45 | ```sh 46 | git clone --recurse-submodules https://github.com/ITotalJustice/sys-patch.git 47 | cd ./sys-patch 48 | make 49 | ``` 50 | 51 | The output of `out/` can be copied to your SD card. 52 | To activate the sys-module, reboot your switch, or, use [sysmodules overlay](https://github.com/WerWolv/ovl-sysmodules/releases/latest) with the accompanying overlay to activate it. 53 | 54 | --- 55 | 56 | ## What is being patched? 57 | 58 | Here's a quick run down of what's being patched: 59 | 60 | - **fs** 61 | - **es** 62 | - **ldr** 63 | - **nifm** 64 | 65 | **fs** and **es** need new patches after every new firmware version. 66 | **ldr** needs new patches after every new [Atmosphere](https://github.com/Atmosphere-NX/Atmosphere/) release. 67 | **nifm** ctest patch allows the device to connect to a network without needing to make a connection to a server. 68 | 69 | The patches are applied on boot. Once done, the sys-module stops running. 70 | The memory footprint *(16kib)* and the binary size *(~50kib)* are both very small. 71 | 72 | --- 73 | 74 | ## FAQ: 75 | 76 | ### If I am using sigpatches already, is there any point in using this? 77 | 78 | Yes, in 3 situations. 79 | 80 | 1. A new **ldr** patch needs to be created after every Atmosphere update. Sometimes, a new silent Atmosphere update is released. This tool will always patch **ldr** without having to update patches. 81 | 82 | 2. Building Atmosphere from src will require you to generate a new **ldr** patch for that custom built Atmosphere. This is easy enough due to the public scripts / tools that exist out there, however this will always be able to patch **ldr**. 83 | 84 | 3. If you forget to update your patches when you update your firmware / Atmosphere, this sys-module should be able to patch everything. So it can be used as a fall back. 85 | 86 | ### Does this mean that I should stop downloading / using sigpatches? 87 | 88 | No, I would personally recommend continuing to use sigpatches. Reason being is that should this tool ever break, i likely wont be quick to fix it. 89 | 90 | --- 91 | 92 | ## Credits / Thanks 93 | 94 | Software is built on the shoulders of giants. This tool wouldn't be possible without these people: 95 | 96 | - MrDude 97 | - BornToHonk (farni) 98 | - TeJay 99 | - ArchBox 100 | - Switchbrew (libnx, switch-examples) 101 | - DevkitPro (toolchain) 102 | - [minIni](https://github.com/compuphase/minIni) 103 | - [libtesla](https://github.com/WerWolv/libtesla) 104 | - [Shoutout to the best switch cfw setup guide](https://rentry.org/SwitchHackingIsEasy) 105 | -------------------------------------------------------------------------------- /sysmod/sys-patch.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sys-patch", 3 | "title_id": "0x420000000000000B", 4 | "title_id_range_min": "0x420000000000000B", 5 | "title_id_range_max": "0x420000000000000B", 6 | "main_thread_stack_size": "0x1000", 7 | "main_thread_priority": 49, 8 | "default_cpu_id": 3, 9 | "process_category": 1, 10 | "is_retail": true, 11 | "pool_partition": 2, 12 | "is_64_bit": true, 13 | "address_space_type": 1, 14 | "filesystem_access": { 15 | "permissions": "0xffffffffffffffff" 16 | }, 17 | "service_access": ["*"], 18 | "service_host": ["patch"], 19 | "kernel_capabilities": [{ 20 | "type": "kernel_flags", 21 | "value": { 22 | "highest_thread_priority": 63, 23 | "lowest_thread_priority": 24, 24 | "lowest_cpu_id": 3, 25 | "highest_cpu_id": 3 26 | } 27 | }, { 28 | "type": "syscalls", 29 | "value": { 30 | "svcUnknown": "0x00", 31 | "svcSetHeapSize": "0x01", 32 | "svcSetMemoryPermission": "0x02", 33 | "svcSetMemoryAttribute": "0x03", 34 | "svcMapMemory": "0x04", 35 | "svcUnmapMemory": "0x05", 36 | "svcQueryMemory": "0x06", 37 | "svcExitProcess": "0x07", 38 | "svcCreateThread": "0x08", 39 | "svcStartThread": "0x09", 40 | "svcExitThread": "0x0a", 41 | "svcSleepThread": "0x0b", 42 | "svcGetThreadPriority": "0x0c", 43 | "svcSetThreadPriority": "0x0d", 44 | "svcGetThreadCoreMask": "0x0e", 45 | "svcSetThreadCoreMask": "0x0f", 46 | "svcGetCurrentProcessorNumber": "0x10", 47 | "svcSignalEvent": "0x11", 48 | "svcClearEvent": "0x12", 49 | "svcMapSharedMemory": "0x13", 50 | "svcUnmapSharedMemory": "0x14", 51 | "svcCreateTransferMemory": "0x15", 52 | "svcCloseHandle": "0x16", 53 | "svcResetSignal": "0x17", 54 | "svcWaitSynchronization": "0x18", 55 | "svcCancelSynchronization": "0x19", 56 | "svcArbitrateLock": "0x1a", 57 | "svcArbitrateUnlock": "0x1b", 58 | "svcWaitProcessWideKeyAtomic": "0x1c", 59 | "svcSignalProcessWideKey": "0x1d", 60 | "svcGetSystemTick": "0x1e", 61 | "svcConnectToNamedPort": "0x1f", 62 | "svcSendSyncRequestLight": "0x20", 63 | "svcSendSyncRequest": "0x21", 64 | "svcSendSyncRequestWithUserBuffer": "0x22", 65 | "svcSendAsyncRequestWithUserBuffer": "0x23", 66 | "svcGetProcessId": "0x24", 67 | "svcGetThreadId": "0x25", 68 | "svcBreak": "0x26", 69 | "svcOutputDebugString": "0x27", 70 | "svcReturnFromException": "0x28", 71 | "svcGetInfo": "0x29", 72 | "svcFlushEntireDataCache": "0x2a", 73 | "svcFlushDataCache": "0x2b", 74 | "svcMapPhysicalMemory": "0x2c", 75 | "svcUnmapPhysicalMemory": "0x2d", 76 | "svcGetFutureThreadInfo": "0x2e", 77 | "svcGetLastThreadInfo": "0x2f", 78 | "svcGetResourceLimitLimitValue": "0x30", 79 | "svcGetResourceLimitCurrentValue": "0x31", 80 | "svcSetThreadActivity": "0x32", 81 | "svcGetThreadContext3": "0x33", 82 | "svcWaitForAddress": "0x34", 83 | "svcSignalToAddress": "0x35", 84 | "svcUnknown": "0x36", 85 | "svcUnknown": "0x37", 86 | "svcUnknown": "0x38", 87 | "svcUnknown": "0x39", 88 | "svcUnknown": "0x3a", 89 | "svcUnknown": "0x3b", 90 | "svcDumpInfo": "0x3c", 91 | "svcDumpInfoNew": "0x3d", 92 | "svcUnknown": "0x3e", 93 | "svcUnknown": "0x3f", 94 | "svcCreateSession": "0x40", 95 | "svcAcceptSession": "0x41", 96 | "svcReplyAndReceiveLight": "0x42", 97 | "svcReplyAndReceive": "0x43", 98 | "svcReplyAndReceiveWithUserBuffer": "0x44", 99 | "svcCreateEvent": "0x45", 100 | "svcUnknown": "0x46", 101 | "svcUnknown": "0x47", 102 | "svcMapPhysicalMemoryUnsafe": "0x48", 103 | "svcUnmapPhysicalMemoryUnsafe": "0x49", 104 | "svcSetUnsafeLimit": "0x4a", 105 | "svcCreateCodeMemory": "0x4b", 106 | "svcControlCodeMemory": "0x4c", 107 | "svcSleepSystem": "0x4d", 108 | "svcReadWriteRegister": "0x4e", 109 | "svcSetProcessActivity": "0x4f", 110 | "svcCreateSharedMemory": "0x50", 111 | "svcMapTransferMemory": "0x51", 112 | "svcUnmapTransferMemory": "0x52", 113 | "svcCreateInterruptEvent": "0x53", 114 | "svcQueryPhysicalAddress": "0x54", 115 | "svcQueryIoMapping": "0x55", 116 | "svcCreateDeviceAddressSpace": "0x56", 117 | "svcAttachDeviceAddressSpace": "0x57", 118 | "svcDetachDeviceAddressSpace": "0x58", 119 | "svcMapDeviceAddressSpaceByForce": "0x59", 120 | "svcMapDeviceAddressSpaceAligned": "0x5a", 121 | "svcMapDeviceAddressSpace": "0x5b", 122 | "svcUnmapDeviceAddressSpace": "0x5c", 123 | "svcInvalidateProcessDataCache": "0x5d", 124 | "svcStoreProcessDataCache": "0x5e", 125 | "svcFlushProcessDataCache": "0x5f", 126 | "svcDebugActiveProcess": "0x60", 127 | "svcBreakDebugProcess": "0x61", 128 | "svcTerminateDebugProcess": "0x62", 129 | "svcGetDebugEvent": "0x63", 130 | "svcContinueDebugEvent": "0x64", 131 | "svcGetProcessList": "0x65", 132 | "svcGetThreadList": "0x66", 133 | "svcGetDebugThreadContext": "0x67", 134 | "svcSetDebugThreadContext": "0x68", 135 | "svcQueryDebugProcessMemory": "0x69", 136 | "svcReadDebugProcessMemory": "0x6a", 137 | "svcWriteDebugProcessMemory": "0x6b", 138 | "svcSetHardwareBreakPoint": "0x6c", 139 | "svcGetDebugThreadParam": "0x6d", 140 | "svcUnknown": "0x6e", 141 | "svcGetSystemInfo": "0x6f", 142 | "svcCreatePort": "0x70", 143 | "svcManageNamedPort": "0x71", 144 | "svcConnectToPort": "0x72", 145 | "svcSetProcessMemoryPermission": "0x73", 146 | "svcMapProcessMemory": "0x74", 147 | "svcUnmapProcessMemory": "0x75", 148 | "svcQueryProcessMemory": "0x76", 149 | "svcMapProcessCodeMemory": "0x77", 150 | "svcUnmapProcessCodeMemory": "0x78", 151 | "svcCreateProcess": "0x79", 152 | "svcStartProcess": "0x7a", 153 | "svcTerminateProcess": "0x7b", 154 | "svcGetProcessInfo": "0x7c", 155 | "svcCreateResourceLimit": "0x7d", 156 | "svcSetResourceLimitLimitValue": "0x7e", 157 | "svcCallSecureMonitor": "0x7f" 158 | } 159 | }, { 160 | "type": "min_kernel_version", 161 | "value": "0x0030" 162 | }, { 163 | "type": "handle_table_size", 164 | "value": 64 165 | }, { 166 | "type": "debug_flags", 167 | "value": { 168 | "allow_debug": false, 169 | "force_debug": true 170 | } 171 | }] 172 | } 173 | -------------------------------------------------------------------------------- /common/minIni/minIni.h: -------------------------------------------------------------------------------- 1 | /* minIni - Multi-Platform INI file parser, suitable for embedded systems 2 | * 3 | * Copyright (c) CompuPhase, 2008-2021 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | * use this file except in compliance with the License. You may obtain a copy 7 | * of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | * 17 | * Version: $Id: minIni.h 53 2015-01-18 13:35:11Z thiadmer.riemersma@gmail.com $ 18 | */ 19 | #ifndef MININI_H 20 | #define MININI_H 21 | 22 | #include "minGlue.h" 23 | 24 | #if (defined _UNICODE || defined __UNICODE__ || defined UNICODE) && !defined INI_ANSIONLY 25 | #include 26 | #define mTCHAR TCHAR 27 | #else 28 | /* force TCHAR to be "char", but only for minIni */ 29 | #define mTCHAR char 30 | #endif 31 | 32 | #if !defined INI_BUFFERSIZE 33 | #define INI_BUFFERSIZE 512 34 | #endif 35 | 36 | #if defined __cplusplus 37 | extern "C" { 38 | #endif 39 | 40 | int ini_getbool(const mTCHAR *Section, const mTCHAR *Key, int DefValue, const mTCHAR *Filename); 41 | long ini_getl(const mTCHAR *Section, const mTCHAR *Key, long DefValue, const mTCHAR *Filename); 42 | int ini_gets(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *DefValue, mTCHAR *Buffer, int BufferSize, const mTCHAR *Filename); 43 | int ini_getsection(int idx, mTCHAR *Buffer, int BufferSize, const mTCHAR *Filename); 44 | int ini_getkey(const mTCHAR *Section, int idx, mTCHAR *Buffer, int BufferSize, const mTCHAR *Filename); 45 | 46 | int ini_hassection(const mTCHAR *Section, const mTCHAR *Filename); 47 | int ini_haskey(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Filename); 48 | 49 | #if defined INI_REAL 50 | INI_REAL ini_getf(const mTCHAR *Section, const mTCHAR *Key, INI_REAL DefValue, const mTCHAR *Filename); 51 | #endif 52 | 53 | #if !defined INI_READONLY 54 | int ini_putl(const mTCHAR *Section, const mTCHAR *Key, long Value, const mTCHAR *Filename); 55 | int ini_puts(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, const mTCHAR *Filename); 56 | #if defined INI_REAL 57 | int ini_putf(const mTCHAR *Section, const mTCHAR *Key, INI_REAL Value, const mTCHAR *Filename); 58 | #endif 59 | #endif /* INI_READONLY */ 60 | 61 | #if !defined INI_NOBROWSE 62 | typedef int (*INI_CALLBACK)(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData); 63 | int ini_browse(INI_CALLBACK Callback, void *UserData, const mTCHAR *Filename); 64 | #endif /* INI_NOBROWSE */ 65 | 66 | #if defined __cplusplus 67 | } 68 | #endif 69 | 70 | 71 | #if defined __cplusplus 72 | 73 | #if defined __WXWINDOWS__ 74 | #include "wxMinIni.h" 75 | #else 76 | #include 77 | 78 | /* The C++ class in minIni.h was contributed by Steven Van Ingelgem. */ 79 | class minIni 80 | { 81 | public: 82 | minIni(const std::string& filename) : iniFilename(filename) 83 | { } 84 | 85 | bool getbool(const std::string& Section, const std::string& Key, bool DefValue=false) const 86 | { return ini_getbool(Section.c_str(), Key.c_str(), int(DefValue), iniFilename.c_str()) != 0; } 87 | 88 | long getl(const std::string& Section, const std::string& Key, long DefValue=0) const 89 | { return ini_getl(Section.c_str(), Key.c_str(), DefValue, iniFilename.c_str()); } 90 | 91 | int geti(const std::string& Section, const std::string& Key, int DefValue=0) const 92 | { return static_cast(this->getl(Section, Key, long(DefValue))); } 93 | 94 | std::string gets(const std::string& Section, const std::string& Key, const std::string& DefValue="") const 95 | { 96 | char buffer[INI_BUFFERSIZE]; 97 | ini_gets(Section.c_str(), Key.c_str(), DefValue.c_str(), buffer, INI_BUFFERSIZE, iniFilename.c_str()); 98 | return buffer; 99 | } 100 | 101 | std::string getsection(int idx) const 102 | { 103 | char buffer[INI_BUFFERSIZE]; 104 | ini_getsection(idx, buffer, INI_BUFFERSIZE, iniFilename.c_str()); 105 | return buffer; 106 | } 107 | 108 | std::string getkey(const std::string& Section, int idx) const 109 | { 110 | char buffer[INI_BUFFERSIZE]; 111 | ini_getkey(Section.c_str(), idx, buffer, INI_BUFFERSIZE, iniFilename.c_str()); 112 | return buffer; 113 | } 114 | 115 | bool hassection(const std::string& Section) const 116 | { return ini_hassection(Section.c_str(), iniFilename.c_str()) != 0; } 117 | 118 | bool haskey(const std::string& Section, const std::string& Key) const 119 | { return ini_haskey(Section.c_str(), Key.c_str(), iniFilename.c_str()) != 0; } 120 | 121 | #if defined INI_REAL 122 | INI_REAL getf(const std::string& Section, const std::string& Key, INI_REAL DefValue=0) const 123 | { return ini_getf(Section.c_str(), Key.c_str(), DefValue, iniFilename.c_str()); } 124 | #endif 125 | 126 | #if ! defined INI_READONLY 127 | bool put(const std::string& Section, const std::string& Key, long Value) 128 | { return ini_putl(Section.c_str(), Key.c_str(), Value, iniFilename.c_str()) != 0; } 129 | 130 | bool put(const std::string& Section, const std::string& Key, int Value) 131 | { return ini_putl(Section.c_str(), Key.c_str(), (long)Value, iniFilename.c_str()) != 0; } 132 | 133 | bool put(const std::string& Section, const std::string& Key, bool Value) 134 | { return ini_putl(Section.c_str(), Key.c_str(), (long)Value, iniFilename.c_str()) != 0; } 135 | 136 | bool put(const std::string& Section, const std::string& Key, const std::string& Value) 137 | { return ini_puts(Section.c_str(), Key.c_str(), Value.c_str(), iniFilename.c_str()) != 0; } 138 | 139 | bool put(const std::string& Section, const std::string& Key, const char* Value) 140 | { return ini_puts(Section.c_str(), Key.c_str(), Value, iniFilename.c_str()) != 0; } 141 | 142 | #if defined INI_REAL 143 | bool put(const std::string& Section, const std::string& Key, INI_REAL Value) 144 | { return ini_putf(Section.c_str(), Key.c_str(), Value, iniFilename.c_str()) != 0; } 145 | #endif 146 | 147 | bool del(const std::string& Section, const std::string& Key) 148 | { return ini_puts(Section.c_str(), Key.c_str(), 0, iniFilename.c_str()) != 0; } 149 | 150 | bool del(const std::string& Section) 151 | { return ini_puts(Section.c_str(), 0, 0, iniFilename.c_str()) != 0; } 152 | #endif 153 | 154 | #if !defined INI_NOBROWSE 155 | bool browse(INI_CALLBACK Callback, void *UserData) const 156 | { return ini_browse(Callback, UserData, iniFilename.c_str()) != 0; } 157 | #endif 158 | 159 | private: 160 | std::string iniFilename; 161 | }; 162 | 163 | #endif /* __WXWINDOWS__ */ 164 | #endif /* __cplusplus */ 165 | 166 | #endif /* MININI_H */ 167 | -------------------------------------------------------------------------------- /overlay/Makefile: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------------- 2 | .SUFFIXES: 3 | #--------------------------------------------------------------------------------- 4 | 5 | ifeq ($(strip $(DEVKITPRO)),) 6 | $(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") 7 | endif 8 | 9 | TOPDIR ?= $(CURDIR) 10 | include $(DEVKITPRO)/libnx/switch_rules 11 | 12 | #--------------------------------------------------------------------------------- 13 | # TARGET is the name of the output 14 | # BUILD is the directory where object files & intermediate files will be placed 15 | # SOURCES is a list of directories containing source code 16 | # DATA is a list of directories containing data files 17 | # INCLUDES is a list of directories containing header files 18 | # ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) 19 | # 20 | # NO_ICON: if set to anything, do not use icon. 21 | # NO_NACP: if set to anything, no .nacp file is generated. 22 | # APP_TITLE is the name of the app stored in the .nacp file (Optional) 23 | # APP_AUTHOR is the author of the app stored in the .nacp file (Optional) 24 | # APP_VERSION is the version of the app stored in the .nacp file (Optional) 25 | # APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) 26 | # ICON is the filename of the icon (.jpg), relative to the project folder. 27 | # If not set, it attempts to use one of the following (in this order): 28 | # - .jpg 29 | # - icon.jpg 30 | # - /default_icon.jpg 31 | # 32 | # CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder. 33 | # If not set, it attempts to use one of the following (in this order): 34 | # - .json 35 | # - config.json 36 | # If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead 37 | # of a homebrew executable (.nro). This is intended to be used for sysmodules. 38 | # NACP building is skipped as well. 39 | #--------------------------------------------------------------------------------- 40 | APP_TITLE := sys-patch 41 | APP_AUTHOR := TotalJustice 42 | APP_VERSION := $(VERSION_DIRTY) 43 | 44 | TARGET := sys-patch-overlay 45 | BUILD := build 46 | SOURCES := src ../common/minIni 47 | DATA := data 48 | INCLUDES := include ../common libtesla/include 49 | 50 | NO_ICON := 1 51 | 52 | #--------------------------------------------------------------------------------- 53 | # options for code generation 54 | #--------------------------------------------------------------------------------- 55 | ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE 56 | 57 | CFLAGS := -g -Wall -O2 -ffunction-sections \ 58 | $(ARCH) $(DEFINES) $(CUSTOM_DEFINES) 59 | 60 | CFLAGS += $(INCLUDE) -D__SWITCH__ 61 | 62 | CXXFLAGS := $(CFLAGS) -std=c++23 -fno-exceptions 63 | 64 | ASFLAGS := -g $(ARCH) 65 | LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) 66 | 67 | LIBS := -lnx 68 | 69 | #--------------------------------------------------------------------------------- 70 | # list of directories containing libraries, this must be the top level containing 71 | # include and lib 72 | #--------------------------------------------------------------------------------- 73 | LIBDIRS := $(PORTLIBS) $(LIBNX) 74 | 75 | 76 | #--------------------------------------------------------------------------------- 77 | # no real need to edit anything past this point unless you need to add additional 78 | # rules for different file extensions 79 | #--------------------------------------------------------------------------------- 80 | ifneq ($(BUILD),$(notdir $(CURDIR))) 81 | #--------------------------------------------------------------------------------- 82 | 83 | export OUTPUT := $(CURDIR)/$(TARGET) 84 | export TOPDIR := $(CURDIR) 85 | 86 | export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ 87 | $(foreach dir,$(DATA),$(CURDIR)/$(dir)) 88 | 89 | export DEPSDIR := $(CURDIR)/$(BUILD) 90 | 91 | CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) 92 | CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) 93 | SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) 94 | BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) 95 | 96 | #--------------------------------------------------------------------------------- 97 | # use CXX for linking C++ projects, CC for standard C 98 | #--------------------------------------------------------------------------------- 99 | ifeq ($(strip $(CPPFILES)),) 100 | #--------------------------------------------------------------------------------- 101 | export LD := $(CC) 102 | #--------------------------------------------------------------------------------- 103 | else 104 | #--------------------------------------------------------------------------------- 105 | export LD := $(CXX) 106 | #--------------------------------------------------------------------------------- 107 | endif 108 | #--------------------------------------------------------------------------------- 109 | 110 | export OFILES_BIN := $(addsuffix .o,$(BINFILES)) 111 | export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) 112 | export OFILES := $(OFILES_BIN) $(OFILES_SRC) 113 | export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) 114 | 115 | export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ 116 | $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ 117 | -I$(CURDIR)/$(BUILD) 118 | 119 | export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) 120 | 121 | ifeq ($(strip $(CONFIG_JSON)),) 122 | jsons := $(wildcard *.json) 123 | ifneq (,$(findstring $(TARGET).json,$(jsons))) 124 | export APP_JSON := $(TOPDIR)/$(TARGET).json 125 | else 126 | ifneq (,$(findstring config.json,$(jsons))) 127 | export APP_JSON := $(TOPDIR)/config.json 128 | endif 129 | endif 130 | else 131 | export APP_JSON := $(TOPDIR)/$(CONFIG_JSON) 132 | endif 133 | 134 | ifeq ($(strip $(ICON)),) 135 | icons := $(wildcard *.jpg) 136 | ifneq (,$(findstring $(TARGET).jpg,$(icons))) 137 | export APP_ICON := $(TOPDIR)/$(TARGET).jpg 138 | else 139 | ifneq (,$(findstring icon.jpg,$(icons))) 140 | export APP_ICON := $(TOPDIR)/icon.jpg 141 | endif 142 | endif 143 | else 144 | export APP_ICON := $(TOPDIR)/$(ICON) 145 | endif 146 | 147 | ifeq ($(strip $(NO_ICON)),) 148 | export NROFLAGS += --icon=$(APP_ICON) 149 | endif 150 | 151 | ifeq ($(strip $(NO_NACP)),) 152 | export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp 153 | endif 154 | 155 | ifneq ($(APP_TITLEID),) 156 | export NACPFLAGS += --titleid=$(APP_TITLEID) 157 | endif 158 | 159 | ifneq ($(ROMFS),) 160 | export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) 161 | endif 162 | 163 | .PHONY: $(BUILD) clean all 164 | 165 | #--------------------------------------------------------------------------------- 166 | all: $(BUILD) 167 | 168 | 169 | $(BUILD): 170 | @[ -d $@ ] || mkdir -p $@ 171 | @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile 172 | 173 | @mkdir -p out/switch/.overlays 174 | @cp $(CURDIR)/$(TARGET).ovl out/switch/.overlays/ 175 | 176 | #--------------------------------------------------------------------------------- 177 | clean: 178 | @echo clean ... 179 | @rm -fr $(BUILD) $(TARGET).ovl $(TARGET).nro $(TARGET).nacp $(TARGET).elf 180 | @rm -rf out/ 181 | @rm -f sys-patch-overlay.zip 182 | 183 | #--------------------------------------------------------------------------------- 184 | ftp: all 185 | @echo making dist ... 186 | curl -T sys-patch-overlay.ovl ftp://192.168.200.71:5000/switch/.overlays/ --user tj:12345678 187 | 188 | #--------------------------------------------------------------------------------- 189 | dist: all 190 | @echo making dist ... 191 | 192 | @rm -f sys-patch-overlay.zip 193 | @cd out; zip -r ../sys-patch-overlay.zip ./*; cd ../ 194 | #--------------------------------------------------------------------------------- 195 | else 196 | .PHONY: all 197 | 198 | DEPENDS := $(OFILES:.o=.d) 199 | 200 | #--------------------------------------------------------------------------------- 201 | # main targets 202 | #--------------------------------------------------------------------------------- 203 | all : $(OUTPUT).ovl 204 | 205 | $(OUTPUT).ovl : $(OUTPUT).elf $(OUTPUT).nacp 206 | @elf2nro $< $@ $(NROFLAGS) 207 | @echo "built ... $(notdir $(OUTPUT).ovl)" 208 | 209 | $(OUTPUT).elf : $(OFILES) 210 | 211 | $(OFILES_SRC) : $(HFILES_BIN) 212 | 213 | #--------------------------------------------------------------------------------- 214 | # you need a rule like this for each extension you use as binary data 215 | #--------------------------------------------------------------------------------- 216 | %.bin.o %_bin.h : %.bin 217 | #--------------------------------------------------------------------------------- 218 | @echo $(notdir $<) 219 | @$(bin2o) 220 | 221 | -include $(DEPENDS) 222 | 223 | #--------------------------------------------------------------------------------------- 224 | endif 225 | #--------------------------------------------------------------------------------------- 226 | -------------------------------------------------------------------------------- /sysmod/Makefile: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------------- 2 | .SUFFIXES: 3 | #--------------------------------------------------------------------------------- 4 | 5 | ifeq ($(strip $(DEVKITPRO)),) 6 | $(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") 7 | endif 8 | 9 | TOPDIR ?= $(CURDIR) 10 | include $(DEVKITPRO)/libnx/switch_rules 11 | 12 | #--------------------------------------------------------------------------------- 13 | # TARGET is the name of the output 14 | # BUILD is the directory where object files & intermediate files will be placed 15 | # SOURCES is a list of directories containing source code 16 | # DATA is a list of directories containing data files 17 | # INCLUDES is a list of directories containing header files 18 | # ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) 19 | # 20 | # NO_ICON: if set to anything, do not use icon. 21 | # NO_NACP: if set to anything, no .nacp file is generated. 22 | # APP_TITLE is the name of the app stored in the .nacp file (Optional) 23 | # APP_AUTHOR is the author of the app stored in the .nacp file (Optional) 24 | # APP_VERSION is the version of the app stored in the .nacp file (Optional) 25 | # APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) 26 | # ICON is the filename of the icon (.jpg), relative to the project folder. 27 | # If not set, it attempts to use one of the following (in this order): 28 | # - .jpg 29 | # - icon.jpg 30 | # - /default_icon.jpg 31 | # 32 | # CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder. 33 | # If not set, it attempts to use one of the following (in this order): 34 | # - .json 35 | # - config.json 36 | # If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead 37 | # of a homebrew executable (.nro). This is intended to be used for sysmodules. 38 | # NACP building is skipped as well. 39 | #--------------------------------------------------------------------------------- 40 | TARGET := sys-patch 41 | BUILD := build 42 | SOURCES := src ../common/minIni 43 | DATA := data 44 | INCLUDES := include ../common 45 | #ROMFS := romfs 46 | 47 | # sys-patch 48 | #--------------------------------------------------------------------------------- 49 | # options for code generation 50 | #--------------------------------------------------------------------------------- 51 | ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE 52 | 53 | CFLAGS := -g -Wall -O2 -ffunction-sections \ 54 | $(ARCH) $(DEFINES) $(CUSTOM_DEFINES) 55 | 56 | CFLAGS += $(INCLUDE) -D__SWITCH__ 57 | 58 | CXXFLAGS := $(CFLAGS) -std=c++23 -fno-rtti -fno-exceptions 59 | 60 | ASFLAGS := -g $(ARCH) 61 | LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) 62 | 63 | LIBS := -lnx 64 | 65 | #--------------------------------------------------------------------------------- 66 | # list of directories containing libraries, this must be the top level containing 67 | # include and lib 68 | #--------------------------------------------------------------------------------- 69 | LIBDIRS := $(PORTLIBS) $(LIBNX) 70 | 71 | 72 | #--------------------------------------------------------------------------------- 73 | # no real need to edit anything past this point unless you need to add additional 74 | # rules for different file extensions 75 | #--------------------------------------------------------------------------------- 76 | ifneq ($(BUILD),$(notdir $(CURDIR))) 77 | #--------------------------------------------------------------------------------- 78 | 79 | export OUTPUT := $(CURDIR)/$(TARGET) 80 | export TOPDIR := $(CURDIR) 81 | 82 | export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ 83 | $(foreach dir,$(DATA),$(CURDIR)/$(dir)) 84 | 85 | export DEPSDIR := $(CURDIR)/$(BUILD) 86 | 87 | CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) 88 | CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) 89 | SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) 90 | BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) 91 | 92 | #--------------------------------------------------------------------------------- 93 | # use CXX for linking C++ projects, CC for standard C 94 | #--------------------------------------------------------------------------------- 95 | ifeq ($(strip $(CPPFILES)),) 96 | #--------------------------------------------------------------------------------- 97 | export LD := $(CC) 98 | #--------------------------------------------------------------------------------- 99 | else 100 | #--------------------------------------------------------------------------------- 101 | export LD := $(CXX) 102 | #--------------------------------------------------------------------------------- 103 | endif 104 | #--------------------------------------------------------------------------------- 105 | 106 | export OFILES_BIN := $(addsuffix .o,$(BINFILES)) 107 | export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) 108 | export OFILES := $(OFILES_BIN) $(OFILES_SRC) 109 | export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) 110 | 111 | export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ 112 | $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ 113 | -I$(CURDIR)/$(BUILD) 114 | 115 | export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) 116 | 117 | ifeq ($(strip $(CONFIG_JSON)),) 118 | jsons := $(wildcard *.json) 119 | ifneq (,$(findstring $(TARGET).json,$(jsons))) 120 | export APP_JSON := $(TOPDIR)/$(TARGET).json 121 | else 122 | ifneq (,$(findstring config.json,$(jsons))) 123 | export APP_JSON := $(TOPDIR)/config.json 124 | endif 125 | endif 126 | else 127 | export APP_JSON := $(TOPDIR)/$(CONFIG_JSON) 128 | endif 129 | 130 | ifeq ($(strip $(ICON)),) 131 | icons := $(wildcard *.jpg) 132 | ifneq (,$(findstring $(TARGET).jpg,$(icons))) 133 | export APP_ICON := $(TOPDIR)/$(TARGET).jpg 134 | else 135 | ifneq (,$(findstring icon.jpg,$(icons))) 136 | export APP_ICON := $(TOPDIR)/icon.jpg 137 | endif 138 | endif 139 | else 140 | export APP_ICON := $(TOPDIR)/$(ICON) 141 | endif 142 | 143 | ifeq ($(strip $(NO_ICON)),) 144 | export NROFLAGS += --icon=$(APP_ICON) 145 | endif 146 | 147 | ifeq ($(strip $(NO_NACP)),) 148 | export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp 149 | endif 150 | 151 | ifneq ($(APP_TITLEID),) 152 | export NACPFLAGS += --titleid=$(APP_TITLEID) 153 | endif 154 | 155 | ifneq ($(ROMFS),) 156 | export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) 157 | endif 158 | 159 | .PHONY: $(BUILD) clean all 160 | 161 | #--------------------------------------------------------------------------------- 162 | all: $(BUILD) 163 | 164 | $(BUILD): 165 | @[ -d $@ ] || mkdir -p $@ 166 | @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile 167 | 168 | @rm -rf out/ 169 | @mkdir -p out/atmosphere/contents/420000000000000B/flags 170 | @touch out/atmosphere/contents/420000000000000B/flags/boot2.flag 171 | @cp $(CURDIR)/toolbox.json out/atmosphere/contents/420000000000000B/toolbox.json 172 | @cp $(CURDIR)/$(TARGET).nsp out/atmosphere/contents/420000000000000B/exefs.nsp 173 | 174 | #--------------------------------------------------------------------------------- 175 | clean: 176 | @echo clean ... 177 | ifeq ($(strip $(APP_JSON)),) 178 | @rm -fr $(BUILD) $(TARGET).nro $(TARGET).nacp $(TARGET).elf 179 | else 180 | @rm -fr $(BUILD) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf 181 | endif 182 | @rm -rf out/ 183 | @rm -f sys-patch.zip 184 | 185 | #--------------------------------------------------------------------------------- 186 | dist: all 187 | @echo making dist ... 188 | 189 | @rm -f sys-patch-no-overlay.zip 190 | @cd out; zip -r ../sys-patch-no-overlay.zip ./*; cd ../ 191 | #--------------------------------------------------------------------------------- 192 | else 193 | .PHONY: all 194 | 195 | DEPENDS := $(OFILES:.o=.d) 196 | 197 | #--------------------------------------------------------------------------------- 198 | # main targets 199 | #--------------------------------------------------------------------------------- 200 | ifeq ($(strip $(APP_JSON)),) 201 | 202 | all : $(OUTPUT).nro 203 | 204 | ifeq ($(strip $(NO_NACP)),) 205 | $(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp 206 | else 207 | $(OUTPUT).nro : $(OUTPUT).elf 208 | endif 209 | 210 | else 211 | 212 | all : $(OUTPUT).nsp 213 | 214 | $(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm 215 | 216 | $(OUTPUT).nso : $(OUTPUT).elf 217 | 218 | endif 219 | 220 | $(OUTPUT).elf : $(OFILES) 221 | 222 | $(OFILES_SRC) : $(HFILES_BIN) 223 | 224 | #--------------------------------------------------------------------------------- 225 | # you need a rule like this for each extension you use as binary data 226 | #--------------------------------------------------------------------------------- 227 | %.bin.o %_bin.h : %.bin 228 | #--------------------------------------------------------------------------------- 229 | @echo $(notdir $<) 230 | @$(bin2o) 231 | 232 | -include $(DEPENDS) 233 | 234 | #--------------------------------------------------------------------------------------- 235 | endif 236 | #--------------------------------------------------------------------------------------- 237 | -------------------------------------------------------------------------------- /overlay/src/main.cpp: -------------------------------------------------------------------------------- 1 | #define TESLA_INIT_IMPL // If you have more than one file using the tesla header, only define this in the main one 2 | #define STBTT_STATIC 3 | #include // The Tesla Header 4 | #include 5 | #include "minIni/minIni.h" 6 | 7 | namespace { 8 | 9 | constexpr auto CONFIG_PATH = "/config/sys-patch/config.ini"; 10 | constexpr auto LOG_PATH = "/config/sys-patch/log.ini"; 11 | 12 | auto does_file_exist(const char* path) -> bool { 13 | Result rc{}; 14 | FsFileSystem fs{}; 15 | FsFile file{}; 16 | char path_buf[FS_MAX_PATH]{}; 17 | 18 | if (R_FAILED(fsOpenSdCardFileSystem(&fs))) { 19 | return false; 20 | } 21 | 22 | strcpy(path_buf, path); 23 | rc = fsFsOpenFile(&fs, path_buf, FsOpenMode_Read, &file); 24 | fsFileClose(&file); 25 | fsFsClose(&fs); 26 | return R_SUCCEEDED(rc); 27 | } 28 | 29 | // creates a directory, non-recursive! 30 | auto create_dir(const char* path) -> bool { 31 | Result rc{}; 32 | FsFileSystem fs{}; 33 | char path_buf[FS_MAX_PATH]{}; 34 | 35 | if (R_FAILED(fsOpenSdCardFileSystem(&fs))) { 36 | return false; 37 | } 38 | 39 | strcpy(path_buf, path); 40 | rc = fsFsCreateDirectory(&fs, path_buf); 41 | fsFsClose(&fs); 42 | return R_SUCCEEDED(rc); 43 | } 44 | 45 | struct ConfigEntry { 46 | ConfigEntry(const char* _section, const char* _key, bool default_value) : 47 | section{_section}, key{_key}, value{default_value} { 48 | this->load_value_from_ini(); 49 | } 50 | 51 | void load_value_from_ini() { 52 | this->value = ini_getbool(this->section, this->key, this->value, CONFIG_PATH); 53 | } 54 | 55 | auto create_list_item(const char* text) { 56 | auto item = new tsl::elm::ToggleListItem(text, value); 57 | item->setStateChangedListener([this](bool new_value){ 58 | this->value = new_value; 59 | ini_putl(this->section, this->key, this->value, CONFIG_PATH); 60 | }); 61 | return item; 62 | } 63 | 64 | const char* const section; 65 | const char* const key; 66 | bool value; 67 | }; 68 | 69 | class GuiOptions final : public tsl::Gui { 70 | public: 71 | GuiOptions() { } 72 | 73 | tsl::elm::Element* createUI() override { 74 | auto frame = new tsl::elm::OverlayFrame("sys-patch", VERSION_WITH_HASH); 75 | auto list = new tsl::elm::List(); 76 | 77 | list->addItem(new tsl::elm::CategoryHeader("Options")); 78 | list->addItem(config_patch_sysmmc.create_list_item("Patch sysMMC")); 79 | list->addItem(config_patch_emummc.create_list_item("Patch emuMMC")); 80 | list->addItem(config_logging.create_list_item("Logging")); 81 | list->addItem(config_version_skip.create_list_item("Version skip")); 82 | 83 | frame->setContent(list); 84 | return frame; 85 | } 86 | 87 | ConfigEntry config_patch_sysmmc{"options", "patch_sysmmc", true}; 88 | ConfigEntry config_patch_emummc{"options", "patch_emummc", true}; 89 | ConfigEntry config_logging{"options", "enable_logging", true}; 90 | ConfigEntry config_version_skip{"options", "version_skip", true}; 91 | }; 92 | 93 | class GuiToggle final : public tsl::Gui { 94 | public: 95 | GuiToggle() { } 96 | 97 | tsl::elm::Element* createUI() override { 98 | auto frame = new tsl::elm::OverlayFrame("sys-patch", VERSION_WITH_HASH); 99 | auto list = new tsl::elm::List(); 100 | 101 | list->addItem(new tsl::elm::CategoryHeader("FS - 0100000000000000")); 102 | list->addItem(config_noacidsigchk1.create_list_item("noacidsigchk1")); 103 | list->addItem(config_noacidsigchk2.create_list_item("noacidsigchk2")); 104 | list->addItem(config_noncasigchk_old.create_list_item("noncasigchk_old")); 105 | list->addItem(config_noncasigchk_new.create_list_item("noncasigchk_new")); 106 | list->addItem(config_nocntchk_old.create_list_item("nocntchk_old")); 107 | list->addItem(config_nocntchk_new.create_list_item("nocntchk_new")); 108 | 109 | list->addItem(new tsl::elm::CategoryHeader("LDR - 0100000000000001")); 110 | list->addItem(config_noacidsigchk.create_list_item("noacidsigchk")); 111 | 112 | list->addItem(new tsl::elm::CategoryHeader("ES - 0100000000000033")); 113 | list->addItem(config_es1.create_list_item("es1")); 114 | list->addItem(config_es2.create_list_item("es2")); 115 | list->addItem(config_es3.create_list_item("es3")); 116 | list->addItem(config_es4.create_list_item("es4")); 117 | list->addItem(config_es5.create_list_item("es5")); 118 | list->addItem(config_es6.create_list_item("es6")); 119 | 120 | list->addItem(new tsl::elm::CategoryHeader("NIFM - 010000000000000F")); 121 | list->addItem(config_ctest.create_list_item("ctest")); 122 | 123 | frame->setContent(list); 124 | return frame; 125 | } 126 | 127 | ConfigEntry config_noacidsigchk1{"fs", "noacidsigchk1", true}; 128 | ConfigEntry config_noacidsigchk2{"fs", "noacidsigchk2", true}; 129 | ConfigEntry config_noncasigchk_old{"fs", "noncasigchk_old", true}; 130 | ConfigEntry config_noncasigchk_new{"fs", "noncasigchk_new", true}; 131 | ConfigEntry config_nocntchk_old{"fs", "nocntchk_old", true}; 132 | ConfigEntry config_nocntchk_new{"fs", "nocntchk_new", true}; 133 | ConfigEntry config_noacidsigchk{"ldr", "noacidsigchk", true}; 134 | ConfigEntry config_es1{"es", "es1", true}; 135 | ConfigEntry config_es2{"es", "es2", true}; 136 | ConfigEntry config_es3{"es", "es3", true}; 137 | ConfigEntry config_es4{"es", "es4", true}; 138 | ConfigEntry config_es5{"es", "es5", true}; 139 | ConfigEntry config_es6{"es", "es6", true}; 140 | ConfigEntry config_ctest{"nifm", "ctest", false}; 141 | }; 142 | 143 | class GuiLog final : public tsl::Gui { 144 | public: 145 | GuiLog() { } 146 | 147 | tsl::elm::Element* createUI() override { 148 | auto frame = new tsl::elm::OverlayFrame("sys-patch", VERSION_WITH_HASH); 149 | auto list = new tsl::elm::List(); 150 | 151 | if (does_file_exist(LOG_PATH)) { 152 | struct CallbackUser { 153 | tsl::elm::List* list; 154 | std::string last_section; 155 | } callback_userdata{list}; 156 | 157 | ini_browse([](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData){ 158 | auto user = (CallbackUser*)UserData; 159 | std::string_view value{Value}; 160 | 161 | if (value == "Skipped") { 162 | return 1; 163 | } 164 | 165 | if (user->last_section != Section) { 166 | user->last_section = Section; 167 | user->list->addItem(new tsl::elm::CategoryHeader("Log: " + user->last_section)); 168 | } 169 | 170 | #define F(x) ((x) >> 4) // 8bit -> 4bit 171 | constexpr tsl::Color colour_syspatch{F(0), F(255), F(200), F(255)}; 172 | constexpr tsl::Color colour_file{F(255), F(177), F(66), F(255)}; 173 | constexpr tsl::Color colour_unpatched{F(250), F(90), F(58), F(255)}; 174 | #undef F 175 | 176 | if (value.starts_with("Patched")) { 177 | if (value.ends_with("(sys-patch)")) { 178 | user->list->addItem(new tsl::elm::ListItem(Key, "Patched", colour_syspatch)); 179 | } else { 180 | user->list->addItem(new tsl::elm::ListItem(Key, "Patched", colour_file)); 181 | } 182 | } else if (value.starts_with("Unpatched") || value.starts_with("Disabled")) { 183 | user->list->addItem(new tsl::elm::ListItem(Key, Value, colour_unpatched)); 184 | } else if (user->last_section == "stats") { 185 | user->list->addItem(new tsl::elm::ListItem(Key, Value, tsl::style::color::ColorDescription)); 186 | } else { 187 | user->list->addItem(new tsl::elm::ListItem(Key, Value, tsl::style::color::ColorText)); 188 | } 189 | 190 | return 1; 191 | }, &callback_userdata, LOG_PATH); 192 | } else { 193 | list->addItem(new tsl::elm::ListItem("No log found!")); 194 | } 195 | 196 | frame->setContent(list); 197 | return frame; 198 | } 199 | }; 200 | 201 | class GuiMain final : public tsl::Gui { 202 | public: 203 | GuiMain() { } 204 | 205 | tsl::elm::Element* createUI() override { 206 | auto frame = new tsl::elm::OverlayFrame("sys-patch", VERSION_WITH_HASH); 207 | auto list = new tsl::elm::List(); 208 | 209 | auto options = new tsl::elm::ListItem("Options"); 210 | auto toggle = new tsl::elm::ListItem("Toggle patches"); 211 | auto log = new tsl::elm::ListItem("Log"); 212 | 213 | options->setClickListener([](u64 keys) -> bool { 214 | if (keys & HidNpadButton_A) { 215 | tsl::changeTo(); 216 | return true; 217 | } 218 | return false; 219 | }); 220 | 221 | toggle->setClickListener([](u64 keys) -> bool { 222 | if (keys & HidNpadButton_A) { 223 | tsl::changeTo(); 224 | return true; 225 | } 226 | return false; 227 | }); 228 | 229 | log->setClickListener([](u64 keys) -> bool { 230 | if (keys & HidNpadButton_A) { 231 | tsl::changeTo(); 232 | return true; 233 | } 234 | return false; 235 | }); 236 | 237 | list->addItem(new tsl::elm::CategoryHeader("Menu")); 238 | list->addItem(options); 239 | list->addItem(toggle); 240 | list->addItem(log); 241 | 242 | frame->setContent(list); 243 | return frame; 244 | } 245 | }; 246 | 247 | // libtesla already initialized fs, hid, pl, pmdmnt, hid:sys and set:sys 248 | class SysPatchOverlay final : public tsl::Overlay { 249 | public: 250 | std::unique_ptr loadInitialGui() override { 251 | return initially(); 252 | } 253 | }; 254 | 255 | } // namespace 256 | 257 | int main(int argc, char **argv) { 258 | create_dir("/config/"); 259 | create_dir("/config/sys-patch/"); 260 | return tsl::loop(argc, argv); 261 | } 262 | -------------------------------------------------------------------------------- /sysmod/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include // for std::min 4 | #include // for std::byteswap 5 | #include // std::unreachable 6 | #include 7 | #include "minIni/minIni.h" 8 | 9 | namespace { 10 | 11 | constexpr u64 INNER_HEAP_SIZE = 0x1000; // Size of the inner heap (adjust as necessary). 12 | constexpr u64 READ_BUFFER_SIZE = 0x1000; // size of static buffer which memory is read into 13 | constexpr u32 FW_VER_ANY = 0x0; 14 | constexpr u16 REGEX_SKIP = 0x100; 15 | 16 | u32 FW_VERSION{}; // set on startup 17 | u32 AMS_VERSION{}; // set on startup 18 | u32 AMS_TARGET_VERSION{}; // set on startup 19 | u8 AMS_KEYGEN{}; // set on startup 20 | u64 AMS_HASH{}; // set on startup 21 | bool VERSION_SKIP{}; // set on startup 22 | 23 | struct DebugEventInfo { 24 | u32 event_type; 25 | u32 flags; 26 | u64 thread_id; 27 | u64 title_id; 28 | u64 process_id; 29 | char process_name[12]; 30 | u32 mmu_flags; 31 | u8 _0x30[0x10]; 32 | }; 33 | 34 | template 35 | constexpr void str2hex(const char* s, T* data, u8& size) { 36 | // skip leading 0x (if any) 37 | if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { 38 | s += 2; 39 | } 40 | 41 | // invalid string will cause a compile-time error due to no return 42 | constexpr auto hexstr_2_nibble = [](char c) -> u8 { 43 | if (c >= 'A' && c <= 'F') { return c - 'A' + 10; } 44 | if (c >= 'a' && c <= 'f') { return c - 'a' + 10; } 45 | if (c >= '0' && c <= '9') { return c - '0'; } 46 | }; 47 | 48 | // parse and convert string 49 | while (*s != '\0') { 50 | if (sizeof(T) == sizeof(u16) && *s == '.') { 51 | data[size] = REGEX_SKIP; 52 | s++; 53 | } else { 54 | data[size] |= hexstr_2_nibble(*s++) << 4; 55 | data[size] |= hexstr_2_nibble(*s++) << 0; 56 | } 57 | size++; 58 | } 59 | } 60 | 61 | struct PatternData { 62 | constexpr PatternData(const char* s) { 63 | str2hex(s, data, size); 64 | } 65 | 66 | u16 data[44]{}; // reasonable max pattern length, adjust as needed 67 | u8 size{}; 68 | }; 69 | 70 | struct PatchData { 71 | constexpr PatchData(const char* s) { 72 | str2hex(s, data, size); 73 | } 74 | 75 | template 76 | constexpr PatchData(T v) { 77 | for (u32 i = 0; i < sizeof(T); i++) { 78 | data[size++] = v & 0xFF; 79 | v >>= 8; 80 | } 81 | } 82 | 83 | auto cmp(const void* _data) -> bool { 84 | return !std::memcmp(data, _data, size); 85 | } 86 | 87 | u8 data[20]{}; // reasonable max patch length, adjust as needed 88 | u8 size{}; 89 | }; 90 | 91 | enum class PatchResult { 92 | NOT_FOUND, 93 | SKIPPED, 94 | DISABLED, 95 | PATCHED_FILE, 96 | PATCHED_SYSPATCH, 97 | FAILED_WRITE, 98 | }; 99 | 100 | struct Patterns { 101 | const char* patch_name; // name of patch 102 | const PatternData byte_pattern; // the pattern to search 103 | 104 | const s32 inst_offset; // instruction offset relative to byte pattern 105 | const s32 patch_offset; // patch offset relative to inst_offset 106 | 107 | bool (*const cond)(u32 inst); // check condition of the instruction 108 | PatchData (*const patch)(u32 inst); // the patch data to be applied 109 | bool (*const applied)(const u8* data, u32 inst); // check to see if patch already applied 110 | 111 | bool enabled; // controlled by config.ini 112 | 113 | const u32 min_fw_ver{FW_VER_ANY}; // set to FW_VER_ANY to ignore 114 | const u32 max_fw_ver{FW_VER_ANY}; // set to FW_VER_ANY to ignore 115 | const u32 min_ams_ver{FW_VER_ANY}; // set to FW_VER_ANY to ignore 116 | const u32 max_ams_ver{FW_VER_ANY}; // set to FW_VER_ANY to ignore 117 | 118 | PatchResult result{PatchResult::NOT_FOUND}; 119 | }; 120 | 121 | struct PatchEntry { 122 | const char* name; // name of the system title 123 | const u64 title_id; // title id of the system title 124 | const std::span patterns; // list of patterns to find 125 | const u32 min_fw_ver{FW_VER_ANY}; // set to FW_VER_ANY to ignore 126 | const u32 max_fw_ver{FW_VER_ANY}; // set to FW_VER_ANY to ignore 127 | }; 128 | 129 | constexpr auto subi_cond(u32 inst) -> bool { 130 | // # Used on Atmosphère-NX 0.11.0 - 0.12.0. 131 | const auto type = (inst >> 24) & 0xFF; 132 | const auto imm = (inst >> 10) & 0xFFF; 133 | return (type == 0x71) && (imm == 0x0A); 134 | } 135 | 136 | constexpr auto subr_cond(u32 inst) -> bool { 137 | // # Used on Atmosphère-NX 0.13.0 and later. 138 | const auto type = (inst >> 21) & 0x7F9; 139 | const auto reg = (inst >> 16) & 0x1F; 140 | return (type == 0x358) && (reg == 0x01); 141 | } 142 | 143 | constexpr auto bl_cond(u32 inst) -> bool { 144 | return ((inst >> 26) & 0x3F) == 0x25; 145 | } 146 | 147 | constexpr auto tbz_cond(u32 inst) -> bool { 148 | return ((inst >> 24) & 0x7F) == 0x36; 149 | } 150 | 151 | constexpr auto subs_cond(u32 inst) -> bool { 152 | return subi_cond(inst) || subr_cond(inst); 153 | } 154 | 155 | constexpr auto cbz_cond(u32 inst) -> bool { 156 | const auto type = inst >> 24; 157 | return type == 0x34 || type == 0xB4; 158 | } 159 | 160 | constexpr auto mov_cond(u32 inst) -> bool { 161 | return ((inst >> 24) & 0x7F) == 0x52; 162 | } 163 | 164 | constexpr auto mov2_cond(u32 inst) -> bool { 165 | if (hosversionBefore(15,0,0)) { 166 | return (inst >> 24) == 0x92; // and x0, x19, #0xffffffff 167 | } else { 168 | return (inst >> 24) == 0x2A; // mov x0, x20 169 | } 170 | } 171 | 172 | constexpr auto bne_cond(u32 inst) -> bool { 173 | const auto type = inst >> 24; 174 | const auto cond = inst & 0x10; 175 | return type == 0x54 || cond == 0x0; 176 | } 177 | 178 | constexpr auto ctest_cond(u32 inst) -> bool { 179 | return std::byteswap(0xF50301AA) == inst; // mov x21, x1 180 | } 181 | 182 | // to view patches, use https://armconverter.com/?lock=arm64 183 | constexpr PatchData ret0_patch_data{ "0xE0031F2A" }; 184 | constexpr PatchData nop_patch_data{ "0x1F2003D5" }; 185 | constexpr PatchData mov0_patch_data{ "0xE0031FAA" }; 186 | constexpr PatchData ctest_patch_data{ "0x00309AD2001EA1F2610100D4E0031FAAC0035FD6" }; 187 | 188 | constexpr auto ret0_patch(u32 inst) -> PatchData { return ret0_patch_data; } 189 | constexpr auto nop_patch(u32 inst) -> PatchData { return nop_patch_data; } 190 | constexpr auto subs_patch(u32 inst) -> PatchData { return subi_cond(inst) ? (u8)0x1 : (u8)0x0; } 191 | constexpr auto mov0_patch(u32 inst) -> PatchData { return mov0_patch_data; } 192 | constexpr auto ctest_patch(u32 inst) -> PatchData { return ctest_patch_data; } 193 | 194 | constexpr auto b_patch(u32 inst) -> PatchData { 195 | const u32 opcode = 0x14 << 24; 196 | const u32 offset = (inst >> 5) & 0x7FFFF; 197 | return opcode | offset; 198 | } 199 | 200 | constexpr auto ret0_applied(const u8* data, u32 inst) -> bool { 201 | return ret0_patch(inst).cmp(data); 202 | } 203 | 204 | constexpr auto nop_applied(const u8* data, u32 inst) -> bool { 205 | return nop_patch(inst).cmp(data); 206 | } 207 | 208 | constexpr auto subs_applied(const u8* data, u32 inst) -> bool { 209 | const auto type_i = (inst >> 24) & 0xFF; 210 | const auto imm = (inst >> 10) & 0xFFF; 211 | const auto type_r = (inst >> 21) & 0x7F9; 212 | const auto reg = (inst >> 16) & 0x1F; 213 | return ((type_i == 0x71) && (imm == 0x1)) || ((type_r == 0x358) && (reg == 0x0)); 214 | } 215 | 216 | constexpr auto b_applied(const u8* data, u32 inst) -> bool { 217 | return 0x14 == (inst >> 24); 218 | } 219 | 220 | constexpr auto mov0_applied(const u8* data, u32 inst) -> bool { 221 | return mov0_patch(inst).cmp(data); 222 | } 223 | 224 | constexpr auto ctest_applied(const u8* data, u32 inst) -> bool { 225 | return ctest_patch(inst).cmp(data); 226 | } 227 | 228 | constinit Patterns fs_patterns[] = { 229 | { "noacidsigchk1", "0xC8FE4739", -24, 0, bl_cond, ret0_patch, ret0_applied, true, FW_VER_ANY, MAKEHOSVERSION(9,2,0) }, 230 | { "noacidsigchk2", "0x0210911F000072", -5, 0, bl_cond, ret0_patch, ret0_applied, true, FW_VER_ANY, MAKEHOSVERSION(9,2,0) }, 231 | { "noncasigchk_old", "0x1E42B9", -5, 0, tbz_cond, nop_patch, nop_applied, true, MAKEHOSVERSION(10,0,0), MAKEHOSVERSION(14,2,1) }, 232 | { "noncasigchk_new", "0x3E4479", -5, 0, tbz_cond, nop_patch, nop_applied, true, MAKEHOSVERSION(15,0,0) }, 233 | { "nocntchk_old", "0x081C00121F05007181000054", -4, 0, bl_cond, ret0_patch, ret0_applied, true, MAKEHOSVERSION(10,0,0), MAKEHOSVERSION(14,2,1) }, 234 | { "nocntchk_new", "0x081C00121F05007141010054", -4, 0, bl_cond, ret0_patch, ret0_applied, true, MAKEHOSVERSION(15,0,0) }, 235 | }; 236 | 237 | constinit Patterns ldr_patterns[] = { 238 | { "noacidsigchk", "0xFD7BC6A8C0035FD6", 16, 2, subs_cond, subs_patch, subs_applied, true }, 239 | }; 240 | 241 | constinit Patterns es_patterns[] = { 242 | { "es1", "0x1F90013128928052", -4, 0, cbz_cond, b_patch, b_applied, true, FW_VER_ANY, MAKEHOSVERSION(13,2,1) }, 243 | { "es2", "0xC07240F9E1930091", -4, 0, tbz_cond, nop_patch, nop_applied, true, FW_VER_ANY, MAKEHOSVERSION(10,2,0) }, 244 | { "es3", "0xF3031FAA02000014", -4, 0, bne_cond, nop_patch, nop_applied, true, FW_VER_ANY, MAKEHOSVERSION(10,2,0) }, 245 | { "es4", "0xC0FDFF35A8C35838", -4, 0, mov_cond, nop_patch, nop_applied, true, MAKEHOSVERSION(11,0,0), MAKEHOSVERSION(13,2,1) }, 246 | { "es5", "0xE023009145EEFF97", -4, 0, cbz_cond, b_patch, b_applied, true, MAKEHOSVERSION(11,0,0), MAKEHOSVERSION(13,2,1) }, 247 | { "es6", "0x.6300...0094A0..D1..FF97", 16, 0, mov2_cond, mov0_patch, mov0_applied, true, MAKEHOSVERSION(14,0,0) }, 248 | }; 249 | 250 | constinit Patterns nifm_patterns[] = { 251 | { "ctest", "....................F40300AA....F30314AAE00314AA9F0201397F8E04F8", 16, -16, ctest_cond, ctest_patch, ctest_applied, true }, 252 | }; 253 | 254 | // NOTE: add system titles that you want to be patched to this table. 255 | // a list of system titles can be found here https://switchbrew.org/wiki/Title_list 256 | constinit PatchEntry patches[] = { 257 | { "fs", 0x0100000000000000, fs_patterns }, 258 | // ldr needs to be patched in fw 10+ 259 | { "ldr", 0x0100000000000001, ldr_patterns, MAKEHOSVERSION(10,0,0) }, 260 | // es was added in fw 2 261 | { "es", 0x0100000000000033, es_patterns, MAKEHOSVERSION(2,0,0) }, 262 | { "nifm", 0x010000000000000F, nifm_patterns }, 263 | }; 264 | 265 | struct EmummcPaths { 266 | char unk[0x80]; 267 | char nintendo[0x80]; 268 | }; 269 | 270 | void smcAmsGetEmunandConfig(EmummcPaths* out_paths) { 271 | SecmonArgs args{}; 272 | args.X[0] = 0xF0000404; /* smcAmsGetEmunandConfig */ 273 | args.X[1] = 0; /* EXO_EMUMMC_MMC_NAND*/ 274 | args.X[2] = (u64)out_paths; /* out path */ 275 | svcCallSecureMonitor(&args); 276 | } 277 | 278 | auto is_emummc() -> bool { 279 | EmummcPaths paths{}; 280 | smcAmsGetEmunandConfig(&paths); 281 | return (paths.unk[0] != '\0') || (paths.nintendo[0] != '\0'); 282 | } 283 | 284 | void patcher(Handle handle, std::span data, u64 addr, std::span patterns) { 285 | for (auto& p : patterns) { 286 | // skip if disabled (controller by config.ini) 287 | if (p.result == PatchResult::DISABLED) { 288 | continue; 289 | } 290 | 291 | // skip if version isn't valid 292 | if (VERSION_SKIP && 293 | ((p.min_fw_ver && p.min_fw_ver > FW_VERSION) || 294 | (p.max_fw_ver && p.max_fw_ver < FW_VERSION) || 295 | (p.min_ams_ver && p.min_ams_ver > AMS_VERSION) || 296 | (p.max_ams_ver && p.max_ams_ver < AMS_VERSION))) { 297 | p.result = PatchResult::SKIPPED; 298 | continue; 299 | } 300 | 301 | // skip if already patched 302 | if (p.result == PatchResult::PATCHED_FILE || p.result == PatchResult::PATCHED_SYSPATCH) { 303 | continue; 304 | } 305 | 306 | for (u32 i = 0; i < data.size(); i++) { 307 | if (i + p.byte_pattern.size >= data.size()) { 308 | break; 309 | } 310 | 311 | // loop through every byte of the pattern data to find a match 312 | // skipping over any bytes if the value is REGEX_SKIP 313 | u32 count{}; 314 | while (count < p.byte_pattern.size) { 315 | if (p.byte_pattern.data[count] != data[i + count] && p.byte_pattern.data[count] != REGEX_SKIP) { 316 | break; 317 | } 318 | count++; 319 | } 320 | 321 | // if we have found a matching pattern 322 | if (count == p.byte_pattern.size) { 323 | // fetch the instruction 324 | u32 inst{}; 325 | const auto inst_offset = i + p.inst_offset; 326 | std::memcpy(&inst, data.data() + inst_offset, sizeof(inst)); 327 | 328 | // check if the instruction is the one that we want 329 | if (p.cond(inst)) { 330 | const auto [patch_data, patch_size] = p.patch(inst); 331 | const auto patch_offset = addr + inst_offset + p.patch_offset; 332 | 333 | // todo: log failed writes, although this should in theory never fail 334 | if (R_FAILED(svcWriteDebugProcessMemory(handle, &patch_data, patch_offset, patch_size))) { 335 | p.result = PatchResult::FAILED_WRITE; 336 | } else { 337 | p.result = PatchResult::PATCHED_SYSPATCH; 338 | } 339 | // move onto next pattern 340 | break; 341 | } else if (p.applied(data.data() + inst_offset + p.patch_offset, inst)) { 342 | // patch already applied by sigpatches 343 | p.result = PatchResult::PATCHED_FILE; 344 | break; 345 | } 346 | } 347 | } 348 | } 349 | } 350 | 351 | auto apply_patch(PatchEntry& patch) -> bool { 352 | Handle handle{}; 353 | DebugEventInfo event_info{}; 354 | 355 | u64 pids[0x50]{}; 356 | s32 process_count{}; 357 | static u8 buffer[READ_BUFFER_SIZE]; 358 | 359 | // skip if version isn't valid 360 | if (VERSION_SKIP && 361 | ((patch.min_fw_ver && patch.min_fw_ver > FW_VERSION) || 362 | (patch.max_fw_ver && patch.max_fw_ver < FW_VERSION))) { 363 | for (auto& p : patch.patterns) { 364 | p.result = PatchResult::SKIPPED; 365 | } 366 | return true; 367 | } 368 | 369 | if (R_FAILED(svcGetProcessList(&process_count, pids, 0x50))) { 370 | return false; 371 | } 372 | 373 | for (s32 i = 0; i < (process_count - 1); i++) { 374 | if (R_SUCCEEDED(svcDebugActiveProcess(&handle, pids[i])) && 375 | R_SUCCEEDED(svcGetDebugEvent(&event_info, handle)) && 376 | patch.title_id == event_info.title_id) { 377 | MemoryInfo mem_info{}; 378 | u64 addr{}; 379 | u32 page_info{}; 380 | 381 | for (;;) { 382 | if (R_FAILED(svcQueryDebugProcessMemory(&mem_info, &page_info, handle, addr))) { 383 | break; 384 | } 385 | addr = mem_info.addr + mem_info.size; 386 | 387 | // if addr=0 then we hit the reserved memory section 388 | if (!addr) { 389 | break; 390 | } 391 | // skip memory that we don't want 392 | if (!mem_info.size || (mem_info.perm & Perm_Rx) != Perm_Rx || ((mem_info.type & 0xFF) != MemType_CodeStatic)) { 393 | continue; 394 | } 395 | 396 | // todo: the byte pattern can in between 2 READ_BUFFER_SIZE boundries! 397 | for (u64 sz = 0; sz < mem_info.size; sz += READ_BUFFER_SIZE) { 398 | const auto actual_size = std::min(READ_BUFFER_SIZE, mem_info.size); 399 | if (R_FAILED(svcReadDebugProcessMemory(buffer, handle, mem_info.addr + sz, actual_size))) { 400 | // todo: log failed reads! 401 | break; 402 | } else { 403 | patcher(handle, std::span{buffer, actual_size}, mem_info.addr + sz, patch.patterns); 404 | } 405 | } 406 | } 407 | svcCloseHandle(handle); 408 | return true; 409 | } else if (handle) { 410 | svcCloseHandle(handle); 411 | handle = 0; 412 | } 413 | } 414 | 415 | return false; 416 | } 417 | 418 | // creates a directory, non-recursive! 419 | auto create_dir(const char* path) -> bool { 420 | Result rc{}; 421 | FsFileSystem fs{}; 422 | char path_buf[FS_MAX_PATH]{}; 423 | 424 | if (R_FAILED(fsOpenSdCardFileSystem(&fs))) { 425 | return false; 426 | } 427 | 428 | strcpy(path_buf, path); 429 | rc = fsFsCreateDirectory(&fs, path_buf); 430 | fsFsClose(&fs); 431 | return R_SUCCEEDED(rc); 432 | } 433 | 434 | // same as ini_get but writes out the default value instead 435 | auto ini_load_or_write_default(const char* section, const char* key, long _default, const char* path) -> long { 436 | if (!ini_haskey(section, key, path)) { 437 | ini_putl(section, key, _default, path); 438 | return _default; 439 | } else { 440 | return ini_getbool(section, key, _default, path); 441 | } 442 | } 443 | 444 | auto patch_result_to_str(PatchResult result) -> const char* { 445 | switch (result) { 446 | case PatchResult::NOT_FOUND: return "Unpatched"; 447 | case PatchResult::SKIPPED: return "Skipped"; 448 | case PatchResult::DISABLED: return "Disabled"; 449 | case PatchResult::PATCHED_FILE: return "Patched (file)"; 450 | case PatchResult::PATCHED_SYSPATCH: return "Patched (sys-patch)"; 451 | case PatchResult::FAILED_WRITE: return "Failed (svcWriteDebugProcessMemory)"; 452 | } 453 | 454 | std::unreachable(); 455 | } 456 | 457 | void num_2_str(char*& s, u16 num) { 458 | u16 max_v = 1000; 459 | if (num > 9) { 460 | while (max_v >= 10) { 461 | if (num >= max_v) { 462 | while (max_v != 1) { 463 | *s++ = '0' + (num / max_v); 464 | num -= (num / max_v) * max_v; 465 | max_v /= 10; 466 | } 467 | } else { 468 | max_v /= 10; 469 | } 470 | } 471 | } 472 | *s++ = '0' + (num); // always add 0 or 1's 473 | } 474 | 475 | void ms_2_str(char* s, u32 num) { 476 | u32 max_v = 100; 477 | *s++ = '0' + (num / 1000); // add seconds 478 | num -= (num / 1000) * 1000; 479 | *s++ = '.'; 480 | 481 | while (max_v >= 10) { 482 | if (num >= max_v) { 483 | while (max_v != 1) { 484 | *s++ = '0' + (num / max_v); 485 | num -= (num / max_v) * max_v; 486 | max_v /= 10; 487 | } 488 | } 489 | else { 490 | *s++ = '0'; // append 0 491 | max_v /= 10; 492 | } 493 | } 494 | *s++ = '0' + (num); // always add 0 or 1's 495 | *s++ = 's'; // in seconds 496 | } 497 | 498 | // eg, 852481 -> 13.2.1 499 | void version_to_str(char* s, u32 ver) { 500 | for (int i = 0; i < 3; i++) { 501 | num_2_str(s, (ver >> 16) & 0xFF); 502 | if (i != 2) { 503 | *s++ = '.'; 504 | } 505 | ver <<= 8; 506 | } 507 | } 508 | 509 | // eg, 0xAF66FF99 -> AF66FF99 510 | void hash_to_str(char* s, u32 hash) { 511 | for (int i = 0; i < 4; i++) { 512 | const auto num = (hash >> 24) & 0xFF; 513 | const auto top = (num >> 4) & 0xF; 514 | const auto bottom = (num >> 0) & 0xF; 515 | 516 | constexpr auto a = [](u8 nib) -> char { 517 | if (nib >= 0 && nib <= 9) { return '0' + nib; } 518 | return 'a' + nib - 10; 519 | }; 520 | 521 | *s++ = a(top); 522 | *s++ = a(bottom); 523 | 524 | hash <<= 8; 525 | } 526 | } 527 | 528 | void keygen_to_str(char* s, u8 keygen) { 529 | num_2_str(s, keygen); 530 | } 531 | 532 | } // namespace 533 | 534 | int main(int argc, char* argv[]) { 535 | constexpr auto ini_path = "/config/sys-patch/config.ini"; 536 | constexpr auto log_path = "/config/sys-patch/log.ini"; 537 | 538 | create_dir("/config/"); 539 | create_dir("/config/sys-patch/"); 540 | ini_remove(log_path); 541 | 542 | // load options 543 | const auto patch_sysmmc = ini_load_or_write_default("options", "patch_sysmmc", 1, ini_path); 544 | const auto patch_emummc = ini_load_or_write_default("options", "patch_emummc", 1, ini_path); 545 | const auto enable_logging = ini_load_or_write_default("options", "enable_logging", 1, ini_path); 546 | VERSION_SKIP = ini_load_or_write_default("options", "version_skip", 1, ini_path); 547 | 548 | // load patch toggles 549 | for (auto& patch : patches) { 550 | for (auto& p : patch.patterns) { 551 | p.enabled = ini_load_or_write_default(patch.name, p.patch_name, p.enabled, ini_path); 552 | if (!p.enabled) { 553 | p.result = PatchResult::DISABLED; 554 | } 555 | } 556 | } 557 | 558 | const auto emummc = is_emummc(); 559 | bool enable_patching = true; 560 | 561 | // check if we should patch sysmmc 562 | if (!patch_sysmmc && !emummc) { 563 | enable_patching = false; 564 | } 565 | 566 | // check if we should patch emummc 567 | if (!patch_emummc && emummc) { 568 | enable_patching = false; 569 | } 570 | 571 | // speedtest 572 | const auto ticks_start = armGetSystemTick(); 573 | 574 | if (enable_patching) { 575 | for (auto& patch : patches) { 576 | apply_patch(patch); 577 | } 578 | } 579 | 580 | const auto ticks_end = armGetSystemTick(); 581 | const auto diff_ns = armTicksToNs(ticks_end) - armTicksToNs(ticks_start); 582 | 583 | if (enable_logging) { 584 | for (auto& patch : patches) { 585 | for (auto& p : patch.patterns) { 586 | if (!enable_patching) { 587 | p.result = PatchResult::SKIPPED; 588 | } 589 | ini_puts(patch.name, p.patch_name, patch_result_to_str(p.result), log_path); 590 | } 591 | } 592 | 593 | // fw of the system 594 | char fw_version[12]{}; 595 | // atmosphere version 596 | char ams_version[12]{}; 597 | // lowest fw supported by atmosphere 598 | char ams_target_version[12]{}; 599 | // ??? 600 | char ams_keygen[3]{}; 601 | // git commit hash 602 | char ams_hash[9]{}; 603 | // how long it took to patch 604 | char patch_time[20]{}; 605 | 606 | version_to_str(fw_version, FW_VERSION); 607 | version_to_str(ams_version, AMS_VERSION); 608 | version_to_str(ams_target_version, AMS_TARGET_VERSION); 609 | keygen_to_str(ams_keygen, AMS_KEYGEN); 610 | hash_to_str(ams_hash, AMS_HASH >> 32); 611 | ms_2_str(patch_time, diff_ns/1000ULL/1000ULL); 612 | 613 | // defined in the Makefile 614 | #define DATE (DATE_DAY "." DATE_MONTH "." DATE_YEAR " " DATE_HOUR ":" DATE_MIN ":" DATE_SEC) 615 | 616 | ini_puts("stats", "version", VERSION_WITH_HASH, log_path); 617 | ini_puts("stats", "build_date", DATE, log_path); 618 | ini_puts("stats", "fw_version", fw_version, log_path); 619 | ini_puts("stats", "ams_version", ams_version, log_path); 620 | ini_puts("stats", "ams_target_version", ams_target_version, log_path); 621 | ini_puts("stats", "ams_keygen", ams_keygen, log_path); 622 | ini_puts("stats", "ams_hash", ams_hash, log_path); 623 | ini_putl("stats", "is_emummc", emummc, log_path); 624 | ini_putl("stats", "heap_size", INNER_HEAP_SIZE, log_path); 625 | ini_putl("stats", "buffer_size", READ_BUFFER_SIZE, log_path); 626 | ini_puts("stats", "patch_time", patch_time, log_path); 627 | } 628 | 629 | // note: sysmod exits here. 630 | // to keep it running, add a for (;;) loop (remember to sleep!) 631 | return 0; 632 | } 633 | 634 | // libnx stuff goes below 635 | extern "C" { 636 | 637 | // Sysmodules should not use applet*. 638 | u32 __nx_applet_type = AppletType_None; 639 | 640 | // Sysmodules will normally only want to use one FS session. 641 | u32 __nx_fs_num_sessions = 1; 642 | 643 | // Newlib heap configuration function (makes malloc/free work). 644 | void __libnx_initheap(void) { 645 | static char inner_heap[INNER_HEAP_SIZE]; 646 | extern char* fake_heap_start; 647 | extern char* fake_heap_end; 648 | 649 | // Configure the newlib heap. 650 | fake_heap_start = inner_heap; 651 | fake_heap_end = inner_heap + sizeof(inner_heap); 652 | } 653 | 654 | // Service initialization. 655 | void __appInit(void) { 656 | Result rc{}; 657 | 658 | // Open a service manager session. 659 | if (R_FAILED(rc = smInitialize())) 660 | fatalThrow(rc); 661 | 662 | // Retrieve the current version of Horizon OS. 663 | if (R_SUCCEEDED(rc = setsysInitialize())) { 664 | SetSysFirmwareVersion fw{}; 665 | if (R_SUCCEEDED(rc = setsysGetFirmwareVersion(&fw))) { 666 | FW_VERSION = MAKEHOSVERSION(fw.major, fw.minor, fw.micro); 667 | hosversionSet(FW_VERSION); 668 | } 669 | setsysExit(); 670 | } 671 | 672 | // get ams version 673 | if (R_SUCCEEDED(rc = splInitialize())) { 674 | u64 v{}; 675 | u64 hash{}; 676 | if (R_SUCCEEDED(rc = splGetConfig((SplConfigItem)65000, &v))) { 677 | AMS_VERSION = (v >> 40) & 0xFFFFFF; 678 | AMS_KEYGEN = (v >> 32) & 0xFF; 679 | AMS_TARGET_VERSION = v & 0xFFFFFF; 680 | } 681 | if (R_SUCCEEDED(rc = splGetConfig((SplConfigItem)65003, &hash))) { 682 | AMS_HASH = hash; 683 | } 684 | 685 | splExit(); 686 | } 687 | 688 | if (R_FAILED(rc = fsInitialize())) 689 | fatalThrow(rc); 690 | 691 | // Add other services you want to use here. 692 | if (R_FAILED(rc = pmdmntInitialize())) 693 | fatalThrow(rc); 694 | 695 | // Close the service manager session. 696 | smExit(); 697 | } 698 | 699 | // Service deinitialization. 700 | void __appExit(void) { 701 | pmdmntExit(); 702 | fsExit(); 703 | } 704 | 705 | } // extern "C" 706 | -------------------------------------------------------------------------------- /common/minIni/minIni.c: -------------------------------------------------------------------------------- 1 | /* minIni - Multi-Platform INI file parser, suitable for embedded systems 2 | * 3 | * These routines are in part based on the article "Multiplatform .INI Files" 4 | * by Joseph J. Graf in the March 1994 issue of Dr. Dobb's Journal. 5 | * 6 | * Copyright (c) CompuPhase, 2008-2021 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 9 | * use this file except in compliance with the License. You may obtain a copy 10 | * of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 16 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 17 | * License for the specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | * Version: $Id: minIni.c 53 2015-01-18 13:35:11Z thiadmer.riemersma@gmail.com $ 21 | */ 22 | 23 | #if (defined _UNICODE || defined __UNICODE__ || defined UNICODE) && !defined INI_ANSIONLY 24 | # if !defined UNICODE /* for Windows */ 25 | # define UNICODE 26 | # endif 27 | # if !defined _UNICODE /* for C library */ 28 | # define _UNICODE 29 | # endif 30 | #endif 31 | 32 | #define MININI_IMPLEMENTATION 33 | #include "minIni.h" 34 | #if defined NDEBUG 35 | #define assert(e) 36 | #else 37 | #include 38 | #endif 39 | 40 | #if !defined __T || defined INI_ANSIONLY 41 | #include 42 | #include 43 | #include 44 | #define TCHAR char 45 | #define __T(s) s 46 | #define _tcscat strcat 47 | #define _tcschr strchr 48 | #define _tcscmp strcmp 49 | #define _tcscpy strcpy 50 | #define _tcsicmp stricmp 51 | #define _tcslen strlen 52 | #define _tcsncmp strncmp 53 | #define _tcsnicmp strnicmp 54 | #define _tcsrchr strrchr 55 | #define _tcstol strtol 56 | #define _tcstod strtod 57 | #define _totupper toupper 58 | #define _stprintf sprintf 59 | #define _tfgets fgets 60 | #define _tfputs fputs 61 | #define _tfopen fopen 62 | #define _tremove remove 63 | #define _trename rename 64 | #endif 65 | 66 | #if defined __linux || defined __linux__ 67 | #define __LINUX__ 68 | #elif defined FREEBSD && !defined __FreeBSD__ 69 | #define __FreeBSD__ 70 | #elif defined(_MSC_VER) 71 | #pragma warning(disable: 4996) /* for Microsoft Visual C/C++ */ 72 | #endif 73 | #if !defined strnicmp && !defined PORTABLE_STRNICMP 74 | #if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ || defined __NetBSD__ || defined __DragonFly__ || defined __GNUC__ 75 | #define strnicmp strncasecmp 76 | #endif 77 | #endif 78 | #if !defined _totupper 79 | #define _totupper toupper 80 | #endif 81 | 82 | #if !defined INI_LINETERM 83 | #if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ || defined __NetBSD__ || defined __DragonFly__ 84 | #define INI_LINETERM __T("\n") 85 | #else 86 | #define INI_LINETERM __T("\r\n") 87 | #endif 88 | #endif 89 | #if !defined INI_FILETYPE 90 | #error Missing definition for INI_FILETYPE. 91 | #endif 92 | 93 | #if !defined sizearray 94 | #define sizearray(a) (sizeof(a) / sizeof((a)[0])) 95 | #endif 96 | 97 | enum quote_option { 98 | QUOTE_NONE, 99 | QUOTE_ENQUOTE, 100 | QUOTE_DEQUOTE, 101 | }; 102 | 103 | #if defined PORTABLE_STRNICMP 104 | int strnicmp(const TCHAR *s1, const TCHAR *s2, size_t n) 105 | { 106 | while (n-- != 0 && (*s1 || *s2)) { 107 | register int c1, c2; 108 | c1 = *s1++; 109 | if ('a' <= c1 && c1 <= 'z') 110 | c1 += ('A' - 'a'); 111 | c2 = *s2++; 112 | if ('a' <= c2 && c2 <= 'z') 113 | c2 += ('A' - 'a'); 114 | if (c1 != c2) 115 | return c1 - c2; 116 | } 117 | return 0; 118 | } 119 | #endif /* PORTABLE_STRNICMP */ 120 | 121 | static TCHAR *skipleading(const TCHAR *str) 122 | { 123 | assert(str != NULL); 124 | while ('\0' < *str && *str <= ' ') 125 | str++; 126 | return (TCHAR *)str; 127 | } 128 | 129 | static TCHAR *skiptrailing(const TCHAR *str, const TCHAR *base) 130 | { 131 | assert(str != NULL); 132 | assert(base != NULL); 133 | while (str > base && '\0' < *(str-1) && *(str-1) <= ' ') 134 | str--; 135 | return (TCHAR *)str; 136 | } 137 | 138 | static TCHAR *striptrailing(TCHAR *str) 139 | { 140 | TCHAR *ptr = skiptrailing(_tcschr(str, '\0'), str); 141 | assert(ptr != NULL); 142 | *ptr = '\0'; 143 | return str; 144 | } 145 | 146 | static TCHAR *ini_strncpy(TCHAR *dest, const TCHAR *source, size_t maxlen, enum quote_option option) 147 | { 148 | size_t d, s; 149 | 150 | assert(maxlen>0); 151 | assert(source != NULL && dest != NULL); 152 | assert((dest < source || (dest == source && option != QUOTE_ENQUOTE)) || dest > source + strlen(source)); 153 | if (option == QUOTE_ENQUOTE && maxlen < 3) 154 | option = QUOTE_NONE; /* cannot store two quotes and a terminating zero in less than 3 characters */ 155 | 156 | switch (option) { 157 | case QUOTE_NONE: 158 | for (d = 0; d < maxlen - 1 && source[d] != '\0'; d++) 159 | dest[d] = source[d]; 160 | assert(d < maxlen); 161 | dest[d] = '\0'; 162 | break; 163 | case QUOTE_ENQUOTE: 164 | d = 0; 165 | dest[d++] = '"'; 166 | for (s = 0; source[s] != '\0' && d < maxlen - 2; s++, d++) { 167 | if (source[s] == '"') { 168 | if (d >= maxlen - 3) 169 | break; /* no space to store the escape character plus the one that follows it */ 170 | dest[d++] = '\\'; 171 | } 172 | dest[d] = source[s]; 173 | } 174 | dest[d++] = '"'; 175 | dest[d] = '\0'; 176 | break; 177 | case QUOTE_DEQUOTE: 178 | for (d = s = 0; source[s] != '\0' && d < maxlen - 1; s++, d++) { 179 | if ((source[s] == '"' || source[s] == '\\') && source[s + 1] == '"') 180 | s++; 181 | dest[d] = source[s]; 182 | } 183 | dest[d] = '\0'; 184 | break; 185 | default: 186 | assert(0); 187 | } 188 | 189 | return dest; 190 | } 191 | 192 | static TCHAR *cleanstring(TCHAR *string, enum quote_option *quotes) 193 | { 194 | int isstring; 195 | TCHAR *ep; 196 | 197 | assert(string != NULL); 198 | assert(quotes != NULL); 199 | 200 | /* Remove a trailing comment */ 201 | isstring = 0; 202 | for (ep = string; *ep != '\0' && ((*ep != ';' && *ep != '#') || isstring); ep++) { 203 | if (*ep == '"') { 204 | if (*(ep + 1) == '"') 205 | ep++; /* skip "" (both quotes) */ 206 | else 207 | isstring = !isstring; /* single quote, toggle isstring */ 208 | } else if (*ep == '\\' && *(ep + 1) == '"') { 209 | ep++; /* skip \" (both quotes */ 210 | } 211 | } 212 | assert(ep != NULL && (*ep == '\0' || *ep == ';' || *ep == '#')); 213 | *ep = '\0'; /* terminate at a comment */ 214 | striptrailing(string); 215 | /* Remove double quotes surrounding a value */ 216 | *quotes = QUOTE_NONE; 217 | if (*string == '"' && (ep = _tcschr(string, '\0')) != NULL && *(ep - 1) == '"') { 218 | string++; 219 | *--ep = '\0'; 220 | *quotes = QUOTE_DEQUOTE; /* this is a string, so remove escaped characters */ 221 | } 222 | return string; 223 | } 224 | 225 | static int getkeystring(INI_FILETYPE *fp, const TCHAR *Section, const TCHAR *Key, 226 | int idxSection, int idxKey, TCHAR *Buffer, int BufferSize, 227 | INI_FILEPOS *mark) 228 | { 229 | TCHAR *sp, *ep; 230 | int len, idx; 231 | enum quote_option quotes; 232 | TCHAR LocalBuffer[INI_BUFFERSIZE]; 233 | 234 | assert(fp != NULL); 235 | /* Move through file 1 line at a time until a section is matched or EOF. If 236 | * parameter Section is NULL, only look at keys above the first section. If 237 | * idxSection is positive, copy the relevant section name. 238 | */ 239 | len = (Section != NULL) ? (int)_tcslen(Section) : 0; 240 | if (len > 0 || idxSection >= 0) { 241 | assert(idxSection >= 0 || Section != NULL); 242 | idx = -1; 243 | do { 244 | do { 245 | if (!ini_read(LocalBuffer, INI_BUFFERSIZE, fp)) 246 | return 0; 247 | sp = skipleading(LocalBuffer); 248 | ep = _tcsrchr(sp, ']'); 249 | } while (*sp != '[' || ep == NULL); 250 | /* When arrived here, a section was found; now optionally skip leading and 251 | * trailing whitespace. 252 | */ 253 | assert(sp != NULL && *sp == '['); 254 | sp = skipleading(sp + 1); 255 | assert(ep != NULL && *ep == ']'); 256 | ep = skiptrailing(ep, sp); 257 | } while ((((int)(ep-sp) != len || Section == NULL || _tcsnicmp(sp, Section, len) != 0) && ++idx != idxSection)); 258 | if (idxSection >= 0) { 259 | if (idx == idxSection) { 260 | assert(ep != NULL); 261 | *ep = '\0'; /* the end of the section name was found earlier */ 262 | ini_strncpy(Buffer, sp, BufferSize, QUOTE_NONE); 263 | return 1; 264 | } 265 | return 0; /* no more section found */ 266 | } 267 | } 268 | 269 | /* Now that the section has been found, find the entry. 270 | * Stop searching upon leaving the section's area. 271 | */ 272 | assert(Key != NULL || idxKey >= 0); 273 | len = (Key != NULL) ? (int)_tcslen(Key) : 0; 274 | idx = -1; 275 | do { 276 | if (mark != NULL) 277 | ini_tell(fp, mark); /* optionally keep the mark to the start of the line */ 278 | if (!ini_read(LocalBuffer,INI_BUFFERSIZE,fp) || *(sp = skipleading(LocalBuffer)) == '[') 279 | return 0; 280 | sp = skipleading(LocalBuffer); 281 | ep = _tcschr(sp, '='); /* Parse out the equal sign */ 282 | if (ep == NULL) 283 | ep = _tcschr(sp, ':'); 284 | } while (*sp == ';' || *sp == '#' || ep == NULL 285 | || ((len == 0 || (int)(skiptrailing(ep,sp)-sp) != len || _tcsnicmp(sp,Key,len) != 0) && ++idx != idxKey)); 286 | if (idxKey >= 0) { 287 | if (idx == idxKey) { 288 | assert(ep != NULL); 289 | assert(*ep == '=' || *ep == ':'); 290 | *ep = '\0'; 291 | striptrailing(sp); 292 | ini_strncpy(Buffer, sp, BufferSize, QUOTE_NONE); 293 | return 1; 294 | } 295 | return 0; /* no more key found (in this section) */ 296 | } 297 | 298 | /* Copy up to BufferSize chars to buffer */ 299 | assert(ep != NULL); 300 | assert(*ep == '=' || *ep == ':'); 301 | sp = skipleading(ep + 1); 302 | sp = cleanstring(sp, "es); /* Remove a trailing comment */ 303 | ini_strncpy(Buffer, sp, BufferSize, quotes); 304 | return 1; 305 | } 306 | 307 | /** ini_gets() 308 | * \param Section the name of the section to search for 309 | * \param Key the name of the entry to find the value of 310 | * \param DefValue default string in the event of a failed read 311 | * \param Buffer a pointer to the buffer to copy into 312 | * \param BufferSize the maximum number of characters to copy 313 | * \param Filename the name and full path of the .ini file to read from 314 | * 315 | * \return the number of characters copied into the supplied buffer 316 | */ 317 | int ini_gets(const TCHAR *Section, const TCHAR *Key, const TCHAR *DefValue, 318 | TCHAR *Buffer, int BufferSize, const TCHAR *Filename) 319 | { 320 | INI_FILETYPE fp; 321 | int ok = 0; 322 | 323 | if (Buffer == NULL || BufferSize <= 0 || Key == NULL) 324 | return 0; 325 | if (ini_openread(Filename, &fp)) { 326 | ok = getkeystring(&fp, Section, Key, -1, -1, Buffer, BufferSize, NULL); 327 | (void)ini_close(&fp); 328 | } 329 | if (!ok) 330 | ini_strncpy(Buffer, (DefValue != NULL) ? DefValue : __T(""), BufferSize, QUOTE_NONE); 331 | return (int)_tcslen(Buffer); 332 | } 333 | 334 | /** ini_getl() 335 | * \param Section the name of the section to search for 336 | * \param Key the name of the entry to find the value of 337 | * \param DefValue the default value in the event of a failed read 338 | * \param Filename the name of the .ini file to read from 339 | * 340 | * \return the value located at Key 341 | */ 342 | long ini_getl(const TCHAR *Section, const TCHAR *Key, long DefValue, const TCHAR *Filename) 343 | { 344 | TCHAR LocalBuffer[64]; 345 | int len = ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename); 346 | return (len == 0) ? DefValue 347 | : ((len >= 2 && _totupper((int)LocalBuffer[1]) == 'X') ? _tcstol(LocalBuffer, NULL, 16) 348 | : _tcstol(LocalBuffer, NULL, 10)); 349 | } 350 | 351 | #if defined INI_REAL 352 | /** ini_getf() 353 | * \param Section the name of the section to search for 354 | * \param Key the name of the entry to find the value of 355 | * \param DefValue the default value in the event of a failed read 356 | * \param Filename the name of the .ini file to read from 357 | * 358 | * \return the value located at Key 359 | */ 360 | INI_REAL ini_getf(const TCHAR *Section, const TCHAR *Key, INI_REAL DefValue, const TCHAR *Filename) 361 | { 362 | TCHAR LocalBuffer[64]; 363 | int len = ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename); 364 | return (len == 0) ? DefValue : ini_atof(LocalBuffer); 365 | } 366 | #endif 367 | 368 | /** ini_getbool() 369 | * \param Section the name of the section to search for 370 | * \param Key the name of the entry to find the value of 371 | * \param DefValue default value in the event of a failed read; it should 372 | * zero (0) or one (1). 373 | * \param Filename the name and full path of the .ini file to read from 374 | * 375 | * A true boolean is found if one of the following is matched: 376 | * - A string starting with 'y' or 'Y' 377 | * - A string starting with 't' or 'T' 378 | * - A string starting with '1' 379 | * 380 | * A false boolean is found if one of the following is matched: 381 | * - A string starting with 'n' or 'N' 382 | * - A string starting with 'f' or 'F' 383 | * - A string starting with '0' 384 | * 385 | * \return the true/false flag as interpreted at Key 386 | */ 387 | int ini_getbool(const TCHAR *Section, const TCHAR *Key, int DefValue, const TCHAR *Filename) 388 | { 389 | TCHAR LocalBuffer[2] = __T(""); 390 | int ret; 391 | 392 | ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename); 393 | LocalBuffer[0] = (TCHAR)_totupper((int)LocalBuffer[0]); 394 | if (LocalBuffer[0] == 'Y' || LocalBuffer[0] == '1' || LocalBuffer[0] == 'T') 395 | ret = 1; 396 | else if (LocalBuffer[0] == 'N' || LocalBuffer[0] == '0' || LocalBuffer[0] == 'F') 397 | ret = 0; 398 | else 399 | ret = DefValue; 400 | 401 | return(ret); 402 | } 403 | 404 | /** ini_getsection() 405 | * \param idx the zero-based sequence number of the section to return 406 | * \param Buffer a pointer to the buffer to copy into 407 | * \param BufferSize the maximum number of characters to copy 408 | * \param Filename the name and full path of the .ini file to read from 409 | * 410 | * \return the number of characters copied into the supplied buffer 411 | */ 412 | int ini_getsection(int idx, TCHAR *Buffer, int BufferSize, const TCHAR *Filename) 413 | { 414 | INI_FILETYPE fp; 415 | int ok = 0; 416 | 417 | if (Buffer == NULL || BufferSize <= 0 || idx < 0) 418 | return 0; 419 | if (ini_openread(Filename, &fp)) { 420 | ok = getkeystring(&fp, NULL, NULL, idx, -1, Buffer, BufferSize, NULL); 421 | (void)ini_close(&fp); 422 | } 423 | if (!ok) 424 | *Buffer = '\0'; 425 | return (int)_tcslen(Buffer); 426 | } 427 | 428 | /** ini_getkey() 429 | * \param Section the name of the section to browse through, or NULL to 430 | * browse through the keys outside any section 431 | * \param idx the zero-based sequence number of the key to return 432 | * \param Buffer a pointer to the buffer to copy into 433 | * \param BufferSize the maximum number of characters to copy 434 | * \param Filename the name and full path of the .ini file to read from 435 | * 436 | * \return the number of characters copied into the supplied buffer 437 | */ 438 | int ini_getkey(const TCHAR *Section, int idx, TCHAR *Buffer, int BufferSize, const TCHAR *Filename) 439 | { 440 | INI_FILETYPE fp; 441 | int ok = 0; 442 | 443 | if (Buffer == NULL || BufferSize <= 0 || idx < 0) 444 | return 0; 445 | if (ini_openread(Filename, &fp)) { 446 | ok = getkeystring(&fp, Section, NULL, -1, idx, Buffer, BufferSize, NULL); 447 | (void)ini_close(&fp); 448 | } 449 | if (!ok) 450 | *Buffer = '\0'; 451 | return (int)_tcslen(Buffer); 452 | } 453 | 454 | /** ini_hassection() 455 | * \param Section the name of the section to search for 456 | * \param Filename the name of the .ini file to read from 457 | * 458 | * \return 1 if the section is found, 0 if not found 459 | */ 460 | int ini_hassection(const mTCHAR *Section, const mTCHAR *Filename) 461 | { 462 | TCHAR LocalBuffer[32]; /* dummy buffer */ 463 | INI_FILETYPE fp; 464 | int ok = 0; 465 | 466 | if (ini_openread(Filename, &fp)) { 467 | ok = getkeystring(&fp, Section, NULL, -1, 0, LocalBuffer, sizearray(LocalBuffer), NULL); 468 | (void)ini_close(&fp); 469 | } 470 | return ok; 471 | } 472 | 473 | /** ini_haskey() 474 | * \param Section the name of the section to search for 475 | * \param Key the name of the entry to find the value of 476 | * \param Filename the name of the .ini file to read from 477 | * 478 | * \return 1 if the key is found, 0 if not found 479 | */ 480 | int ini_haskey(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Filename) 481 | { 482 | TCHAR LocalBuffer[32]; /* dummy buffer */ 483 | INI_FILETYPE fp; 484 | int ok = 0; 485 | 486 | if (ini_openread(Filename, &fp)) { 487 | ok = getkeystring(&fp, Section, Key, -1, -1, LocalBuffer, sizearray(LocalBuffer), NULL); 488 | (void)ini_close(&fp); 489 | } 490 | return ok; 491 | } 492 | 493 | 494 | #if !defined INI_NOBROWSE 495 | /** ini_browse() 496 | * \param Callback a pointer to a function that will be called for every 497 | * setting in the INI file. 498 | * \param UserData arbitrary data, which the function passes on the 499 | * \c Callback function 500 | * \param Filename the name and full path of the .ini file to read from 501 | * 502 | * \return 1 on success, 0 on failure (INI file not found) 503 | * 504 | * \note The \c Callback function must return 1 to continue 505 | * browsing through the INI file, or 0 to stop. Even when the 506 | * callback stops the browsing, this function will return 1 507 | * (for success). 508 | */ 509 | int ini_browse(INI_CALLBACK Callback, void *UserData, const TCHAR *Filename) 510 | { 511 | TCHAR LocalBuffer[INI_BUFFERSIZE]; 512 | int lenSec, lenKey; 513 | enum quote_option quotes; 514 | INI_FILETYPE fp; 515 | 516 | if (Callback == NULL) 517 | return 0; 518 | if (!ini_openread(Filename, &fp)) 519 | return 0; 520 | 521 | LocalBuffer[0] = '\0'; /* copy an empty section in the buffer */ 522 | lenSec = (int)_tcslen(LocalBuffer) + 1; 523 | for ( ;; ) { 524 | TCHAR *sp, *ep; 525 | if (!ini_read(LocalBuffer + lenSec, INI_BUFFERSIZE - lenSec, &fp)) 526 | break; 527 | sp = skipleading(LocalBuffer + lenSec); 528 | /* ignore empty strings and comments */ 529 | if (*sp == '\0' || *sp == ';' || *sp == '#') 530 | continue; 531 | /* see whether we reached a new section */ 532 | ep = _tcsrchr(sp, ']'); 533 | if (*sp == '[' && ep != NULL) { 534 | sp = skipleading(sp + 1); 535 | ep = skiptrailing(ep, sp); 536 | *ep = '\0'; 537 | ini_strncpy(LocalBuffer, sp, INI_BUFFERSIZE, QUOTE_NONE); 538 | lenSec = (int)_tcslen(LocalBuffer) + 1; 539 | continue; 540 | } 541 | /* not a new section, test for a key/value pair */ 542 | ep = _tcschr(sp, '='); /* test for the equal sign or colon */ 543 | if (ep == NULL) 544 | ep = _tcschr(sp, ':'); 545 | if (ep == NULL) 546 | continue; /* invalid line, ignore */ 547 | *ep++ = '\0'; /* split the key from the value */ 548 | striptrailing(sp); 549 | ini_strncpy(LocalBuffer + lenSec, sp, INI_BUFFERSIZE - lenSec, QUOTE_NONE); 550 | lenKey = (int)_tcslen(LocalBuffer + lenSec) + 1; 551 | /* clean up the value */ 552 | sp = skipleading(ep); 553 | sp = cleanstring(sp, "es); /* Remove a trailing comment */ 554 | ini_strncpy(LocalBuffer + lenSec + lenKey, sp, INI_BUFFERSIZE - lenSec - lenKey, quotes); 555 | /* call the callback */ 556 | if (!Callback(LocalBuffer, LocalBuffer + lenSec, LocalBuffer + lenSec + lenKey, UserData)) 557 | break; 558 | } 559 | 560 | (void)ini_close(&fp); 561 | return 1; 562 | } 563 | #endif /* INI_NOBROWSE */ 564 | 565 | #if ! defined INI_READONLY 566 | static void ini_tempname(TCHAR *dest, const TCHAR *source, int maxlength) 567 | { 568 | TCHAR *p; 569 | 570 | ini_strncpy(dest, source, maxlength, QUOTE_NONE); 571 | p = _tcschr(dest, '\0'); 572 | assert(p != NULL); 573 | *(p - 1) = '~'; 574 | } 575 | 576 | static enum quote_option check_enquote(const TCHAR *Value) 577 | { 578 | const TCHAR *p; 579 | 580 | /* run through the value, if it has trailing spaces, or '"', ';' or '#' 581 | * characters, enquote it 582 | */ 583 | assert(Value != NULL); 584 | for (p = Value; *p != '\0' && *p != '"' && *p != ';' && *p != '#'; p++) 585 | /* nothing */; 586 | return (*p != '\0' || (p > Value && *(p - 1) == ' ')) ? QUOTE_ENQUOTE : QUOTE_NONE; 587 | } 588 | 589 | static void writesection(TCHAR *LocalBuffer, const TCHAR *Section, INI_FILETYPE *fp) 590 | { 591 | if (Section != NULL && _tcslen(Section) > 0) { 592 | TCHAR *p; 593 | LocalBuffer[0] = '['; 594 | ini_strncpy(LocalBuffer + 1, Section, INI_BUFFERSIZE - 4, QUOTE_NONE); /* -1 for '[', -1 for ']', -2 for '\r\n' */ 595 | p = _tcschr(LocalBuffer, '\0'); 596 | assert(p != NULL); 597 | *p++ = ']'; 598 | _tcscpy(p, INI_LINETERM); /* copy line terminator (typically "\n") */ 599 | if (fp != NULL) 600 | (void)ini_write(LocalBuffer, fp); 601 | } 602 | } 603 | 604 | static void writekey(TCHAR *LocalBuffer, const TCHAR *Key, const TCHAR *Value, INI_FILETYPE *fp) 605 | { 606 | TCHAR *p; 607 | enum quote_option option = check_enquote(Value); 608 | ini_strncpy(LocalBuffer, Key, INI_BUFFERSIZE - 3, QUOTE_NONE); /* -1 for '=', -2 for '\r\n' */ 609 | p = _tcschr(LocalBuffer, '\0'); 610 | assert(p != NULL); 611 | *p++ = '='; 612 | ini_strncpy(p, Value, INI_BUFFERSIZE - (p - LocalBuffer) - 2, option); /* -2 for '\r\n' */ 613 | p = _tcschr(LocalBuffer, '\0'); 614 | assert(p != NULL); 615 | _tcscpy(p, INI_LINETERM); /* copy line terminator (typically "\n") */ 616 | if (fp != NULL) 617 | (void)ini_write(LocalBuffer, fp); 618 | } 619 | 620 | static int cache_accum(const TCHAR *string, int *size, int max) 621 | { 622 | int len = (int)_tcslen(string); 623 | if (*size + len >= max) 624 | return 0; 625 | *size += len; 626 | return 1; 627 | } 628 | 629 | static int cache_flush(TCHAR *buffer, int *size, 630 | INI_FILETYPE *rfp, INI_FILETYPE *wfp, INI_FILEPOS *mark) 631 | { 632 | int terminator_len = (int)_tcslen(INI_LINETERM); 633 | int pos = 0, pos_prev = -1; 634 | 635 | (void)ini_seek(rfp, mark); 636 | assert(buffer != NULL); 637 | buffer[0] = '\0'; 638 | assert(size != NULL); 639 | assert(*size <= INI_BUFFERSIZE); 640 | while (pos < *size && pos != pos_prev) { 641 | pos_prev = pos; /* to guard against zero bytes in the INI file */ 642 | (void)ini_read(buffer + pos, INI_BUFFERSIZE - pos, rfp); 643 | while (pos < *size && buffer[pos] != '\0') 644 | pos++; /* cannot use _tcslen() because buffer may not be zero-terminated */ 645 | } 646 | if (buffer[0] != '\0') { 647 | assert(pos > 0 && pos <= INI_BUFFERSIZE); 648 | if (pos == INI_BUFFERSIZE) 649 | pos--; 650 | buffer[pos] = '\0'; /* force zero-termination (may be left unterminated in the above while loop) */ 651 | (void)ini_write(buffer, wfp); 652 | } 653 | ini_tell(rfp, mark); /* update mark */ 654 | *size = 0; 655 | /* return whether the buffer ended with a line termination */ 656 | return (pos > terminator_len) && (_tcscmp(buffer + pos - terminator_len, INI_LINETERM) == 0); 657 | } 658 | 659 | static int close_rename(INI_FILETYPE *rfp, INI_FILETYPE *wfp, const TCHAR *filename, TCHAR *buffer) 660 | { 661 | (void)ini_close(rfp); 662 | (void)ini_close(wfp); 663 | (void)ini_tempname(buffer, filename, INI_BUFFERSIZE); 664 | #if defined ini_remove || defined INI_REMOVE 665 | (void)ini_remove(filename); 666 | #endif 667 | (void)ini_rename(buffer, filename); 668 | return 1; 669 | } 670 | 671 | /** ini_puts() 672 | * \param Section the name of the section to write the string in 673 | * \param Key the name of the entry to write, or NULL to erase all keys in the section 674 | * \param Value a pointer to the buffer the string, or NULL to erase the key 675 | * \param Filename the name and full path of the .ini file to write to 676 | * 677 | * \return 1 if successful, otherwise 0 678 | */ 679 | int ini_puts(const TCHAR *Section, const TCHAR *Key, const TCHAR *Value, const TCHAR *Filename) 680 | { 681 | INI_FILETYPE rfp; 682 | INI_FILETYPE wfp; 683 | INI_FILEPOS mark; 684 | INI_FILEPOS head, tail; 685 | TCHAR *sp, *ep; 686 | TCHAR LocalBuffer[INI_BUFFERSIZE]; 687 | int len, match, flag, cachelen; 688 | 689 | assert(Filename != NULL); 690 | if (!ini_openread(Filename, &rfp)) { 691 | /* If the .ini file doesn't exist, make a new file */ 692 | if (Key != NULL && Value != NULL) { 693 | if (!ini_openwrite(Filename, &wfp)) 694 | return 0; 695 | writesection(LocalBuffer, Section, &wfp); 696 | writekey(LocalBuffer, Key, Value, &wfp); 697 | (void)ini_close(&wfp); 698 | } 699 | return 1; 700 | } 701 | 702 | /* If parameters Key and Value are valid (so this is not an "erase" request) 703 | * and the setting already exists, there are two short-cuts to avoid rewriting 704 | * the INI file. 705 | */ 706 | if (Key != NULL && Value != NULL) { 707 | match = getkeystring(&rfp, Section, Key, -1, -1, LocalBuffer, sizearray(LocalBuffer), &head); 708 | if (match) { 709 | /* if the current setting is identical to the one to write, there is 710 | * nothing to do. 711 | */ 712 | if (_tcscmp(LocalBuffer,Value) == 0) { 713 | (void)ini_close(&rfp); 714 | return 1; 715 | } 716 | /* if the new setting has the same length as the current setting, and the 717 | * glue file permits file read/write access, we can modify in place. 718 | */ 719 | #if defined ini_openrewrite || defined INI_OPENREWRITE 720 | /* we already have the start of the (raw) line, get the end too */ 721 | ini_tell(&rfp, &tail); 722 | /* create new buffer (without writing it to file) */ 723 | writekey(LocalBuffer, Key, Value, NULL); 724 | if (_tcslen(LocalBuffer) == (size_t)(tail - head)) { 725 | /* length matches, close the file & re-open for read/write, then 726 | * write at the correct position 727 | */ 728 | (void)ini_close(&rfp); 729 | if (!ini_openrewrite(Filename, &wfp)) 730 | return 0; 731 | (void)ini_seek(&wfp, &head); 732 | (void)ini_write(LocalBuffer, &wfp); 733 | (void)ini_close(&wfp); 734 | return 1; 735 | } 736 | #endif 737 | } 738 | /* key not found, or different value & length -> proceed */ 739 | } else if (Key != NULL && Value == NULL) { 740 | /* Conversely, for a request to delete a setting; if that setting isn't 741 | present, just return */ 742 | match = getkeystring(&rfp, Section, Key, -1, -1, LocalBuffer, sizearray(LocalBuffer), NULL); 743 | if (!match) { 744 | (void)ini_close(&rfp); 745 | return 1; 746 | } 747 | /* key found -> proceed to delete it */ 748 | } 749 | 750 | /* Get a temporary file name to copy to. Use the existing name, but with 751 | * the last character set to a '~'. 752 | */ 753 | (void)ini_close(&rfp); 754 | ini_tempname(LocalBuffer, Filename, INI_BUFFERSIZE); 755 | if (!ini_openwrite(LocalBuffer, &wfp)) 756 | return 0; 757 | /* In the case of (advisory) file locks, ini_openwrite() may have been blocked 758 | * on the open, and after the block is lifted, the original file may have been 759 | * renamed, which is why the original file was closed and is now reopened */ 760 | if (!ini_openread(Filename, &rfp)) { 761 | /* If the .ini file doesn't exist any more, make a new file */ 762 | assert(Key != NULL && Value != NULL); 763 | writesection(LocalBuffer, Section, &wfp); 764 | writekey(LocalBuffer, Key, Value, &wfp); 765 | (void)ini_close(&wfp); 766 | return 1; 767 | } 768 | 769 | (void)ini_tell(&rfp, &mark); 770 | cachelen = 0; 771 | 772 | /* Move through the file one line at a time until a section is 773 | * matched or until EOF. Copy to temp file as it is read. 774 | */ 775 | len = (Section != NULL) ? (int)_tcslen(Section) : 0; 776 | if (len > 0) { 777 | do { 778 | if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) { 779 | /* Failed to find section, so add one to the end */ 780 | flag = cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); 781 | if (Key!=NULL && Value!=NULL) { 782 | if (!flag) 783 | (void)ini_write(INI_LINETERM, &wfp); /* force a new line behind the last line of the INI file */ 784 | writesection(LocalBuffer, Section, &wfp); 785 | writekey(LocalBuffer, Key, Value, &wfp); 786 | } 787 | return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ 788 | } 789 | /* Check whether this line is a section */ 790 | sp = skipleading(LocalBuffer); 791 | ep = _tcsrchr(sp, ']'); 792 | match = (*sp == '[' && ep != NULL); 793 | if (match) { 794 | /* A section was found, skip leading and trailing whitespace */ 795 | assert(sp != NULL && *sp == '['); 796 | sp = skipleading(sp + 1); 797 | assert(ep != NULL && *ep == ']'); 798 | ep = skiptrailing(ep, sp); 799 | match = ((int)(ep-sp) == len && _tcsnicmp(sp, Section, len) == 0); 800 | } 801 | /* Copy the line from source to dest, but not if this is the section that 802 | * we are looking for and this section must be removed 803 | */ 804 | if (!match || Key != NULL) { 805 | if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) { 806 | cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); 807 | (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); 808 | cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); 809 | } 810 | } 811 | } while (!match); 812 | } 813 | cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); 814 | /* when deleting a section, the section head that was just found has not been 815 | * copied to the output file, but because this line was not "accumulated" in 816 | * the cache, the position in the input file was reset to the point just 817 | * before the section; this must now be skipped (again) 818 | */ 819 | if (Key == NULL) { 820 | (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); 821 | (void)ini_tell(&rfp, &mark); 822 | } 823 | 824 | /* Now that the section has been found, find the entry. Stop searching 825 | * upon leaving the section's area. Copy the file as it is read 826 | * and create an entry if one is not found. 827 | */ 828 | len = (Key != NULL) ? (int)_tcslen(Key) : 0; 829 | for( ;; ) { 830 | if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) { 831 | /* EOF without an entry so make one */ 832 | flag = cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); 833 | if (Key!=NULL && Value!=NULL) { 834 | if (!flag) 835 | (void)ini_write(INI_LINETERM, &wfp); /* force a new line behind the last line of the INI file */ 836 | writekey(LocalBuffer, Key, Value, &wfp); 837 | } 838 | return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ 839 | } 840 | sp = skipleading(LocalBuffer); 841 | ep = _tcschr(sp, '='); /* Parse out the equal sign */ 842 | if (ep == NULL) 843 | ep = _tcschr(sp, ':'); 844 | match = (ep != NULL && len > 0 && (int)(skiptrailing(ep,sp)-sp) == len && _tcsnicmp(sp,Key,len) == 0); 845 | if ((Key != NULL && match) || *sp == '[') 846 | break; /* found the key, or found a new section */ 847 | /* copy other keys in the section */ 848 | if (Key == NULL) { 849 | (void)ini_tell(&rfp, &mark); /* we are deleting the entire section, so update the read position */ 850 | } else { 851 | if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) { 852 | cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); 853 | (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); 854 | cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); 855 | } 856 | } 857 | } 858 | /* the key was found, or we just dropped on the next section (meaning that it 859 | * wasn't found); in both cases we need to write the key, but in the latter 860 | * case, we also need to write the line starting the new section after writing 861 | * the key 862 | */ 863 | flag = (*sp == '['); 864 | cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); 865 | if (Key != NULL && Value != NULL) 866 | writekey(LocalBuffer, Key, Value, &wfp); 867 | /* cache_flush() reset the "read pointer" to the start of the line with the 868 | * previous key or the new section; read it again (because writekey() destroyed 869 | * the buffer) 870 | */ 871 | (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); 872 | if (flag) { 873 | /* the new section heading needs to be copied to the output file */ 874 | cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); 875 | } else { 876 | /* forget the old key line */ 877 | (void)ini_tell(&rfp, &mark); 878 | } 879 | /* Copy the rest of the INI file */ 880 | while (ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) { 881 | if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) { 882 | cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); 883 | (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); 884 | cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); 885 | } 886 | } 887 | cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); 888 | return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ 889 | } 890 | 891 | /* Ansi C "itoa" based on Kernighan & Ritchie's "Ansi C" book. */ 892 | #define ABS(v) ((v) < 0 ? -(v) : (v)) 893 | 894 | static void strreverse(TCHAR *str) 895 | { 896 | int i, j; 897 | for (i = 0, j = (int)_tcslen(str) - 1; i < j; i++, j--) { 898 | TCHAR t = str[i]; 899 | str[i] = str[j]; 900 | str[j] = t; 901 | } 902 | } 903 | 904 | static void long2str(long value, TCHAR *str) 905 | { 906 | int i = 0; 907 | long sign = value; 908 | 909 | /* generate digits in reverse order */ 910 | do { 911 | int n = (int)(value % 10); /* get next lowest digit */ 912 | str[i++] = (TCHAR)(ABS(n) + '0'); /* handle case of negative digit */ 913 | } while (value /= 10); /* delete the lowest digit */ 914 | if (sign < 0) 915 | str[i++] = '-'; 916 | str[i] = '\0'; 917 | 918 | strreverse(str); 919 | } 920 | 921 | /** ini_putl() 922 | * \param Section the name of the section to write the value in 923 | * \param Key the name of the entry to write 924 | * \param Value the value to write 925 | * \param Filename the name and full path of the .ini file to write to 926 | * 927 | * \return 1 if successful, otherwise 0 928 | */ 929 | int ini_putl(const TCHAR *Section, const TCHAR *Key, long Value, const TCHAR *Filename) 930 | { 931 | TCHAR LocalBuffer[32]; 932 | long2str(Value, LocalBuffer); 933 | return ini_puts(Section, Key, LocalBuffer, Filename); 934 | } 935 | 936 | #if defined INI_REAL 937 | /** ini_putf() 938 | * \param Section the name of the section to write the value in 939 | * \param Key the name of the entry to write 940 | * \param Value the value to write 941 | * \param Filename the name and full path of the .ini file to write to 942 | * 943 | * \return 1 if successful, otherwise 0 944 | */ 945 | int ini_putf(const TCHAR *Section, const TCHAR *Key, INI_REAL Value, const TCHAR *Filename) 946 | { 947 | TCHAR LocalBuffer[64]; 948 | ini_ftoa(LocalBuffer, Value); 949 | return ini_puts(Section, Key, LocalBuffer, Filename); 950 | } 951 | #endif /* INI_REAL */ 952 | #endif /* !INI_READONLY */ 953 | --------------------------------------------------------------------------------