Striven
20 hours ago c538c151c7462ad0395ff2c15c5e11e89e362aa8
Initial commit
1 files deleted
6 files added
2 files modified
607 ■■■■■ changed files
content/.gitkeep patch | view | raw | blame | history
content/RendezVous/Common Structures.md 72 ●●●●● patch | view | raw | blame | history
content/RendezVous/PRUDP.md 325 ●●●●● patch | view | raw | blame | history
content/RendezVous/RMC.md 130 ●●●●● patch | view | raw | blame | history
content/RendezVous/index.md 19 ●●●●● patch | view | raw | blame | history
content/Ubi Services/index.md 5 ●●●●● patch | view | raw | blame | history
content/index.md 7 ●●●●● patch | view | raw | blame | history
package-lock.json 40 ●●●● patch | view | raw | blame | history
quartz.config.ts 9 ●●●●● patch | view | raw | blame | history
content/.gitkeep
content/RendezVous/Common Structures.md
New file
@@ -0,0 +1,72 @@
---
title: Common Structures
---
# Structure: `Buffer`
## Example
```
Hex View  00 01 02 03 04 05 06 07  08 09 0A 0B 0C 0D 0E 0F
00000000  04 00 01 02 04 05                                ......
```
## Parsing
| Name   | Description                         | Type         |
|--------|-------------------------------------|--------------|
| Length | The length of the buffer, in bytes. | `u16`        |
| Data   | The raw data of the buffer.         | `u8[Length]` |
# Structure: `String`
A null-terminated set of UTF8 bytes.
## Example
```
Hex View  00 01 02 03 04 05 06 07  08 09 0A 0B 0C 0D 0E 0F
00000000  0E 00 4C 6F 67 69 6E 50  72 6F 74 6F 63 6F 6C 00  ..LoginProtocol.
```
## Parsing
| Name   | Description                                                 | Type                          |
|--------|-------------------------------------------------------------|-------------------------------|
| Buffer | The UTF-8 encoded, null-terminated string data as a buffer. | [`Buffer`](#structure-buffer) |
# Structure: `List<T>`
## Example
```
Hex View  00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000  01 00 00 00 12 00 43 6C 69 65 6E 74 56 65 72 73  ......ClientVers
00000010  69 6F 6E 49 6E 66 6F 00 01 00                    ionInfo...
```
## Parsing
| Name            | Description                                 | Type                 |
|-----------------|---------------------------------------------|----------------------|
| Number of Items | The number of individual items in the list. | `u32`                |
| Items           | The items in the list.                      | `T[Number of Items]` |
# Structure: `StationURL`
A RendezVous URI, with at least protocol (`prudp:`), address (e.g., `address=127.0.0.1`), and port (e.g. `port=6005`).
## Example
```
Hex View  00 01 02 03 04 05 06 07  08 09 0A 0B 0C 0D 0E 0F
00000000  70 72 75 64 70 3A 2F 61  64 64 72 65 73 73 3D 31  prudp:/address=1
00000010  32 37 2E 30 2E 30 2E 31  3B 70 6F 72 74 3D 36 30  27.0.0.1;port=60
00000020  30 35                                             05
```
## Parsing
| Name | Description             | Type                          |
|------|-------------------------|-------------------------------|
| URL  | The URL of the station. | [`String`](#structure-string) |
# Structure: `UUID`
A platform-wide unique identifier for any sort of resource.
| Name | Description | Type |
|-|-|-|
| Bytes | The bytes that make up this UUID | `[16]u8` |
content/RendezVous/PRUDP.md
New file
@@ -0,0 +1,325 @@
---
title: PRUDP
---
PRUDP probably stands for **P**rotected **R**eliable **U**ser **D**atagram **P**rotocol, as it is designed for adding reliability, encryption and compression to [UDP](https://en.wikipedia.org/wiki/User_Datagram_Protocol). To do this, it adds [connections](#connections), [acknowledgements](#acknowledgements), [ordering](#ordering), [fragmentation](#fragmentation), [compression](#compression), [encryption](#encryption) and [checksums](#checksums).
# Packets
## Base Packet
**Every** packet, sent by both the client and the server, has the following base information:
| Name             | Description                                                                                                                    | Type                                          |
|------------------|--------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------|
| Source           | The [virtual stream](#virtual-port) that the packet is being sent from.                                                        | [`Stream`](#structure-stream)                 |
| Destination      | The [virtual stream](#virtual-port) that the packet is being sent to.                                                          | [`Stream`](#structure-stream)                 |
| Type and Flags   | Type of the packet, and flags indicating how the client should treat this packet. See below for how to parse each packet type. | [`Type and Flags`](#structure-type-and-flags) |
| Session ID       | Unknown.                                                                                                                       | `u8`                                          |
| Packet Signature | Unknown.                                                                                                                       | `u32`                                         |
| Sequence ID      | A strictly monotonically increasing integer used for [acknowledgements](#acknowledgements) and [ordering](#ordering).          | `u16`                                         |
A 4-byte checksum is appended to the end of **every** packet, see [Checksums](#checksums).
## Packet: `SYN`
This packet establishes a connection with the server. `SYN` here likely stands for **synchronization**, because it synchronizes the client's sequence ID with the server's.
See the [synchronize](#1-synchronize) stage of connections.
### Client->Server Payload
| Name                 | Description | Type  |
|----------------------|-------------|-------|
| Connection Signature | Unknown.    | `u32` |
### Server->Client Payload
| Name                 | Description | Type  |
|----------------------|-------------|-------|
| Connection Signature | Unknown.    | `u32` |
## Packet: `CONNECT`
This stage is used to execute a Diffie-Hellman key exchange, and then derive a shared secret key.
The key exchange operation is $S = d \times Q$, where $S$ is the resulting key, $d$ is the server's private key, and $Q$ is the client's public key. The resulting `y` co-ordinate of this exchange is discarded.
The shared key, used for `DATA` packet encryption and decryption, is derived using the first `16` bytes of a `SHA1` hash on the Diffie-Hellman exchange result.
The server may also use this stage to affirm the client's [connection signature](#connections).
See the [key exchange](#3-key-exchange) stage of connections.
### Client->Server Payload
| Name                 | Description                | Type                                                 |
|----------------------|----------------------------|------------------------------------------------------|
| Connection Signature | Unknown.                   | `u32`                                                |
| Public Key           | The client's _public key_. | [`Public Key`](#structure-public-key) |
### Server->Client Payload
| Name                 | Description                                                                                                                                                                                                                                               | Type                                                 |
|----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------|
| Connection Signature | Unknown.                                                                                                                                                                                                                                                  | `u32`                                                |
| Public Key Signature | The server's _public key_ signed by the server's _certification private key_.                                                                                                                                                                             | [`Buffer`](03_structure.md#structure-buffer)         |
| Public Key           | The server's _public key_.                                                                                                                                                                                                                                | [`Public Key`](03_structure.md#structure-public-key) |
| Tag                  | A HMAC-SHA256 signature on both the [client's key](#structure-public-key) and the [server's key](#structure-public-key) concatenated, signed using the Diffie-Hellman exchange result (NOT the shared key). | [`Buffer`](Common%20Structures#structure-buffer)         |
## Packet: `DATA`
The whole data is _first_ fragmented, _then_ compressed, _then_ encrypted.
This means that the data in this packet, if fragmented, should be [decrypted first](#encryption), then [de-compressed](#compression) (if applicable), and then re-assembled.
The **compressed payload** (before encryption) is prefixed with a single byte indicating the compression ratio. `0` means that the remaining data is **not** compressed. The client appears to only ever use `0x02` for this byte, however.
The **fragmented payload** (before compression) is suffixed with the 2-byte **sequence id** of the data packet being sent.
See the [Data](#5-data) stage of connections.
### Client->Server, Server->Client Payload
| Name        | Description                                                                                                                                                     | Type  |
|-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|-------|
| Fragment ID | Used to re-assemble [fragmented](#fragmentation) data packets, starting from `1` (if multiple fragments exist), and ending in `0` to indicate a final fragment. | `u32` |
| ...         | Encrypted and compressed data                                                                                                                                   | `any` |
## Packet: `DISCONNECT`
This packet contains no additional information, see the [Disconnect](#6-disconnect) stage of connections.
## Packet: `PING`
This packet contains no additional information, see the [Pinging](#2-pinging) stage of connections.
## Packet: `USER`
This packet contains no additional information, see the [User](#4-user) stage of connections.
# Concepts
## Connections
PRUDP adds **connections** to UDP, meaning that each client is recorded, keeping state, and kept alive using [pings](#packet-ping) and [acknowledgements](#acknowledgements).
Terminology for this section:
| Word   | Meaning                                                    |
|--------|------------------------------------------------------------|
| Client | A user of this protocol wishing to establish a connection. |
| Host   | A user of this protocol willing to accept a connection.    |
### Virtual Port
Each UDP socket from the client supports multiple **virtual streams**. This means that the sender of a [packet](#base-packet) is not just identified by the IP and port that it originated from, but also the virtual source port and the virtual destination port.
It is presumably implementation behaviour how these ports function. For example, for the initial LoadBalancer PRUDP connection, the game seems to open a virtual port `0x01` and sends packets to the host's virtual port `0x0f`. This likely means that the host can expect a certain kind of data, e.g. [RMC](RMC) data.
Also, each stream can be identified by its [stream type](#enum-stream-type), but it is yet to be seen how this impacts the data.
This article uses **connection** to refer to these **virtual streams**, with the knowledge that each UDP socket may have multiple connections assigned to it.
### Identification
Similar to TLS, PRUDP defines a system by which the client can ensure the identity of the host being connected to. This is done using a certificate public key provided through another means (e.g. given by [Ubi Services](../Ubi%20Services)). The host's public key is signed using the certificate private key during the [key exchange](#3-key-exchange) stage.
You can generate this certificate key pair using OpenSSL:
- Private Key: `openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -out key.pem`
- Public Key: `openssl ec -in key.pem -pubout -out key.out`
### Sequence
#### 1. Synchronize
The initial packet is sent by the **client**, who sends a [`SYN`](#packet-syn) packet to indicate that they wish to establish a connection with the **host**. This packet has two purposes:
- It has an initial **sequence id**, so that the host can begin [ordering packets](#ordering) sent by the client.
- To identify that the client is a **PRUDP** user, and the [type of connection](#virtual-port) being made.
The host then sends back a [`SYN`](#packet-syn) of its own, and can register this client and begin pinging it to keep the connection alive.
#### 2. Pinging
Both the client and host ping eachother, using a [`PING`](#packet-ping) packet. These seem to be sent roughly every `10` seconds, although presumably may be sent whenever.
Also, the game appears to consider the missing acknowledgement of **2** consecutive pings as a connection failure, and will silently close the connection.
#### 3. Key Exchange
After receiving a [`SYN`](#packet-syn) packet from the host, the client will attempt to establish a basis for [encrypted communication](#encryption) to be used over [`DATA`](#packet-data) packets, sending its own ephemeral [public key](#structure-public-key) in a [`CONNECT`](#packet-connect) packet. The host performs a Diffie-Hellman key exchange using its secret key, and the **shared secret** used for [encryption](#encryption) is derived using the first 16 bytes of the SHA1 hash of the result of this exchange.
The host then sends back its own ephemeral [public key](#structure-public-key), prefixed with its **signature** signed using the [signing private key](#identification), for the client to verify using the host's public certificate. The response also contains a 32 byte tag, which is computed using a SHA256 HMAC signature of the client's [public key](#structure-public-key) concatenated with the host's [public key](#structure-public-key), using the result of the Diffie-Hellman key exchange as a key (NOT the SHA1 derived key used for encryption).
##### Pseudo-Code Example
```py
def exchange_keys(client_public_key):
    exchange_result = diffie_hellman_exchange(client_public_key, host_secret_key)
    signature = sign(host_public_key, signing_private_key)
    combined_keys = client_public_key + host_public_key
    tag = hmac(combined_keys, exchange_result, "sha256")
    derived_secret = hash(exchange_result, "sha1")
    return signature, host_public_key, tag, derived_secret
```
#### 4. "User"
It is not clear what the point of this stage is, but after a key exchange has been performed and a derived shared secret has been established, the client sends a [`USER`](#packet-user) packet, which the host acknowledges.
#### 5. Data
The client and host can now both begin sending [`DATA`](#packet-data) packets, [encrypted](#encryption) using the derived secret established during the [key exchange](#3-key-exchange) stage. The data may also be [fragmented](#fragmentation) and [compressed](#compression).
The unfragmented, uncompressed and unencrypted whole payload is not defined, and may use any protocol. This is because PRUDP is a reliable transportation layer on top of UDP. The application defines the protocol used for the communicated information. For example, the [RMC](RMC) protocol may be used on top of the PRUDP protocol.
#### 6. Disconnect
When the client or host wishes to close a connection, for example in the case of missing acknowledgements (dead connection), or when manually disconnecting, they can simply send a [`DISCONNECT`](#packet-disconnect) packet to the other. This does not need to be acknowledged, and it is not required for a client or host to send a disconnect packet, they can also simply refuse to stop accepting packets.
Consider it courtesy to send a disconnect packet.
## Acknowledgements
Clients receiving packets with the [Need Ack](#structure-type-and-flags) flag are required to send back a packet of the **same type**, with the [Ack](#structure-type-and-flags) flag set and the sequence ID set to the received packet's sequence ID. The packet in response may optionally contain data, so that responses can be given _and_ the original packet acknowledged in the same packet. Note that this requires [sequence ID synchronization](#packet-syn), and therefore may not be possible once the client and server start sending disjointed [`DATA`](#packet-data) packets.
For example, when a connecting client sends a [`CONNECT`](#client-server-payload-1) packet, it is set with the [Need Ack](#structure-type-and-flags) flag. A receiving server can then exchange the client's public key for a shared secret, and then can respond with its own public key in another [`CONNECT`](#server-client-payload-1) packet with the [Ack](#structure-type-and-flags) flag set.
[Reliable](#structure-type-and-flags)-flagged packets are re-sent by the sender if an acknowledgement is not received within a certain time frame.
Some packets with the [Need Ack](#structure-type-and-flags) flag also have the [Multi Ack](#structure-type-and-flags) flag. The purpose of this flag is not entirely clear, but the recipient must respond to it with both the [Ack](#structure-type-and-flags) flag **and** the [Multi Ack](#structure-type-and-flags) flag set.
## Ordering
A receiver may choose to keep track of which sequence ID a sender sent last, so as to know which to expect next from the sender. If a sequence ID is sent that is _greater_ than the one expected, the receiver may also choose to retain the packet until the expected sequence ID has been sent by the sender. This is recommended because UDP is inherently unreliable, and may deliver packets out of order than was sent by the client.
Sequence IDs that are _less_ than the one expected can be rejected, and therefore treated as a [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce).
> [!IMPORTANT]
> Packets should *always* be acknowledged regardless of whether they are out of order or not, as the sender may be re-sending it on purpose, having not received an acknowledgement from the recipient.
> [!WARNING]
> Care should be made to only handle or retain packets with sequence IDs that have not already been retained, so as to not double count repeated sending of the same packet.
### Pings
Pings appear to use a difference sequence to the main reliable packets. This may be the case for *all* unreliable packets.
## Fragmentation
[`DATA`](#packet-data) packets **may** be fragmented. The maximum packet size is not defined, but generally should be kept less than `1024` bytes for best deliverability.
Each [`DATA`](#packet-data) is tagged with a **fragment id**, which starts at `1`, incrementing each fragment. The **final fragment** should have a fragment id of `0`.
Each **fragment data**, a slice of the whole data, is suffixed with a 2 byte LE integer, which is the same as the sequence ID of the fragment packet being sent.
### Example
| Fragment # | Fragment ID |
|-|-|
| `1/4` | `1` |
| `2/4` | `2` |
| `3/4` | `3` |
| `4/4` | `0` |
### Example
| Fragment # | Fragment ID |
|-|-|
| `1/1` | `0` |
> [!NOTE]
> This means that non-fragmented data is sent with a fragment id of `0`.
### Pseudo-Code Example
```py
def send_fragmented(whole_data, max_size, next_sequence_id):
    frag_id = 1
    i = 0
    while i < len(whole_data):
        i += max_size
        # frag_id = 0 indicates that this is the last fragment that the recipient should expect
        is_last = i >= len(whole_data)
        if (is_last) frag_id = 0
        # next_sequence_id = 2 byte LE integer, should be same as the packet sequence id
        fragmented_data = whole_data[i-max_size : i] + next_sequence_id
        # note that each data fragment is otherwise a completely standalone packet with its own sequence id
        send_data(encrypt(compress(fragmented_data)), frag_id)
        frag_id += 1
        next_sequence_id += 1
    return next_sequence_id
```
## Compression
[`DATA`](#packet-data) packets **may** be compressed using [zlib](https://www.zlib.net/). This **always** comes before [encryption](#encryption) and after [fragmentation](#fragmentation), meaning that the **fragmented** data is compressed.
After compression, the compressed data is prefixed with a 1-byte **compression ratio**, which seems to always be set to `0x02`.
> [!NOTE]
> Packets do not have to be compressed, in which case the **compression ratio** should read `0x00` so that the recipient can skip compression and read directly.
### Pseudo-Code Example
```py
def compress(fragmented_data):
    compressed_data = zlib_deflate(fragmented_data, level=default)
    # compression_ratio = 1 byte integer
    # seems to always be: compression_ratio = 2
    return compression_ratio + compressed_data
```
## Encryption
[`DATA`](#packet-data) packets **must** be encrypted using 128 bit [AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard)-[CBC](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_block_chaining_(CBC)). This **always** comes after [compression](#compression), meaning that the **fragmented, compressed** data is encrypted.
The 16 byte IV for each packet is generated randomly using a [CRPNG](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator), and prefixes the encrypted data in the payload.
The key used for the encryption is derived using the shared key established from the [key exchange](#3-key-exchange) stage.
### Pseudo-Code Example
```py
def encrypt(data, derived_key):
    iv = crypto_random_bytes(16)
    cipher_text = aes_cbc_encrypt(data, derived_key, iv)
    return iv + cipher_text
```
## Checksums
**Every** packet has a 4-byte checksum at the end. The algorithm used is adding each 4 bytes of the packet interpreted as `u32` LE. The data is padded to have a length divisible by `4` using `0`s.
### Pseudo-Code Example
```py
def append_checksum(data):
    # sum of u32 LE words (zero-padded to 4-byte boundary).
    pad_len = (4 - len(data) % 4) % 4
    padded = data + b'\x00' * pad_len
    words = struct.unpack('<%dI' % (len(padded) // 4), padded)
    # checksum is u32 little-endian
    return data + (sum(words) & 0xFFFFFFFF)
```
# Structures
### Structure: `Public Key`
An **uncompressed** `SEC1` formatted key, **without** the preceeding compression tag (`0x04`).
### Structure: `Stream`
A packed `u8` containing
| Name | Description                                       | Type                               |
|------|---------------------------------------------------|------------------------------------|
| Port | The [virtual port](#virtual-port) for this stream. | `u4`                               |
| Port | The type of stream that the connection is using.  | [`Stream Type`](#enum-stream-type) |
### Structure: `Type and Flags`
A packed `u8` containing
| Name      | Description                                                                                                                                                                                                          | Type                               |
|-----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------|
| Type      | The type of packet that is being sent.                                                                                                                                                                               | [`Packet Type`](#enum-packet-type) |
| Ack       | This packet is an [acknowledgement](#acknowledgements) packet for a reliable packet sent by the recipient.                                                                                                           | `bool`                             |
| Reliable  | This packet **must** be acknowledged by the recipient, or it will be re-sent.                                                                                                                                        | `bool`                             |
| Need Ack  | The sender is expecting an acknowledgement for this packet.                                                                                                                                                          | `bool`                             |
| Has Size  | The packet contains a `u16` size following the packet header (or, for [`SYN`](#packet-syn) and [`CONNECT`](#packet-connect), after the connection signature, and for [`DATA`](#packet-data), after the fragment ID). | `bool`                             |
| Multi Ack | Unknown, doesn't appear to be supported, but if a packet flagged with `Need Ack` has this flag, the recipient should send an `Ack` with this flag set too.                                                           | `bool`                             |
### Enum: `Stream Type`
A `u4` containing these values:
| Name                 | Value |
|----------------------|-------|
| `RV Authentication`  | `2`   |
| `RV Secure`          | `3`   |
| `Sandbox Management` | `4`   |
| `NAT`                | `5`   |
| `Session Discovery`  | `6`   |
| `Nat Echo`           | `7`   |
> [!NOTE]
> Hyperscape appears to exclusively use the `RV Secure` (`3`) type.
### Enum: `Packet Type`
A `u3` containing these values:
| Name                               | Value |
|------------------------------------|-------|
| [`SYN`](#packet-syn)               | `0`   |
| [`CONNECT`](#packet-connect)       | `1`   |
| [`DATA`](#packet-data)             | `2`   |
| [`DISCONNECT`](#packet-disconnect) | `3`   |
| [`PING`](#packet-ping)             | `4`   |
| [`USER`](#packet-user)             | `6`   |
content/RendezVous/RMC.md
New file
@@ -0,0 +1,130 @@
---
title: RMC
---
The RMC protocol, standing for **R**emote **M**ethod **C**all, is used to make transactional requests, such that "methods" are called by the client and responses are generated by the server.
RMC itself is designed as a transport for several other application-defined protocols, such that services can send and handle requests through RMC. This means that there's another level deeper to go before we get to application/game data, as each protocol implements its own reading and writing of bytes, although they follow similar patterns and standardised ways.
## Official Implementation
In HyperScape, this protocol is done over [PRUDP](PRUDP) data packets.
When a request is made, usually in a function going by `<protocol name>Protocol::Call<method name>`, a call context is created and containers in memory for data returned by the server are associated with **return value pointers**, with IDs from `[0, n)`, where n is the number of parameters.
When a response is received, these **return value pointers** are fetched from the call context, and the packet is parsed and data is placed into these pointers.
## Known Protocols
Here are the currently understood RMC protocols implemented in HyperScape. Some appear to be standard to Ubisoft games as a whole, while others appear to be designed specifically for the game.
| Protocol                                                                     | Description                                                                                      | HyperScape-specific? |
|------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|----------------------|
| [`LoginProtocol`](Protocols/Login%20Protocol)                       | Used for authenticating with an RMC server using a Ubisoft token.                                | ✖️                    |
| [`CloudServersProtocol`](Protocols/Cloud%20Servers%20Protocol)         | Used for listing datacenters for connecting to game servers.                                     | ✔️                    |
| [`ApexAntitoxicityProtocol`](Protocols/Apex%20Antitoxicity%20Protocol) | Currently unknown, possibly for managing bans and mutes.                                         | ✔️                    |
| [`SessionProtocol`](Protocols/Session%20Protocol)                   | Presumably for creating, joining and leaving game sessions, but it is unknown what that entails. | ✔️                    |
| ...                                                                          | ...                                                                                              | ...                  |
> [!NOTE]
> This list is far from complete.
## Base Packet Format
##### Parsing
| Name          | Description                                                                                                                                                 | Type                                           |
|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------|
| Packet Length | The total number of bytes in the packet, not including the length itself.                                                                                   | `u32`                                          |
| Protocol Name | The protocol service on the server that is responsible for managing this request                                                                            | [`String`](Common%20Structures#structure-string) |
| Is Request?   | Whether or not this packet is for a _request_.                                                                                                              | `bool`                                         |
| ...           | See [Request Packet Format](#request-packet-format) or [Response Packet Format](#response-packet-format) for parsing the packet depending on `Is Request?`. | ...                                            |
## Request Packet Format
##### Example
```
Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000  F9 00 00 00 0E 00 4C 6F 67 69 6E 50 72 6F 74 6F  ù.....LoginProto
00000010  63 6F 6C 00 01 06 00 00 00 1B 00 4C 6F 67 69 6E  col........Login
00000020  50 72 6F 74 6F 63 6F 6C 3A 3A 52 65 67 69 73 74  Protocol::Regist
00000030  65 72 5F 56 31 00 00 00 00 00 04 00 00 00 2F 00  er_V1........./.
00000040  70 72 75 64 70 3A 2F 61 64 64 72 65 73 73 3D 30  prudp:/address=0
00000050  30 30 2E 30 30 30 2E 30 30 2E 30 30 30 3B 70 6F  00.000.00.000;po
00000060  72 74 3D 39 31 30 33 3B 73 69 64 3D 31 35 00 2E  rt=9103;sid=15..
00000070  00 70 72 75 64 70 3A 2F 61 64 64 72 65 73 73 3D  .prudp:/address=
00000080  30 30 30 2E 30 30 30 2E 30 30 30 2E 30 3B 70 6F  000.000.000.0;po
00000090  72 74 3D 39 31 30 33 3B 73 69 64 3D 31 35 00 2D  rt=9103;sid=15.-
000000A0  00 70 72 75 64 70 3A 2F 61 64 64 72 65 73 73 3D  .prudp:/address=
000000B0  30 30 30 2E 30 30 30 2E 30 30 2E 30 3B 70 6F 72  000.000.00.0;por
000000C0  74 3D 39 31 30 33 3B 73 69 64 3D 31 35 00 2D 00  t=9103;sid=15.-.
000000D0  70 72 75 64 70 3A 2F 61 64 64 72 65 73 73 3D 30  prudp:/address=0
000000E0  30 30 2E 30 30 30 2E 30 30 2E 30 3B 70 6F 72 74  00.000.00.0;port
000000F0  3D 39 31 30 33 3B 73 69 64 3D 31 35 00           =9103;sid=15.
```
##### Parsing
| Name           | Description                                                                                                         | Type                                                                                       |
|----------------|---------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|
| ...            | See [Base Packet Format](#base-packet-format) for parsing the prologue of a request packet.                         | ...                                                                                        |
| Call ID        | The nonce token for this call to identify the response associated with this request.                                | `u32`                                                                                      |
| Method Name    | The method to call on the server.                                                                                   | [`String`](Common%20Structures#structure-string)                                             |
| Class Versions | Version information about the data in the request                                                                   | [`List`](Common%20Structures#structure-listt)`<`[`ClassVersion`](#structure-classversion)`>` |
| ...            | See the respective [protocol and method specification](#known-protocols) to parse and write the rest of the packet. |                                                                                            |
### Structure: `ClassVersion`
Example:
```
Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000  12 00 43 6C 69 65 6E 74 56 65 72 73 69 6f 6E 49  ......ClientVersionI
00000010  6E 66 6F 00 01 00                                nfo...
```
| Name           | Description                                       | Type                                           |
|----------------|---------------------------------------------------|------------------------------------------------|
| Structure Name | The name of the structure to assign a version to. | [`String`](Common%20Structures#structure-string) |
| Version        | The version of the structure.                     | `u16`                                          |
## Response Packet Format
##### Example
```
Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000  25 00 00 00 0E 00 4C 6F 67 69 6E 50 72 6F 74 6F  %.....LoginProto
00000010  63 6F 6C 00 00 00 0B 00 52 65 6E 64 65 7A 56 6F  col.....RendezVo
00000020  75 73 00 81 00 05 00 00 00                       us.......
```
> [!NOTE]
> TODO: Successful response example
##### Parsing
| Name           | Description                                                                                                                                                                           | Type   |
|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|
| ...            | See [Base Packet Format](#base-packet-format) for parsing the prologue of a response packet.                                                                                          | ...    |
| Is Successful? | Whether or not this request was successful                                                                                                                                            | `bool` |
| ...            | See [Successful Response Format](#successful-response-format) or [Unsuccessful Response Format](#unsuccessful-response-format) for parsing this packet depending on `Is Successful?`. | ...    |
### Successful Response Format
##### Parsing
| Name        | Description                                                                                                         | Type                                           |
|-------------|---------------------------------------------------------------------------------------------------------------------|------------------------------------------------|
| ...         | See [Response Packet Format](#response-packet-format) for parsing the prologue of a successful response packet.     | ...                                            |
| Call ID     | The nonce token for the call to associate the response to the request.                                              | `u32`                                          |
| Method Name | The method that was called by the client, see note below.                                                           | [`String`](Common%20Structures#structure-string) |
| ...         | See the respective [protocol and method specification](#known-protocols) to parse and write the rest of the packet. |                                                |
> [!IMPORTANT]
> Typically, a `*` is appended to the Method Name in responses for currently unknown reasons. Likely to do with the fact that the response handlers update pointers to data containers created in the original request.
>
> For example, where the request might be `LoginProtocol::LoginWithToken_V1`, the response would be for `LoginProtocol::LoginWithToken_V1*`.
### Unsuccessful Response Format
##### Parsing
| Name            | Description                                                                                                        | Type                                           |
|-----------------|--------------------------------------------------------------------------------------------------------------------|------------------------------------------------|
| ...             | See [Response Packet Format](#response-packet-format) for parsing the prologue of an unsuccessful response packet. | ...                                            |
| Error Namespace | The category of error that occurred.                                                                               | [`String`](Common%20Structures#structure-string) |
| Error Code      | The code of the error in the specified namespace                                                                   | `u16`                                          |
| Call ID         | The nonce token for the call to associate the response to the request.                                             | `u32`                                          |
content/RendezVous/index.md
New file
@@ -0,0 +1,19 @@
---
title: RendezVous
---
RendezVous is a wide-ranging matchmaking, networking and lobby framework that serves as the basis for much of HyperScape's remote connection and gameplay system.
RendezVous was developed by Quazal, seemingly around 2000, and the technology has since been licensed out to other studios[^1]. Quazal was purchased by UbiSoft in 2010[^2].
At the heart of RendezVous is an extremely customisable, extensible and dynamic system, and serves as much more of a general philosophy with a base implementation than a full library in itself. This makes it very difficult to have a single source of truth for defining RendezVous formats and flows.
Therefore, these pages serve only to explain Ubisoft's use and version of RendezVous, and does not attempt to implement other games' or studios' flavours of the framework. Other wikis and pages exist to do that, such as [Pretendo](https://developer.pretendo.network/home), which implements Nintendo's licensed version and is significantly different to the one described here.
Since RendezVous is so complex, it's unlikely that you would be able to use these pages to understand how to implement a RendezVous stack, however it should provide a solid enough basis for understanding the protocols used and how the game uses them.
#### Footnotes
[^1]: https://web.archive.org/web/20180823162719/http:/quazal.com/rendez-vous.htm
[^2]: https://www.fasken.com/en/experience/2010/11/ubisoft-acquires-quazal-technologies
content/Ubi Services/index.md
New file
@@ -0,0 +1,5 @@
---
title: Ubi Services
---
Stub!
content/index.md
New file
@@ -0,0 +1,7 @@
---
title: Home
---
The purpose of this wiki is to explain, in detail, the internal construction of the game [Hyper Scape](https://en.wikipedia.org/wiki/Hyper_Scape), developed by Ubisoft, released on 2nd July 2020, and shut down on 27th April 2022.
Information is derived from reverse engineering, leaks, and information gathered by others concerning other games.
package-lock.json
@@ -97,13 +97,13 @@
      "version": "2.10.2",
      "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.10.2.tgz",
      "integrity": "sha512-uFsRXwIGyu+r6AMdz+XijIIZJYpoWeYzILt5yZ2d3mCjQrWUTVpVD9WL/jZAbvp+Ed04rOhrsk7FiTcEDseB5A==",
      "license": "(Apache-2.0 AND BSD-3-Clause)",
      "peer": true
      "license": "(Apache-2.0 AND BSD-3-Clause)"
    },
    "node_modules/@citation-js/core": {
      "version": "0.7.14",
      "resolved": "https://registry.npmjs.org/@citation-js/core/-/core-0.7.14.tgz",
      "integrity": "sha512-dgeGqYDSQmn2MtnWZkwPGpJQPh43yr1lAAr9jl1NJ9pIY1RXUQxtlAUZVur0V9PHdbfQC+kkvB1KC3VpgVV3MA==",
      "peer": true,
      "dependencies": {
        "@citation-js/date": "^0.5.0",
        "@citation-js/name": "^0.4.2",
@@ -2611,8 +2611,7 @@
      "version": "0.2.0",
      "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz",
      "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==",
      "license": "MIT/X11",
      "peer": true
      "license": "MIT/X11"
    },
    "node_modules/buffer-from": {
      "version": "1.1.2",
@@ -2732,8 +2731,7 @@
      "version": "0.5.2",
      "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz",
      "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==",
      "license": "MIT",
      "peer": true
      "license": "MIT"
    },
    "node_modules/comma-separated-tokens": {
      "version": "2.0.3",
@@ -3106,6 +3104,7 @@
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
      "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
      "peer": true,
      "engines": {
        "node": ">=12"
      }
@@ -3284,6 +3283,7 @@
      "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
      "hasInstallScript": true,
      "license": "MIT",
      "peer": true,
      "bin": {
        "esbuild": "bin/esbuild"
      },
@@ -3696,7 +3696,6 @@
      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
      "license": "MIT",
      "peer": true,
      "engines": {
        "node": ">=8"
      }
@@ -5854,6 +5853,7 @@
      "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.2.tgz",
      "integrity": "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==",
      "license": "MIT",
      "peer": true,
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/preact"
@@ -6415,7 +6415,6 @@
      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
      "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
      "license": "Apache-2.0",
      "peer": true,
      "dependencies": {
        "tslib": "^2.1.0"
      }
@@ -6500,7 +6499,6 @@
      ],
      "license": "MIT",
      "optional": true,
      "peer": true,
      "dependencies": {
        "sass": "1.97.2"
      }
@@ -6517,7 +6515,6 @@
      "os": [
        "android"
      ],
      "peer": true,
      "engines": {
        "node": ">=14.0.0"
      }
@@ -6534,7 +6531,6 @@
      "os": [
        "android"
      ],
      "peer": true,
      "engines": {
        "node": ">=14.0.0"
      }
@@ -6551,7 +6547,6 @@
      "os": [
        "android"
      ],
      "peer": true,
      "engines": {
        "node": ">=14.0.0"
      }
@@ -6568,7 +6563,6 @@
      "os": [
        "android"
      ],
      "peer": true,
      "engines": {
        "node": ">=14.0.0"
      }
@@ -6585,7 +6579,6 @@
      "os": [
        "darwin"
      ],
      "peer": true,
      "engines": {
        "node": ">=14.0.0"
      }
@@ -6602,7 +6595,6 @@
      "os": [
        "darwin"
      ],
      "peer": true,
      "engines": {
        "node": ">=14.0.0"
      }
@@ -6619,7 +6611,6 @@
      "os": [
        "linux"
      ],
      "peer": true,
      "engines": {
        "node": ">=14.0.0"
      }
@@ -6636,7 +6627,6 @@
      "os": [
        "linux"
      ],
      "peer": true,
      "engines": {
        "node": ">=14.0.0"
      }
@@ -6653,7 +6643,6 @@
      "os": [
        "linux"
      ],
      "peer": true,
      "engines": {
        "node": ">=14.0.0"
      }
@@ -6670,7 +6659,6 @@
      "os": [
        "linux"
      ],
      "peer": true,
      "engines": {
        "node": ">=14.0.0"
      }
@@ -6687,7 +6675,6 @@
      "os": [
        "linux"
      ],
      "peer": true,
      "engines": {
        "node": ">=14.0.0"
      }
@@ -6704,7 +6691,6 @@
      "os": [
        "linux"
      ],
      "peer": true,
      "engines": {
        "node": ">=14.0.0"
      }
@@ -6721,7 +6707,6 @@
      "os": [
        "linux"
      ],
      "peer": true,
      "engines": {
        "node": ">=14.0.0"
      }
@@ -6738,7 +6723,6 @@
      "os": [
        "linux"
      ],
      "peer": true,
      "engines": {
        "node": ">=14.0.0"
      }
@@ -6755,7 +6739,6 @@
        "!linux",
        "!win32"
      ],
      "peer": true,
      "dependencies": {
        "sass": "1.97.2"
      }
@@ -6772,7 +6755,6 @@
      "os": [
        "win32"
      ],
      "peer": true,
      "engines": {
        "node": ">=14.0.0"
      }
@@ -6789,7 +6771,6 @@
      "os": [
        "win32"
      ],
      "peer": true,
      "engines": {
        "node": ">=14.0.0"
      }
@@ -6959,6 +6940,7 @@
      "version": "1.26.2",
      "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.26.2.tgz",
      "integrity": "sha512-iP7u2NA9A6JwRRCkIUREEX2cMhlYV5EBmbbSlfSRvPThwca8HBRbVkWuNWW+kw9+i6BSUZqqG6YeUs5dC2SjZw==",
      "peer": true,
      "dependencies": {
        "@shikijs/core": "1.26.2",
        "@shikijs/engine-javascript": "1.26.2",
@@ -7130,7 +7112,6 @@
      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
      "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
      "license": "MIT",
      "peer": true,
      "dependencies": {
        "has-flag": "^4.0.0"
      },
@@ -7158,7 +7139,6 @@
      "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz",
      "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==",
      "license": "MIT",
      "peer": true,
      "dependencies": {
        "sync-message-port": "^1.0.0"
      },
@@ -7183,7 +7163,6 @@
      "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz",
      "integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==",
      "license": "MIT",
      "peer": true,
      "engines": {
        "node": ">=16.0.0"
      }
@@ -7492,8 +7471,7 @@
      "version": "6.0.0",
      "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz",
      "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==",
      "license": "MIT",
      "peer": true
      "license": "MIT"
    },
    "node_modules/vfile": {
      "version": "6.0.3",
quartz.config.ts
@@ -8,7 +8,7 @@
 */
const config: QuartzConfig = {
  configuration: {
    pageTitle: "Quartz 4",
    pageTitle: "Scapegoat Docs",
    pageTitleSuffix: "",
    enableSPA: true,
    enablePopovers: true,
@@ -16,7 +16,7 @@
      provider: "plausible",
    },
    locale: "en-US",
    baseUrl: "quartz.jzhao.xyz",
    baseUrl: "docs.scapegoat.cx",
    ignorePatterns: ["private", "templates", ".obsidian"],
    defaultDateType: "modified",
    theme: {
@@ -68,9 +68,12 @@
      }),
      Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }),
      Plugin.GitHubFlavoredMarkdown(),
      Plugin.TableOfContents(),
      Plugin.TableOfContents({
        maxDepth: 2,
      }),
      Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
      Plugin.Description(),
      Plugin.FolderPage(),
      Plugin.Latex({ renderEngine: "katex" }),
    ],
    filters: [Plugin.RemoveDrafts()],