Syscalls
When the Spectranet ROM is paged in, it exposes fixed three-byte jump-table entries from $3E00 to $3EF0. Use the symbols from spectranet.inc rather than literal addresses.
Reaching a vector
On original Spectranet, PAGEIN ($3FF9) must map the ROM before a raw jump-table call. HLCALL ($3FFA) jumps to the address in HL and IXCALL ($3FFD) jumps to the address in IX; both arrange a return through PAGEOUT ($007C). Spectranext traps an instruction fetch in $3FF8–$3FFF, so call IXCALL and call HLCALL work without an earlier explicit PAGEIN.
ld ix, OPEN
call IXCALL
Full jump table reference ($3E00 … $3EF0)
Every symbol links to its ABI entry below. Unless stated otherwise, carry set means failure and A contains the error code; success clears carry. Registers not named as outputs are volatile.
Socket and network calls
SOCKET ($3E00)
Allocates a network socket. Input: B = protocol/type; C = socket flags. Output: A = allocated file descriptor.
CLOSE ($3E03)
Closes a socket. Input: A = socket file descriptor. Output: none.
LISTEN ($3E06)
Places a bound TCP socket into listening state. Input: A = socket file descriptor. Output: none.
ACCEPT ($3E09)
Accepts a pending connection. Input: A = listening socket file descriptor. Output: A = newly allocated connected socket file descriptor.
BIND ($3E0C)
Binds a local port. Input: A = socket file descriptor; DE = host-order port. Output: none.
CONNECT ($3E0F)
Connects a socket to a remote endpoint. Input: A = socket file descriptor; DE = pointer to four-byte IPv4 address; BC = host-order port. Output: none.
SEND ($3E12)
Sends bytes on a stream socket. Input: A = socket file descriptor; DE = source buffer; BC = byte count. Output: BC = bytes sent.
RECV ($3E15)
Blocks until data is available, then receives bytes. Input: A = socket file descriptor; DE = destination buffer; BC = maximum byte count. Output: BC = bytes received.
SENDTO ($3E18)
Sends a datagram. Input: A = socket file descriptor; HL = pointer to eight-byte socket-address structure; DE = source buffer; BC = byte count. Output: BC = bytes sent.
RECVFROM ($3E1B)
Receives a datagram and its peer address. Input: A = socket file descriptor; HL = eight-byte address-result buffer; DE = destination buffer; BC = maximum byte count. Output: BC = bytes received; address structure written through HL.
POLL ($3E1E)
Checks a list of sockets for an event. Input: DE = file-descriptor array; B = number of entries. Output: A = first ready descriptor and B = event flags, or A = 0 with Z set when none are ready.
POLLALL ($3E21)
Checks all open sockets. Input: none. Output: A = ready file descriptor and B = event flags, or A = 0 with Z set when none are ready.
POLLFD ($3E24)
Checks one socket without blocking. Input: A = socket file descriptor. Output: C = event flags when ready; Z set when it is not ready.
GETHOSTBYNAME ($3E27)
Resolves a hostname with DNS. Input: HL = NUL-terminated hostname; DE = four-byte IPv4 result buffer. Output: IPv4 address written through DE.
UI, conversion, and interface calls
PUTCHAR42 ($3E2A)
Writes one character using the 42-column UI. Input: A = character. Output: none.
PRINT42 ($3E2D)
Writes a NUL-terminated 42-column UI string. Input: HL = string. Output: none.
CLEAR42 ($3E30)
Clears the Spectranet UI screen. Input: none. Output: none.
SETPAGEA ($3E33)
Maps a page into page A ($1000–$1FFF). Input: A = page number. Output: none.
SETPAGEB ($3E36)
Maps a page into page B ($2000–$2FFF). Input: A = page number. Output: none.
LONG2IPSTRING ($3E39)
Formats a four-byte IPv4 address as dotted decimal. Input: HL = address bytes; DE = destination string buffer. Output: NUL-terminated string through DE.
IPSTRING2LONG ($3E3C)
Parses dotted-decimal IPv4 text. Input: HL = NUL-terminated string; DE = four-byte result buffer. Output: IPv4 address through DE.
ITOA8 ($3E3F)
Formats an unsigned byte in decimal. Input: A = value; HL = destination buffer. Output: NUL-terminated decimal text through HL.
RAND16 ($3E42)
Returns a pseudo-random 16-bit value. Input: none. Output: HL = random value.
REMOTEADDRESS ($3E45)
Returns a connected socket's peer address. Input: A = socket file descriptor; DE = eight-byte socket-address buffer. Output: address structure through DE.
IFCONFIG_INET ($3E48)
Sets the interface IPv4 address. Input: DE = four-byte IPv4 address. Output: none.
IFCONFIG_NETMASK ($3E4B)
Sets the interface netmask. Input: DE = four-byte IPv4 address. Output: none.
IFCONFIG_GW ($3E4E)
Sets the interface gateway. Input: DE = four-byte IPv4 address. Output: none.
INITHW ($3E51)
Initialises network hardware. Input: none. Output: none.
GETHWADDR ($3E54)
Reads the interface MAC address. Input: DE = six-byte destination buffer. Output: MAC address through DE.
DECONFIG ($3E57)
Clears interface configuration. Input: none. Output: none.
MAC2STRING ($3E5A)
Formats a six-byte MAC address. Input: HL = MAC bytes; DE = destination string buffer. Output: NUL-terminated text through DE.
STRING2MAC ($3E5D)
Parses a MAC-address string. Input: HL = NUL-terminated text; DE = six-byte result buffer. Output: MAC bytes through DE.
ITOH8 ($3E60)
Formats an unsigned byte as two hexadecimal characters. Input: A = value; HL = destination buffer. Output: NUL-terminated text through HL.
HTOI8 ($3E63)
Parses up to two hexadecimal characters. Input: HL = text. Output: A = byte value.
GETKEY ($3E66)
Reads a key from the UI input path. Input: none. Output: A = key code.
KEYUP ($3E69)
Reports key state. Input: none. Output: A = key state.
INPUTSTRING ($3E6C)
Reads an editable NUL-terminated string. Input: HL = destination buffer; B = maximum length. Output: string through HL.
GET_IFCONFIG_INET ($3E6F)
Reads the configured IPv4 address. Input: DE = four-byte destination buffer. Output: address through DE.
GET_IFCONFIG_NETMASK ($3E72)
Reads the configured netmask. Input: DE = four-byte destination buffer. Output: address through DE.
GET_IFCONFIG_GW ($3E75)
Reads the configured gateway. Input: DE = four-byte destination buffer. Output: address through DE.
Trap, paging, BASIC, and allocation calls
SETTRAP ($3E78)
Installs the programmable paging trap. Input: HL = trap routine address; A = trap configuration. Output: none; control transfers to the trap when it fires.
DISABLETRAP ($3E7B)
Disables the programmable trap. Input: none. Output: none.
ENABLETRAP ($3E7E)
Enables the programmable trap. Input: none. Output: none.
PUSHPAGEA ($3E81)
Saves the page-A mapping and maps another page. Input: A = page number. Output: none; matching POPPAGEA consumes the saved page from the stack.
POPPAGEA ($3E84)
Restores the previous page-A mapping. Input: stack contains the saved page. Output: none.
PUSHPAGEB ($3E87)
Saves the page-B mapping and maps another page. Input: A = page number. Output: none; matching POPPAGEB consumes the saved page from the stack.
POPPAGEB ($3E8A)
Restores the previous page-B mapping. Input: stack contains the saved page. Output: none.
PAGETRAPRETURN ($3E8D)
Completes a page-trap handler and restores saved paging. Input: trap stack/state. Output: no normal register result; returns to trapped code.
TRAPRETURN ($3E90)
Completes a non-paging trap. Input: trap stack/state. Output: no normal register result; returns to trapped code.
ADDBASICEXT ($3E93)
Registers a BASIC extension descriptor. Input: HL = extension descriptor. Output: none.
STATEMENT_END ($3E96)
Validates that a BASIC extension statement has no unwanted trailing text. Input: BASIC parser state. Output: returns only at statement end; otherwise reports a parse error.
EXIT_SUCCESS ($3E99)
Returns successfully from a BASIC extension. Input: BASIC extension return state. Output: no normal register result; resumes BASIC.
PARSE_ERROR ($3E9C)
Reports a BASIC parser error and exits the extension. Input: A = BASIC error code. Output: no normal register result.
RESERVEPAGE ($3E9F)
Reserves a free Spectranet RAM page. Input: none. Output: A = allocated page number.
FREEPAGE ($3EA2)
Returns a reserved RAM page to the pool. Input: A = page number. Output: none.
REPORTERR ($3EA5)
Reports a Spectranet error. Input: A = error code. Output: no normal register result.
VFS calls
The calls below dispatch to the selected VFS mount. Paths are NUL-terminated. Structures and flag values are documented in libspdos.
MOUNT ($3EA8)
Mounts a filesystem. Input: A = mount point; HL = filesystem-specific mount descriptor. Output: none.
UMOUNT ($3EAB)
Unmounts a filesystem. Input: A = mount point. Output: none.
OPENDIR ($3EAE)
Opens a directory. Input: A = mount point; HL = path. Output: A = directory handle.
OPEN ($3EB1)
Opens a file. Input: A = mount point; HL = path; DE = open flags; BC = mode. Output: A = file handle.
UNLINK ($3EB4)
Deletes a file. Input: A = mount point; HL = path. Output: none.
MKDIR ($3EB7)
Creates a directory. Input: A = mount point; HL = path; BC = mode. Output: none.
RMDIR ($3EBA)
Removes an empty directory. Input: A = mount point; HL = path. Output: none.
SIZE ($3EBD)
Returns filesystem capacity. Input: A = mount point. Output: DEHL = 32-bit total size.
FREE ($3EC0)
Returns free filesystem capacity. Input: A = mount point. Output: DEHL = 32-bit free size.
STAT ($3EC3)
Retrieves file metadata. Input: A = mount point; HL = path; DE = stat-result buffer. Output: metadata through DE.
CHMOD ($3EC6)
Changes file mode/attributes. Input: A = mount point; HL = path; DE = mode. Output: none.
For normal VFS mounts, the mode is passed to the mounted filesystem. On Spectranext's local XFS overlay, bit 15 ($8000, decimal 32768) is the commit-to-flash attribute: it copies a RAM-backed file or directory into persistent flash storage and removes the RAM source. This is an XFS-specific extension, not POSIX permission handling, and is valid only for a file that currently lives in the writable RAM layer. See Committing files for the overlay behaviour, examples, and persistence rules.
READ ($3EC9)
Reads from an open file. Input: A = file handle; DE = destination buffer; BC = maximum byte count. Output: BC = actual bytes read; zero is end of file.
WRITE ($3ECC)
Writes to an open file. Input: A = file handle; HL = source buffer; BC = byte count. Output: BC = actual bytes written.
LSEEK ($3ECF)
Changes an open file's position. Input: A = file handle; DEHL = signed 32-bit offset; C = origin (0 set, 1 current, 2 end). Output: DEHL = resulting position.
On HTTP(S) filesystems, seeking is supported when
the GET response has a known size strictly below 256 KiB (262,144 bytes).
These files are downloaded in full before OPEN completes and retained until
VCLOSE. All three seek origins are supported; seeks past EOF clamp to the
file size. Empty files also support seeking.
Files of 256 KiB or larger, and responses with an unknown size, use a
circular buffer for sequential reads. Consumed bytes are discarded, so
LSEEK fails with carry set and A = EINVAL, including attempts to rewind
to zero.
VCLOSE ($3ED2)
Closes an open VFS file. Input: A = file handle. Output: none.
VPOLL ($3ED5)
Polls a VFS handle. Input: A = file handle. Output: A = availability/status from the mounted filesystem.
READDIR ($3ED8)
READDIR has three forms, selected by DE and C. All three use A as the directory handle returned by OPENDIR. The directory position is a four-byte, little-endian filesystem cookie; it is deliberately opaque to callers. Do not increment it or construct one yourself—only save a cookie returned by TELLDIR and later provide that exact value to SEEKDIR.
Directory entry read
Returns the next entry in the open directory stream.
- Input: A = directory handle; DE = non-zero pointer to the directory-entry buffer.
- Output: entry data written at DE; carry clear.
- End of directory: carry set and A =
EODIR($21).
The mounted filesystem defines the entry structure. Its leading field is a NUL-terminated filename; consumers that need metadata should use the VFS entry layout rather than assuming a fixed filename length.
TELLDIR form
Returns a resumable cookie for the directory's current position. This is not a separate jump-table vector; it is the READDIR position-control form.
- Input: A = directory handle; DE = 0; HL = non-zero pointer to a writable four-byte cursor; C = 0.
- Output: the four-byte little-endian cursor is written at HL; carry clear.
The cursor identifies the position after the entries already read. Save all four bytes unchanged. A cookie may be a small ordinal on one filesystem and an arbitrary server-generated value on another.
SEEKDIR form
Restores a previously saved directory position. This is also a READDIR position-control form.
- Input: A = directory handle; DE = 0; HL = non-zero pointer to a four-byte cursor previously produced by
TELLDIR; C = 1. - Output: carry clear; the next directory-entry read continues from that saved position.
C values other than 0 and 1, or an HL value of 0 in either position-control form, fail with EINVAL.
CLOSEDIR ($3EDB)
Closes a directory handle. Input: A = directory handle. Output: none.
CHDIR ($3EDE)
Changes a mount's current directory. Input: A = mount point; HL = path. Output: none.
GETCWD ($3EE1)
Returns a mount's current directory. Input: A = mount point; HL = destination buffer. Output: NUL-terminated path through HL.
RENAME ($3EE4)
Renames a path within a mount. Input: A = mount point; HL = old path; DE = new path. Output: none.
SETMOUNTPOINT ($3EE7)
Selects the current VFS mount. Input: A = mount point. Output: none.
FREEMOUNTPOINT ($3EEA)
Finds a free VFS mount point. Input: none. Output: A = free mount point.
RESALLOC ($3EED)
Allocates or frees internal VFS resources. Input: C = resource operation; A = handle for a free operation. Output: A = allocated handle or HL = resource pointer, depending on operation.
Spectranext controller call
SPECTRANEXT ($3EF0)
Spectranext's controller-operation multiplexer. Input: A = operation ID; other registers depend on the operation. Output: carry clear on controller success; operation-specific results below.
| A | Operation | Additional input registers | Output registers / memory |
|---|---|---|---|
0 | Get controller status | DE = four-byte IPv4 buffer | B = controller status; C = Wi-Fi state; IPv4 through DE |
1 | Scan Wi-Fi APs | — | A = number of networks |
2 | Read scanned AP name | C = AP index; DE = name buffer | NUL-terminated name through DE |
3 | Connect Wi-Fi | HL = SSID; DE = password | none |
4 | Disconnect Wi-Fi | — | none |
5 | DNS hostname lookup | HL = hostname; DE = four-byte IPv4 buffer | IPv4 through DE |
6 | Engine call | HL = input; DE = output; BC = operation string | controller-defined result in A |
7 | Get controller message | DE = destination buffer | NUL-terminated message through DE |
8 | XFS read | HL = XFS_READ request descriptor | DEHL = bytes read |
9 | Read protected user settings | HL = buffer outside page B; BC = capacity | 16-byte payload through HL; BC = 16; carry/A report errors |
10 | Write protected user settings | HL = 16-byte payload outside page B; BC = 16 | carry/A report errors |
11 | Read system diagnostics | HL = writable 10-byte buffer | Diagnostics through HL: bootloader version, active policy, lockout reason, safety capabilities |
12 | Request staged upgrade | — | carry/A report errors; successful preparation takes effect after page-out |
13 | Internal .spx upgrade handoff | HL = $8080 handoff block | Carry set if handled; A = result |
14 | Read USB gamepad status | HL = writable 8-byte buffer | State, enabled flag, Kempston bits, device address, VID, PID through HL |
15 | Get firmware build version | HL = writable 32-byte buffer outside page B | NUL-terminated version string through HL |
Operation 13 in this table is used by ROM startup for the .spx upgrade
command. It is not a public SPECTRANEXT operation for applications.
XFS_READ (operation 8)
Reads a bounded range of a file through the controller's default XFS overlay into controller RAM pages. Input: HL points to this packed, little-endian 139-byte request:
| Offset | Size | Field |
|---|---|---|
0 | 128 | source_filename: NUL-terminated absolute path (up to 127 characters) |
128 | 4 | source_offset: byte offset in the source file |
132 | 1 | target_first_page: first target RAM page ($C0–$DF) |
133 | 2 | target_first_page_offset: offset within that page ($0000–$0FFF) |
135 | 4 | maximum_data: maximum number of bytes to read |
Output: carry clear and DEHL contains the actual number of bytes read.
Carry set indicates an invalid request or an XFS open, seek, read, or close
failure. A short read at end of file succeeds. The complete target range must fit
within pages $C0–$DF; requests that would run past $DF fail without reading.
Constants and C wrappers are declared in spectranext.h. For the underlying $3400/$3401 mailbox protocol, see I/O Ports.
Controller channels
Spectranext provides two controller channels on both hardware and FuseX. Each channel has its own request workspace, command register and completion status. Both accept the same commands and payload layouts.
The SDK's C functions and ROM calls use channel 0 and wait for completion. Channel 1 is available through direct controller memory access. Submit a request there, continue your program, and check its status when convenient. You can use the normal SDK on channel 0 while channel 1 is working.
For example, channel 1 can download a file with cp while channel 0 performs
spectranext_xfs_read() on a previously completed file. Engine calls share
resources and execute one at a time, so submitting an engine call on each
channel does not make the two engines run simultaneously.
Memory layout
Map controller page $48 into Spectranet page B ($2000–$2FFF) with
SETPAGEB. The addresses below assume that mapping.
Keep your program and source strings outside page B, and ensure the controller
is mapped whenever you access its workspace or registers.
| Area | Channel 0 | Channel 1 |
|---|---|---|
| Workspace | $2800–$2BFB | $2C00–$2FFB |
| Command | $2FFE | $2FFC |
| Status | $2FFF | $2FFD |
Each workspace holds 1,020 bytes. $2BFC–$2BFF are reserved. The existing
channel 0 register addresses and SDK interface are preserved.
Submit, continue, poll
- Use a channel only after its previous operation has finished and you have consumed its result.
- Write the command's input fields into that channel's workspace.
- Write
$FFto its status register, then write the command number to its command register. The status stays$FFwhile the request is pending or running. - Continue computing, rendering, or making channel 0 SDK calls.
- Poll the submitted channel's status once when you need an update. If it is
still
$FF, return to your other work. - When status changes, read the result from that channel's workspace before reusing it.
| Status | Meaning |
|---|---|
$FF | Pending or running. Leave this channel's workspace and registers alone. |
0 | Completed successfully; response fields are ready. |
1 | Completed with an error; inspect the command's result fields. |
The controller resets the command register to $FF when it accepts a request.
Poll status for completion, rather than the command register. There is no
completion interrupt; your program chooses when to check.
Example: copy on channel 1
An engine call uses command 6, with these workspace fields:
| Field | Offset | Channel 0 | Channel 1 | Format |
|---|---|---|---|---|
| Input path | $000 | $2800 | $2C00 | NUL-terminated string, at most 127 characters |
| Output path | $080 | $2880 | $2C80 | NUL-terminated string, at most 127 characters |
| Operation | $100 | $2900 | $2D00 | NUL-terminated string, at most 255 characters |
| Result | $200 | $2A00 | $2E00 | Signed byte: 0 success, negative error |
The following routine assumes controller page $48 is already mapped in
page B and this program owns channel 1. It returns immediately after submission;
carry is set if channel 1 is still busy.
start_copy:
ld a,($2ffd)
cp $ff
jr z,copy_busy
ld hl,input_path
ld de,$2c00
call copy_string
ld hl,output_path
ld de,$2c80
call copy_string
ld hl,operation
ld de,$2d00
call copy_string
ld a,$ff
ld ($2ffd),a ; pending
ld a,6 ; ENGINECALL
ld ($2ffc),a ; submit
or a ; carry clear
ret
copy_busy:
scf
ret
copy_string:
ld a,(hl)
ld (de),a
inc hl
inc de
or a
jr nz,copy_string
ret
input_path: defb "3:assets.bin",0
output_path: defb "assets-cache.bin",0
operation: defb "cp",0
On later iterations, read $2FFD. While it is $FF, keep doing other work.
Once it becomes 0 or 1, read the signed engine result at $2E00.
Only a successful completion makes assets-cache.bin ready to consume.
The same submission sequence works for other engines by changing operation.
Other operations and shared memory
Use the same command numbers and workspace offsets on either channel. For
example, command 8 (XFS_READ) has this packed layout. All multi-byte
values are little-endian.
| Field | Workspace offset | Format |
|---|---|---|
| Source filename | $000 | NUL-terminated path, up to 127 characters |
| Source file offset | $080 | 32-bit byte offset |
| First destination RAM page | $084 | 8-bit page number |
| Offset in that page | $085 | 16-bit byte offset |
| Maximum bytes to read | $087 | 32-bit byte count |
| Bytes read, after completion | $08B | 32-bit byte count |
For channel 1, add each offset to $2C00. Its source path follows the same
local-overlay lookup rules as
the SDK call. After a successful read, inspect bytes_read; EOF can produce a
short read or zero bytes.
The channels have separate workspaces but share files and destination RAM.
Give simultaneous reads separate destination regions, and wait for a copy to
finish before reading its output. If an XFS_READ targets controller page
$48, its destination must fit inside its own workspace after the first 256
bytes: $2900–$2BFB for channel 0, or $2D00–$2FFB for channel 1.
See also
- I/O Ports — controller mailbox and operation IDs
- Memory map — paging and
PAGEIN/PAGEOUT - libspdos — VFS structures, flags, and C wrappers