Modular C++ toolchain and set of libraries for userspace Nintendo Switch processes with 64-bit and 32-bit support. This is the library repository to be cloned as submodule into a project. An example project can be found here
- Clang/LLVM toolchain, linking musl libc + LLVM libc++
- Can be used for standalone programs, modification of existing ones, NROs, and homebrews
- Compatible with RTLD
- Bundled with RTLD reimplementation [WIP]
- Compatible with programs that statically link RTLD into their main executable
- Runtime Replace, Trampoline, B/BL hooking, RW access to code memory
- Sophisticated symbol sourcing system with proper error reporting and compatibility across multiple program target versions
- Constexpr AArch64 assembler
- Abort/Assert util that prints text visible in Atmosphère crash reports
- Symbols imported dynamically can be stripped from final binary
- Framework and user code clearly separated with a submodule to prevent messy codebases and forks
- Multiple addons with additional functionality:
- Rendering:
- Memory Allocation:
- SF client implementation [WIP]:
- SM, PM, LM, Socket
- CMake + GNUMake or Ninja
- cURL
- Clang, LLVM, LLD 19 or later
- Python 3.10,
pyelftools,mmh, andlz4packages
This part can be skipped, since these scripts are ran automatically when needed.
You can either use a prepackaged stdlib, or compile one yourself (pass 'aarch32' as argument to either of the scripts if using 32-bit):
Run tools/setup_libcxx_prepackaged.py from your repository's root to download a pre-packaged stdlib. (~20MiB)
After setting up a repository in a similar fashion to the example repository, run tools/setup_libcxx.py from your repository's root to download and compile musl libc and LLVM libc++. (~1.7GiB)
Run tools/setup_sail.py from your repository's root to compile sail, the tool used to parse .sym files for symbol sourcing.
With a CMakeLists.txt setup similar to the example repository, the project can be built by running CMake from the build folder and calling the build system:
mkdir buildcd buildcmake .. -DCMAKE_BUILD_TYPE=Debug(or Release)- Run
makeorninjadepending on which you used in the above command
Hakkun provides 2 ways of deploying the built files into a usable structure, besides manually copying from the build folder:
By default, binaries are output into the sd folder in the build folder in Atmosphère's LayeredFS structure/the switch folder:
sd
├── atmosphere
│ └── contents
│ └── 0100000000010000
│ └── exefs
│ ├── main.npdm
│ └── subsdk4
└───switch
└── YourProject.nro
If following environment variables are set, Hakkun will automatically attempt to upload files to an FTP server after linking:
HAKKUN_FTP_IP: IP (e.g. 192.168.188.151, my switch)HAKKUN_FTP_PORT: Port (optional, 5000 by default)HAKKUN_FTP_USER: User (optional)HAKKUN_FTP_PASS: Password (optional)
A GitHub workflow template can be found in .github/workflow_templates/build.yml.
Hakkun provides various options that you can configure from config/config.cmake within your repository:
LINKFLAGS: Flags passed to compiler linking commandLLDFLAGS: Flags passed to linkerOPTIMIZE_OPTIONS_DEBUG: Optimization options for Debug modeOPTIMIZE_OPTIONS_RELEASE: Optimization options for Release modeWARN_OPTIONS: Warn optionsDEFINITIONS: Preprocessor definitionsINCLUDES: Include directoriesASM_OPTIONS,C_OPTIONS,CXX_OPTIONS: Various compiler optionsUSE_ADVANCED_RESULT: Whether to enable stack trace building in hk::Result. Use hk::ResultNN to mask out extra data for compatibility with nn::ResultTRAMPOLINE_LEVEL: Level for trampoline hook.- 0: only basic backups (no b.cond, cbz/cbnz, tbz/tbnz), +-128MB branch range
- 1: all backups, +-128MB branch range
- 2: all backups, infinite branch range
HAKKUN_ADDONS: List of Hakkun addons to enableIS_32_BIT: Whether or not target is 32-bitHAKKUN_TARGET: Type of program, MODULE by defaultMODULE: Default RTLD module (e.g. for game mods)MODULE_STANDALONE: Standalone main module providing nn::init::Start + rtld module, packs an exefs.nsp (e.g. for sysmodules)MODULE_DLL: Outputs as a dynamic-link module (NRO) (e.g. for homebrews)
HOMEBREW_TYPE: Type of homebrew loader to targetNONE: DefaultHBLOADER: Target nx-hbloader (libnx)
MODULE_NAME: Name of your output RTLD moduleTITLE_ID: Title ID of the target programMODULE_BINARY: ExeFS slot for your output module (can be sdk, or subsdk0-subsdk9)SDK_PAST_1900: Enable if RTLD version of target program is from SDK 19.0.0 or later, usually the case with titles updated in or later than late 2024TARGET_IS_STATIC: Whether or not target program has statically linked rtld/sdk. Usually sysmodules or applets do this, Applications do not. Enabling this will also add a dummy RTLD module, to work around an unfortunate decision inloaderUSE_SAIL: Whether or not to use sail. If disabled, you can dynamic link normallyBAKE_SYMBOLS: Whether or not to 'bake' symbols provided by sail. Baking will replace all string references to symbols with hashes, reducing binary size at the expense of harder debugging
Sail reads the following configuration file:
List of modules with versions bound to NSO build IDs, if versioning is desired, for example:
@rtld = nnrtld
@smo = RedStar.nss
100 = 3ca12dfaaf9c82da064d1698df79cda1
101 = 50ade4b5eb6e45efb170a6b230d3b0ba
110 = 948dbbfc2fa0c60e2c30316e4c961aba
120 = f5dccddb37e97724ebdbcccdbeb965ff
130 = b424be150a8e7d78701cbe7a439d9ebf
@multimedia
@sdk = nnSdk
100 = ae34e75d02925f4417b24499ad80c39412fc76db
101 = ae34e75d02925f4417b24499ad80c39412fc76db
110 = ae34e75d02925f4417b24499ad80c39412fc76db
120 = ae34e75d02925f4417b24499ad80c39412fc76db
130 = fcf93a41b08c5193a1410d262602dc621617c56f
config/npdm.json configures the NPDM file required to grant certain SVC permissions needed.
The following SVC permissions are used and need to be allowed for Hakkun to function:
"svcQueryMemory": "0x06",
"svcBreak": "0x26",
"svcOutputDebugString": "0x27",
"svcGetInfo": "0x29",
"svcMapProcessMemory": "0x74",
Additionally, the following SVC permissions are needed in 32-bit mode:
"svcInvalidateProcessDataCache": "0x5d",
"svcFlushProcessDataCache": "0x5f",
Hakkun provides sail, a tool that allows parsing of a symbol language contained within .sym files in the sym/ directory of your repository, for the purpose of sourcing symbols across different versions of programs. After configuring ModuleList.sym and VersionList.sym, the following ways of adding symbols can be used:
// immediate symbols
@game:100
coolsymbol = 0x1234
coolsymbol_2 = 0x1238
@game:110 // different version
coolsymbol = 0x1334
coolsymbol_2 = 0x1338
// dynamic symbols:
@sdk:100,110 // multiple versions
_ZN2nn3abc7acquireEv = "_ZN2nn3abc7acquireEv"
_ZN2nn3abc7acquireEv // compact
_ZN2nn3abc8OneThingEv = "_ZN2nn3abc12AnotherThingEv" // different
// data search blocks
// version doesn't matter for data search
some_cool_sead_thingy = 0123456789ABCDEF @ game + 0x20 // < some instructions
some_other_sead_thingy = 0123456789ABCDEF @ game - 0x20
some_another_sead_thingy = 0?23????89AB???F @ game - 0x20 // masking
// data may change
some_other_sead_thingy2 = FEDCBA9876543210 @ game<110 - 0x20 // before 110
some_other_sead_thingy3 = 0123456789ABCDEF @ game>110 - 0x20 // at and after 110
// optimization
some_shit = 0123456789ABCDEF @ game:text + 0x20 // only search .text section
some_balls = 01234567 @ game:rodata // no addend
// addend
a_close_symbol = some_balls + 0x1000
another_close_symbol = a_close_symbol - 0x124
// ReadADRPGlobal
$some_adrp_ldr_instruction = 0123456789ABCDEF @ game - 0x20
some_global_variable = readAdrpGlobal($some_adrp_ldr_instruction)
some_global_variable = readAdrpGlobal($some_adrp_ldr_instruction, 0x2) // 0x2 indicates the offset of the LDR instruction from the ADRP instructionSymbols can be accessed directly through linking, or through hk::util::lookupSymbol.
using namespace hk;
// trampoline hook
HkTrampoline myHook = [](TrampolineStatic(), void* something) -> int /* don't forget the return type! */ {
int value = orig(something); // call original function
// do something ...
return value;
};
static void test() { }
// executed at boot
extern "C" void hkMain() {
// trampoline/replace
myHook.installAtSym<"SomeFunction">(); // install to symbol provided by sail
myHook.uninstall(); // hooks can be uninstalled
myHook.installAtOffset(ro::getMainModule(), 0x1234); // by offset (not recommended)
myHook.uninstall();
myHook.installAtPtr(BlahFunc);
myHook.installAtPtr(&BlahClass::Func);
// replace hooks are also a thing, use HkReplace instead of HkTrampoline
// b/bl branching to function
hook::writeBranchLinkAtSym<"SomeFunction2">(test);
hook::writeBranchAtPtr(1234, test);
// writing to module
ro::getMainModule()->writeRo<u32>(/* offset */ 0x12345678, /* u32 value */ 0xfefefefe);
// constexpr AArch64 assembler
hook::a64::assemble<"add x0, x0, {}">()
.arg(1234)
.installAtMainOffset(0x007feb88)
.installAtSym<"Blablablabla">();
hook::a64::assemble<"nop", true /* Uninstallable */>()
.installAtMainOffset(0x12345678)
.uninstall();
}- shadowninja108
- Sanae6 for sf implementation
- marysaka for oss-rtld
- tetraxile
- GLOSHSEP
Read the code of conduct for rules in engaging with this project.
This project is licensed under the 2-Clause BSD License in LICENSE.BSD2, as well as under the custom license in LICENSE.TDM.
The 2 licenses named above do not apply to the addons/ExpHeap subdirectory, it is individually licensed under the GNU GENERAL PUBLIC LICENSE Version 2 in addons/ExpHeap/LICENSE.
