Skip to content

About

Maintained sys-agent: Switch automation sysmodule with Atmosphere-compatible process memory access, exact and multi-pass searches, SD-card FTP, remote system status/control, and verified one-shot Hekate emuMMC reboot.

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

 
 

Repository files navigation

sys-agent

sys-agent icon

This fork is an independently maintained sys-agent build based on upstream v2.5. It keeps the original TCP protocol compatible while adding Switch-side memory search and a unified process-memory backend that can coexist with Atmosphère cheats.

Custom-build warning: this is not an official upstream release. It can read and write game-process memory and runs as an Atmosphère sysmodule. Back up the existing atmosphere/contents/43000000000000A6 directory before installing, and use it only on a console where you understand the risks.

Project scope and philosophy

sys-agent is a generic Switch automation and research toolbox. Every feature here is process- and game-agnostic: it targets "the foreground application" or an explicit process id, never a specific title. Game-specific offsets, hook layouts, signatures, and state tables belong in consumer projects, never in this sysmodule. New commands should follow this rule:

  • Addresses, sizes, and values are plain numeric arguments (0x/0X hex or decimal) or hex byte pairs; nothing is hard-coded to a particular game's layout.
  • The single exception to "target the foreground application" must be an explicit pid= argument, not an implicit title check.
  • Transactional operations (like debug patch-code) must own their full lifecycle: pause/verify/write/readback/resume in one command, with resume guaranteed on every error path, so clients never manage debugger state themselves.
  • Parse failures return explicit ERR code= lines; never silently proceed with a wrong value (hex payloads are hex only, odd length or non-hex characters are rejected).
  • Out-of-process by design: sys-agent reads, writes, queries mappings, and sets breakpoints from outside the target, but it does not run code inside it. Calling a function in the target's own address space and threads is the in-process agent's job; the two are complementary, not substitutes.

Changes in this fork

  • A unified ProcessMemoryBackend routes legacy peek/poke, multi, pointer, freeze, metadata, and search operations through one selected backend.
  • The default auto policy prefers Atmosphère's dmnt:cht service, sharing dmnt's existing debug handle instead of attempting a conflicting second svcDebugActiveProcess attachment.
  • dmnt and direct policies are available for diagnostics. Direct-debug handles are scoped to an operation and released afterward. debug watch / debug force-close may close the dmnt-owned debug handle when a hardware watchpoint is needed; the next memory command re-attaches dmnt automatically.
  • Asynchronous exact and typed u8/u16/u32/u64 memory search supports absolute, main, and heap-relative ranges, alignment, progress, cancellation, sessions, and paged results.
  • A hardware watchpoint (debug watch) reports the PC of the instruction that writes a watched address, with per-core debug registers linked through a context-IDR breakpoint. It auto-resolves the dmnt-owned debug-handle conflict (dmntClosed=1), and a manual debug force-close is available; memory commands are rejected while a watch is armed.
  • Verified memory access: poke*Verified writes and reads the range back, reporting WRITE_FAILED, READBACK_FAILED, or WRITE_VERIFY_FAILED distinctly; peek*Verified reads twice and retries until two reads agree, so zero data and a failed read are distinguishable. memoryQuery reports the covering mapping (svcQueryMemory semantics), memoryHash returns an FNV-1a 32 fingerprint with a per-chunk retry, and the client memory dump streams a region to a local file in verified chunks.
  • The watchpoint gained ring N (keep the last N hits without auto-detaching), pc=LOW-HIGH (keep only matching hits), debug watch-ring <i>, and watch-status flags stale (fields left over from a finished session) and memoryBlocked (memory commands are refused while armed). log lists the recent commands the console received.
  • The Python client retries read-only commands on an empty response or dropped connection (writes never retry), adds game wait / memory wait-value, --json / --out, and screen capture --diff, translates common native Result codes, and returns typed MemoryInfo / GameStatus results alongside the raw dicts.
  • FTP's RNTO replaces an existing file, and ftpStatus reports listener, bindAttempts, and lastBindError so a listener that never came up is distinguishable from a network problem.
  • A transactional code-patch primitive (debug patch-code) pauses the target process, optionally verifies no thread PC is inside the patched range, verifies the original bytes, writes any payload up to 1 KiB, reads it back, and resumes — one command, with resume guaranteed on every error path. It targets the foreground application by default or any process via pid=. Cache maintenance for the target is handled by the kernel's debug-write path; the primitive never issues cache ops on a target address.
  • Numeric and payload parsing is strict: byte payloads are hex pairs only (optional 0x, odd length or non-hex characters rejected with INVALID_HEX_PAYLOAD); plain numbers are 0x/0X-hex or decimal and invalid strings parse as 0 instead of a silent partial value; unknown button tokens send no button.
  • SD-backed unknown-value sessions support exact, changed, unchanged, increased, and decreased multi-pass refinement without keeping millions of candidates in the sysmodule heap.
  • Search sessions pin their process ID and backend, preventing a running scan from silently switching to a different process or debug owner.
  • An isolated, low-priority FTP worker exposes the SD card at ftp://switch:6001 while a game remains open. It supports normal file and directory CRUD and can be controlled over the existing sys-agent TCP connection.
  • Grouped system-management commands report system, power, storage, network, account, application, and process state. Additive actions cover normal reboot/shutdown, wireless and lock-screen settings, foreground-application termination, and one-shot Hekate emuMMC reboot.
  • The system-management group also carries the boot unlock: an armed sysmodule looks at the screen after a reboot and presses HOME until Horizon's lock screen ("A 开始" / "A 继续") is gone, so it cannot time out into sleep and cut the console off. The judgement is picture analysis on a DC-only JPEG decode (a flat patch on the right is a lock screen, a flat left third means it wants the same button three times), and HOME is the button because it is inert on every screen involved - A would start a game on the HOME menu. It is off unless asked for (bootUnlock arm), the schedule and the arming live on the SD card, and bootUnlock (plus a log on the SD card) reports what it saw and did.
  • The original controller, screen-capture, and memory commands remain available. New commands are additive so existing clients can continue to ignore capabilities they do not use.

See commands.md, the memory-backend design, the exact-search design, and the unknown-search design for protocol and implementation details. FTP architecture, validation results, known filename limitations, and remaining hardware acceptance checks are recorded in the FTP server notes.

A Nintendo Switch (CFW) sysmodule that allows users to remotely control their Switch over a TCP socket and read or write game memory. It can be used for bots, automation, and controlled memory research.

Security warning: the command socket on port 6000 is unauthenticated. The explicitly enabled networkProfile command returns the current Wi-Fi passphrase, while other commands return serial and account identifiers or change system state. Expose this port only on a trusted isolated network and restrict it with the surrounding network firewall.

Features:

Remote Control:

  • Set controller state
  • Simulate buttons press, hold, and release
  • Simulate touch screen drawing

Memory Reading and Writing:

  • Read/write x amount bytes of consecutive memory from RAM based on:
    1. Absolute memory address
    2. Address relative to main nso base
    3. Address relative to heap base
  • Verified access: poke*Verified writes then reads the range back and reports WRITE_FAILED, READBACK_FAILED, or WRITE_VERIFY_FAILED distinctly; peek*Verified reads twice and retries until two reads agree, so "the value is zero" is distinguishable from a failed read.
  • memoryQuery reports the mapping covering an address (type/permission, svcQueryMemory semantics); memoryHash returns an FNV-1a 32 fingerprint of a region; the client memory dump streams a region to a local file in verified chunks.
  • Share Atmosphere dmnt's active debug handle so memory commands and searches can coexist with the cheat VM, with an explicit direct-debug fallback.
  • Run asynchronous exact and typed memory searches with progress, cancellation, and paged results.
  • Hardware write watchpoints (debug watch / watch-status / watch-last / watch-stop) capture the writer's PC, registers, and instruction bytes. ring N keeps the last N hits without auto-detaching and pc=LOW-HIGH keeps only matching hits; debug watch-ring <i> dumps a stored hit.
  • log lists the recent commands the console received, with a system tick.

Screen Capture:

  • Capture current screen and return as JPG

SD card FTP

The custom build starts an anonymous FTP server on port 6001 by default. Its root is the SD card root; it does not expose BIS, save data, gamecard, or other system mounts.

The FTP core runs as one event-driven worker thread and accepts up to four simultaneous client sessions. Connections and transfers are polled and advanced in turns; they do not receive one thread each and multiple large transfers do not run in true CPU-parallel fashion. Concurrent transfers also share Wi-Fi and SD-card bandwidth.

curl ftp://switch:6001/
printf 'ftpStatus\r\n' | nc -w 3 switch 6000
printf 'ftpStop\r\n' | nc -w 3 switch 6000
printf 'ftpStart\r\n' | nc -w 3 switch 6000

Anonymous mode permits uploads, renames, and deletion across the complete SD card. This is intended for a trusted development network. For account access, copy config/ftp.ini.template to /config/sys-agent/ftp.ini on the SD card, set anonymous=0, provide both credentials, and run ftpReload. Invalid credentials or ports leave FTP in an error state without stopping the controller or memory service.

Uploads above 4 GiB work: FAT32 cannot hold one file that large, so the upload is converted, as metadata only, into the split form the console and the PC-side "split for FAT32" tools both use -- one entry that every program reads as one file, with parts 00, 01, ... inside it. A resumed upload (REST followed by STOR) is refused with 550 rather than served, because this FTP core's STOR truncates: the client restarts the file from the beginning instead of uploading on top of a file whose head would be silently lost. Downloads resume normally, and a conversion interrupted by a power cut cleans up after itself on the next upload of that name. See the FTP server notes for the measurements.

ASCII filenames, including spaces, are the supported stable baseline. On the tested Horizon SD filesystem, Chinese and Japanese names return native FS 0x202, while some other non-ASCII names can be opened but are not returned reliably by directory enumeration. The server does not silently rewrite names; use an ASCII temporary or final filename when transferring such files.

Do not run old sys-ftpd on port 6001. Sphaira normally uses port 5000, so both can coexist, although two writers changing the same file is unsafe. For Atmosphere deployment, upload to a temporary filename and rename it only after the transfer completes. During an active C-level search, FTP modifications below /switch/sys-agent/search are rejected to protect the transactional snapshot.

Disclaimer:

This project was created for the purpose of development for bot automation. The creators and maintainers of this project are not liable for any damages caused or bans received. Use at your own risk.

Installation

  1. Download the ZIP from this fork's latest release.

  2. Power off the Switch and back up the existing directory: atmosphere/contents/43000000000000A6.

  3. Extract the ZIP into the root of the SD card. The final files must be:

    atmosphere/contents/43000000000000A6/exefs.nsp
    atmosphere/contents/43000000000000A6/flags/boot2.flag
    atmosphere/contents/43000000000000A6/toolbox.json
    
  4. Safely eject the SD card and fully restart the Switch. Restarting only the game is not sufficient when replacing a running sysmodule.

  5. Verify the service over TCP, for example:

    python3 client/sysagent.py --host switch version   # client version + what the console reports
    printf 'getVersion\r\n' | nc -w 3 switch 6000
    printf 'memoryBackendProbe\r\n' | nc -w 3 switch 6000
    printf 'searchCapabilities\r\n' | nc -w 3 switch 6000

When installed correctly, sys-agent will make the docked Joy-Con HOME button glow during Switch startup. If this does not happen, check the directory layout and boot2.flag.

Upgrade and rollback

To upgrade, replace the complete 43000000000000A6 directory with the one from the new release and fully restart the console. To roll back, restore the directory backed up before installation and restart again. Do not mix exefs.nsp from one release with packaging files from another release.

Deploy from source

deploy_sysagent.py updates an existing sys-agent installation on a running Switch. It builds the current tree with the pinned Docker image, then installs the new build over the built-in FTP server and reboots into emuMMC — without removing the SD card. It requires that sys-agent is already installed and running on the Switch, because it relies on the built-in FTP server (port 6001) and the reboot command (port 6000). For a first-time installation on a console without sys-agent, use the manual steps in Installation instead.

python3 deploy_sysagent.py
python3 deploy_sysagent.py --no-build --dry-run   # preview only, no Switch access

The update uploads the new exefs.nsp and toolbox.json under temporary ASCII names, verifies the byte counts, renames them atomically over the running files, requests systemRebootEmuMMC to reboot directly into the virtual system (pass --normal-reboot for a plain reboot), and waits until the console answers a command round-trip before checking that the new build advertises audio=volume,mute. --verify-only performs just that wait-and-check on a console that is already rebooting. The script intentionally does not back up the Switch-side sys-agent; to roll back, re-deploy an older build or edit the SD card manually.

Build with Docker

This branch is built with the pinned official devkitPro image:

docker run --rm \
  --platform linux/amd64 \
  -v "$PWD:/work" \
  -w /work \
  devkitpro/devkita64:20260219 \
  bash -lc 'source /opt/devkitpro/switchvars.sh && make clean && make && make test'

Clone with --recurse-submodules, or run git submodule update --init --recursive before the first build. The build uses the checked-out pinned FTP source and does not download dependencies.

The build produces sys-agent/sys-agent.nsp and refreshes the ignored installation tree at sys-agent/43000000000000A6. The NSP is copied there as exefs.nsp.

Unknown-search snapshots are runtime-only temporary data under /switch/sys-agent/search on the SD card. They are removed when the session closes or the sysmodule starts again. Do not store personal files in that directory.

Credits

  • Big thank you to jakibaki for a great sysmodule base to learn and work with, as well as being helpful on the Reswitched discord!
  • Thanks to RTNX on discord for bringing to my attention a nasty little bug that would very randomly cause RAM poking to go bad and the switch (sometimes) crashing as a result.
  • Thanks to Anubis for stress testing!
  • Thanks to the Atmosphere project for documenting and providing the dmnt:cht service used by the unified process-memory backend.

About

Maintained sys-agent: Switch automation sysmodule with Atmosphere-compatible process memory access, exact and multi-pass searches, SD-card FTP, remote system status/control, and verified one-shot Hekate emuMMC reboot.

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages