From 292cafd159764197a1884ccabd25979465bab06b Mon Sep 17 00:00:00 2001
From: Striven <sg.striven@cutecat.club>
Date: Fri, 20 Feb 2026 16:23:23 +0000
Subject: [PATCH] Add initial Storm docs
---
05_storm/01_overview.md | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 179 insertions(+), 0 deletions(-)
diff --git a/05_storm/01_overview.md b/05_storm/01_overview.md
index 4068160..d01e47e 100644
--- a/05_storm/01_overview.md
+++ b/05_storm/01_overview.md
@@ -1 +1,180 @@
# 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
+```
\ No newline at end of file
--
Gitblit v1.10.0