# Storm: Overview **Storm** is Ubisoft's proprietary matchmaking engine. The engine itself is **game-agnostic**, meaning all Storm-based games connect to single central regional servers which co-ordinate games between players. There is very little information online about the Storm matchmaking service. ## Routers In the `us-sdkClientStorm` field for the game's [Space](../03_ubisoft_services/03_structure.md#spaces) parameters, a set of **router provisioning URLs** is provided: ```json { "parameters": { "us-sdkClientStorm": { "fields": { "detectConfig": "EnableDebug=false;ValidateDetect=true", "detectProvisioningUrl": "https://ncsa-storm.ubi.com/v1/natdetect", "routerProvisioningUrl": "https://apac-storm.ubi.com/v1/router;https://emea-storm.ubi.com/v1/router;https://ncsa-storm.ubi.com/v1/router;https://ap-southeast-2-storm.ubi.com/v1/router", "matchmakingSandboxName": "SM_PC_LNCH_A", "traversalProvisioningUrl": "https://ncsa-storm.ubi.com/v1/nattraversal", "matchmakingSandboxSpaceId": "4c29e04e-14d2-4245-a3c4-bf4703461119" }, "relatedPopulation": null } } } ``` The URLs provided each provider an endpoint with a versioning number at the end. For example, Hyperscape makes requests to: `https://apac-storm.ubi.com/v1/router/19`. This is an **authenticated route**, meaning a Ubisoft ticket must be provided in the **Authorization** header. Here is an example request: ### Endpoint ``` GET /v1/router/19 ``` **Host:** `ncsa-storm.ubi.com` ### Headers | Header | Value | |---------------|---------------------| | Authorization | `Ubi_v1 t={ticket}` | ### Example Response ```json { "router": "52.207.248.146:12000" } ``` The client can then make UDP requests to this router at the specified port, packets which are structured according to the [Storm Protocol](#storm-protocol) ## Storm Protocol Storm appears to use a mix of **positive acknowledgements** (ACKs), and **negative acknowledgements** (nACKs). ### Ping Packet | Name | Description | Type | |---------------|----------------------------------------------------------------------------------------------------------|------------------------| | Connection ID | The ID of the sender. At this stage `null`. | `u16` | | Unknown | Unknown | `20 bytes` | | Opcode | Packet opcode, `0x10` for ping | [Opcode](#enum-opcode) | | Nonce | Incrementing nonce. First 3 bytes are seemingly random, last byte is incrementing | `u32` | | App ID | Some sort of ID. Client sends the application ID for `Hyperscape`, server responds with an unknown UUID. | `UUID` | | Unknown | Unknown | `u8` | ### Unknown Handshake Packet 1 > [!IMPORTANT] > This is a **possible structure**, but highly unlikely due to strange type sizes. This is derived from seeing which bytes are **static**, and which seem to change on each connection. | Name | Description | Type | |---------------|--------------------------------------------------------------------|------------------------| | Connection ID | The ID of the sender. At this stage `null`. | `u16` | | Unknown | Unknown | `20 bytes` | | Opcode | Packet opcode, `0xe0` for this message | [Opcode](#enum-opcode) | | Unknown | Unknown | `u16` | | Nonce | Some sort of incrementing nonce | `u32` | | App ID | Some sort of ID. Client sends the application ID for `Hyperscape`. | `UUID` | | Unknown | Unknown | `UUID` | | Unknown | Unknown | `u32` | | Unknown | Unknown | `20 bytes` | | Unknown | Unknown | `u8` | | Unknown | Unknown | `3 bytes` | | Unknown | Unknown | `26 bytes` | | Unknown | Unknown | `u16` | | Unknown | Unknown | `7 bytes` | | Unknown | Unknown | `13 bytes` | | Unknown | Unknown | `10 bytes` | | Unknown | Unknown | `u16` | ### Enum: `Opcode` Size: `u8` | Name | Description | Value | |---------|-----------------------------------------|--------| | Ping | Used for determining round-trip latency | `0x10` | | Unknown | Unknown | `0xe0` | ## Detecting Best Region When multiple regional routers are provided in the [`routerProvisioningUrl` field](#routers), the game sends exactly **5** packets to calculate the average round-trip latency. The lowest latency server is then selected. This stage is not required. If only one regional router is provided, then it moves straight to the [handshake](#handshake). Here is an example conversation: ``` [C -> S] 0000 0000000000000000000000000000000000000000 10 00000000 1e38eb00 69e55abd5aca4eeebe94ce3944c72e73 c4 [S -> C] 0000 0000000000000000000000000000000000000000 10 00000000 1e38eb00 63aba5df1da34eab863359bb2f53cd13 00 [C -> S] 0000 0000000000000000000000000000000000000000 10 00000000 1e38eb01 69e55abd5aca4eeebe94ce3944c72e73 c4 [S -> C] 0000 0000000000000000000000000000000000000000 10 00000000 1e38eb01 63aba5df1da34eab863359bb2f53cd13 00 [C -> S] 0000 0000000000000000000000000000000000000000 10 00000000 1e38eb02 69e55abd5aca4eeebe94ce3944c72e73 c4 [S -> C] 0000 0000000000000000000000000000000000000000 10 00000000 1e38eb02 63aba5df1da34eab863359bb2f53cd13 00 [C -> S] 0000 0000000000000000000000000000000000000000 10 00000000 1e38eb03 69e55abd5aca4eeebe94ce3944c72e73 c4 [S -> C] 0000 0000000000000000000000000000000000000000 10 00000000 1e38eb03 63aba5df1da34eab863359bb2f53cd13 00 [C -> S] 0000 0000000000000000000000000000000000000000 10 00000000 1e38eb04 69e55abd5aca4eeebe94ce3944c72e73 c4 [S -> C] 0000 0000000000000000000000000000000000000000 10 00000000 1e38eb04 63aba5df1da34eab863359bb2f53cd13 00 ``` If the router server fails to reply to these packets, the client re-sends them with a **different** starting nonce. Once a router has been selected, the client then performs what is presumably a [handshake](#handshake). ## Handshake This appears to be some sort of handshake sequence for connecting. ``` [C -> S] 77b2 0000000000000000000000000000000000000000 e0 0003 c210 2013 69e55abd5aca4eeebe94ce3944c72e73 3d1e94062594475797cdfb247c1730c0 00130000 0c0ba989e03c0574f2a0 a6 032e09 81500003ffff0c2b0307802a29 0e86 61800000040002 5d8a0c0a5ddaca0b4a5e00d010 989959581d 9c00 [S -> C] 2450 0000000000000000000000000000000000000000 e0 0000 0020 2013 63aba5df1da34eab863359bb2f53cd13 ffffffffffffffffffffffffffffffff 00130001 0c0ba989e03c0574f2a0 b2 032e09 582280c85dc10a00 455455 61800000040000 1e0ff0c2b0307802a2 90e860 [C -> S] 2450 0000000000000000000000000000000000000000 e0 0003 e230 2013 69e55abd5aca4eeebe94ce3944c72e73 3d1e94062594475797cdfb247c1730c0 00130000 0c0ba989e03c0574f2a0 a6 032e09 81500003ffff0c2b0307802a29 0e86 61800000040002 5d8a0c0a5ddaca0b4a5e00d010 989959581d 9d0022aa2a80 ``` Note how the connection IDs swap -- the client sends the server's connection ID, and the server sends the client's connection ID. After the initial handshake, both the client and server start sending [heartbeats](#heartbeats) ## Heartbeats ``` [C -> S] 2450 0000000000000000000000000000000000000000 e0 0000 d6 60 [S -> C] 77b2 0000000000000000000000000000000000000000 e0 0000 00 60 [C -> S] 2450 0000000000000000000000000000000000000000 e0 0000 d6 60 [S -> C] 77b2 0000000000000000000000000000000000000000 e0 0000 00 60 ... repeated ... ``` These heartbeats are sent by the client and server **independently of eachother** approximately every `10` seconds, but are not acknowledged. The protocol relies on the negative presence of these packets to indicate disconnection. This seems to happen after `60` seconds of no heartbeat. ## Disconnect When the client closes the game, the client sends `8` disconnect packets each exactly the same. This is possibly done to ensure the highest chance of arrival, however the server does not appear to take much notice. ``` [C -> S] 6723 0000000000000000000000000000000000000000 e0 0001 664c2b0307802a291bd25300cff05b ffffffff c3 6723 0000000000000000000000000000000000000000 e0 0001 664c2b0307802a291bd25300cff05b ffffffff c3 6723 0000000000000000000000000000000000000000 e0 0001 664c2b0307802a291bd25300cff05b ffffffff c3 6723 0000000000000000000000000000000000000000 e0 0001 664c2b0307802a291bd25300cff05b ffffffff c3 6723 0000000000000000000000000000000000000000 e0 0001 664c2b0307802a291bd25300cff05b ffffffff c3 6723 0000000000000000000000000000000000000000 e0 0001 664c2b0307802a291bd25300cff05b ffffffff c3 6723 0000000000000000000000000000000000000000 e0 0001 664c2b0307802a291bd25300cff05b ffffffff c3 6723 0000000000000000000000000000000000000000 e0 0001 664c2b0307802a291bd25300cff05b ffffffff c3 [S -> C] ... heartbeats for 60 seconds ... 3b6a 0000000000000000000000000000000000000000 e0 0000 004a6019fe0b6158183c015148de93 ffffffff c3 3b6a 0000000000000000000000000000000000000000 e0 0000 004a6019fe0b6158183c015148de93 ffffffff c3 3b6a 0000000000000000000000000000000000000000 e0 0000 004a6019fe0b6158183c015148de93 ffffffff c3 3b6a 0000000000000000000000000000000000000000 e0 0000 004a6019fe0b6158183c015148de93 ffffffff c3 ```