Azahar exposes a UDP RPC server that lets you interact with a running 3DS game from a Python script. You can read and write memory, enumerate running processes, and switch the active process — all without modifying the emulator itself. This makes the scripting API useful for automated testing, research, and tool development.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/azahar-emu/azahar/llms.txt
Use this file to discover all available pages before exploring further.
Prerequisites
Before you can connect, you must enable the RPC server in Azahar’s settings. Once enabled, the server listens on UDP port45987 by default.
No package installation is required. Copy citra.py from the dist/scripting/ directory of the Azahar repository into your project and import it directly.
Connecting
Instantiate theCitra class to open a UDP socket. The constructor accepts an optional address and port.
IP address of the machine running Azahar.
UDP port the RPC server is listening on. Defined as
CITRA_PORT = 45987 in citra.py.API reference
is_connected()
Returns True if the underlying UDP socket is open. Because UDP is connectionless, this only confirms that the socket was created — not that Azahar is running or that the RPC server is enabled.
bool
process_list()
Returns a dictionary of all processes currently running in the emulator.
dict[int, tuple[int, str]] — maps each process_id to a tuple of (title_id, proc_name).
| Key | Type | Description |
|---|---|---|
process_id | int | Numeric process identifier |
title_id | int | 64-bit Nintendo title ID |
proc_name | str | ASCII process name, up to 8 characters |
get_process()
Returns the process ID that is currently selected for memory operations.
int — the current process ID, or None if the request fails.
set_process(process_id)
Sets the active process for subsequent memory read and write operations.
The process ID to select. Use
process_list() to find valid IDs.None
read_memory(read_address, read_size)
Reads bytes from the game’s memory at the given address. Internally splits the request into chunks of up to MAX_REQUEST_DATA_SIZE (1024) bytes.
The memory address to read from, as an integer (e.g.
0x100000).Number of bytes to read. Reads larger than 1024 bytes are handled automatically across multiple requests.
bytes on success, or None if the request fails.
write_memory(write_address, write_contents)
Writes bytes into the game’s memory at the given address. Internally splits large writes into chunks.
The memory address to write to, as an integer.
The raw bytes to write. Each chunk is limited to
MAX_REQUEST_DATA_SIZE - 8 (1016) bytes to leave room for the address and size fields in the request payload.bool — True if all chunks were written successfully, False if any chunk fails.
Working example
Protocol details
The RPC server uses UDP for all communication. Each packet consists of a fixed 16-byte header followed by an optional data payload.| Field | Type | Description |
|---|---|---|
version | uint32 | Protocol version (currently 1) |
request_id | uint32 | Random 32-bit ID used to match replies to requests |
request_type | uint32 | One of the RequestType enum values |
data_size | uint32 | Length of the data section in bytes |
| data | bytes | Request-specific payload |
Request types
| Name | Value | Description |
|---|---|---|
ReadMemory | 1 | Read bytes from a memory address |
WriteMemory | 2 | Write bytes to a memory address |
ProcessList | 3 | Enumerate running processes |
SetGetProcess | 4 | Get or set the active process |
Size limits
| Constant | Value | Description |
|---|---|---|
MAX_REQUEST_DATA_SIZE | 1024 bytes | Maximum data payload per request |
MAX_PACKET_SIZE | 1040 bytes | Maximum total packet size (header + data) |
The header is 16 bytes (
4 × uint32), so MAX_PACKET_SIZE = 1024 + 0x10 = 1040.