Skip to content

Instantly share code, notes, and snippets.

@JayFoxRox
Created July 14, 2018 03:10
Show Gist options
  • Save JayFoxRox/f40d8b98cff74eed787be81597acb642 to your computer and use it in GitHub Desktop.
Save JayFoxRox/f40d8b98cff74eed787be81597acb642 to your computer and use it in GitHub Desktop.
nxdk-rdt protocol specification (failed attempt iirc?)

nxdk-rdt Protocol specification

This protocol was designed to be simple to understand and implement.

However, nxdk-rdt only provides helper functions which are enough to inject your own code into an Xbox. nxdk-rdt itself does not provide a high-level set of functions to do anything useful on their own. To find out how to create these high-level functions using nxdk-rdt, see the examples section.

Reasons for this design are:

  • Compatibility and abstraction: Most nxdk-rdt functions have equivalents in gdbstub, kd and xbdm. We keep nxdk-rdt simple, and will implement high-level functionality for all backends elsewhere.

  • Powerful: Consider a case where a flag is written to MMIO registers and the code has to wait for a flag to change. Querying the state over the network is slow, and designing our own script system to run on the Xbox also isn't ideal. By injecting natively run x86 code into the Xbox, we can solve every sitution without restrictions.

  • Simple: Writing a new client for nxdk-rdt only takes a couple of minutes. Other than sockets, there are also no dependencies. This gives users the freedom to use their favorite tools.

Datatypes

  • U8 = Unsigned 8 bit value (byte)
  • U16 = Unsigned 16 bit value, little-endian
  • U32 = Unsigned 32 bit value, little-endian

Connection

Request frame

A request frame groups multiple requests which will be executed in sequence.

The request frame starts with a U8 which stores how many requests are in this frame. The requests follow in order of planned execution, with each request consisting of a U8 command index, followed by it's parameters.

Request types

The available requests are limited in functionality, but provide enough functionality to implement custom commands using the Call request.

0: SetRequestCompression(U8 algorithm)

Sets the compression for all requests on this connection, starting at the next request in this frame. Calling this function will always end the current compression stream and start a new one.

Supported algorithms:

  • 0: None
  • 1: LZ4 frame

1: SetResponseCompression(U8 algorithm)

Sets the compression for all responses on this connection, starting at the response for the next request.

See SetRequestCompression for a list of supported algorithms.

2: U32 virtual_address = Allocate(U32 size)

Allocates size bytes of data and returns the virtual_address. Free will be called automatically for each allocation when the connection is closed.

3: Free(U32 virtual_address)

Frees the memory allocation at address which was created using Allocate

4: U8 data[] = Read(U32 virtual_address, U32 size)

Reads size bytes from virtual memory at virtual_address and returns it. There are no guarantees made how memory is accessed. This makes the function unsuitable for MMIO.

5: Write(U32 virtual_address, U32 size, U8 data[])

Writes size bytes from data to the memory at virtual_address.

6: FlushInstructionCache()

FIXME: Could also implement this using something like SetExecutable(U32 virtual_address, size?) (not guaranteeing executable from Allocate, and checking in Call) to avoid people from forgetting this.

Forces the instruction cache to be flushed. This should be done before using Call when the code has been updated.

7: Call(U32 virtual_address)

Calls a function at virtual_address. It is guaranteed that the function will always be called from the same thread. The function is called using the x86 call instruction and must return.

If you need to send or receive data, you can use Allocate to allocate space and use Read and Write to communicate.

Response frame

The response frame starts with a U8 which stores how many requests were executed successfully. In case of errors, this value might be lower than the number of requested commands. The response for each successfully completed request in the request frame follows in the order of the requests.

Examples

FIXME:

I'd like something which allows to load commands into the guest, much like an extension loader. That way we could provide the same binary file for each programming language which would register extensions. Howver, it should be less invasive and work using the existing command set and protocol.

Advanced usage

For examples, see XboxDev/python-scripts

FIXME: These are for MMIO / advanced, but we don't need them if we can just create our own using Call:

U8 data = Read8(U32 address) / U16 data = Read16(U32 address)/U32 data = Read32(U32 address)`

Write8(U32 address, U8 data) / Write16(U32 address, U16 data) / Write32(U32 address, U32 data)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment