vax is a custom module/code injection ecosystem for Nintendo Switch games.
It’s essentially a C++ port/improved version of SaltyNX.
Code injection procedure
This is the basic component of this project:
On the one hand, it periodically scans for new-started processes.
If the new process is an application and the SD card contains modules to load for that specific game, it hooks the game and starts with the injection process (explained below)
On the other hand, it hosts three separate ports:
vax:mod: this port interface is used by
vloaderand any other custom module. It serves two main purposes:
Acting as a privileged service manager: the
GetServiceHandle(...)command it serves is similar to SM’s one as it actually does that internally, but through this sysmodule’s full permissions.
- For services like FS, which have their own permission system which works by calling a certain command after accessing it, vax calls it by itself, returning to the module a FS service with full permissions.
Allowing various funcionality specific to modules: currently only consists on the
MemoryCopy(...)command, which works similar to a usual
memcpybut allowing addresses the module can’t directly interact with.
vax:ldr, port interfaces only meant to be accessed from
As a note, SaltyNX would previously serve all these purposes under the
SaltySDport and through a more primitive and unsafe IPC code. It’d also only provide SD card filesystem handles, while now this whole concept was redesigned to have whole full privileged services.
Resource limits and ports
Note that games tend to have (or just always have?) a maximum amount of ports which can be opened of
1, which is meant to open SM and regularly access other services.
Thus, for all the ports which are hosted and accessed through this project and custom modules, the procedure is always to open-port/call-desired-command/close-port, so that after calling the desired command the single port session available is not used by anything, all this before the game starts so the ports can be accessed one-by-one.
Another consequence of this is that custom modules (through replaced funcions being called later after the game starts) can no longer access
vax:mod, since games seem to (always do?) leave their SM session open, not allowing any more ports to be accessed. Thus, anything they might want to obtain from the port (like SD card access) must be done on the module entrypoint.
When a process injection starts,
vax finds random code regions in the game code and overwrites it with
vboot‘s segments, always after having made a backup of the original game code there. Then
vboot is started by adjusting the process’s main thread’s PC.
vboot was badly placed in the game’s memory (we randomly pasted it over game memory), its main purpose is to setup a basic heap through
SetHeapSize (note that the game didn’t even get to start so nothing was set up) where the actual injection entrypoint code,
vloader, will be placed and executed.
After setting up this basic heap (which
vloader will later extend to fit all modules to be loaded),
vloader there through
vax:boot interface commands, and then executes it.
This program is responsible for setting everything up and launching all custom modules.
First of all, the moment it starts it notifies
vboot finished through
vax:ldr commands, so that the game code backup which was overwritten by this program can be restored. It also registers all the existing game modules and sets up newlib/libnx to properly work.
As it’s explained below, this program uses an internal heap to be able to use standard libc(++) code.
After setting up the mentioned initial stuff, it proceeds to manually patch the target game’s
GetInfo SVCs to force the extra heap size we need for our modules when the game later attempts to initialize the heap, effectively extending it while keeping the module heap area untouched.
Then modules are loaded from
sdmc:/atmosphere/contents/<game-app-id>/vax/<any-elf-files>, and they get launched one-by-one.
After finishing with this, it finally jumps to the game’s startup code, normally starting the game with all the modules having done their job. Note that all modules and this program never get unloaded, since their code might be used later in-game (for instance, when replaced functions try to call module-code).
Custom modules must be compiled using
libvmod libs, which are the base libs for any kind of module. Even
vboot (partially) and
vloader have to make use of them, since they are the ones which set up everything correctly.
Custom modules have a certain set of limitations:
- Services must not be accessed the usual way: they must be obtained through
vax:mod(more specifically by
vmod::sf::GetService(...)or dedicated lib code, like
vmod::fsfor FS code)
- Thread/anything involving TLS must not be touched while in-game, but on the module’s main routine (which, as explained above, gets called before the game has even started) it might not suppose any problems. However, better to fully avoid using it.
- For instance, this implies that thread creation MUST be done using the game’s thread code.
- This is all due to libnx’s TLs and Nintendo’s TLS implementations being completely incompatible.
TODO: continue with this