# The Cycle: Frontier — serveur dédié gameplay (R&D netcode)

Branche `game-server`. Objectif : un serveur de jeu autoritatif écrit from scratch
(Prospect.Unreal, réimplémentation C# du netcode Unreal) pour du vrai co-op, sans
passer par un client-hôte P2P. **Statut : bloqué au chiffrement (voir plus bas).**

Client cible : **UE4 build `R3.5.0`** (`4.27.2` netcode), Steam depot 868271.

## Ce qui fonctionne (murs franchis)

La connexion d'un vrai client va jusqu'à l'entrée du login :

```
Handshake stateless UDP (cookie/challenge)        ✅
Reconstruction du paquet (PacketHandler)          ✅
Séquençage des paquets (adopt des seq client)     ✅
Alignement des bunches                             ✅  ← percée
Canal de contrôle ouvert                           ✅
NMT_Hello reçu et parsé                            ✅
Transition login Hello → Login                     ✅
Chiffrement DTLS-PSK                               ❌  ← mur final
```

### Percée : le bit de header spécifique R3.5.0

Le client écrit **un bit de plus** entre l'historique d'ack du `FNetPacketNotify` et
le payload packet-info, que l'UE 4.27 stock (EngineNetVer 16) n'a pas. Décodage
bit-à-bit d'un vrai paquet : l'en-tête fait **65 bits, pas 64**. En consommant ce bit
(`bCycleExtraHeaderBit` dans `UNetConnection.ReceivedPacket`), tout se réaligne :
`bHasPacketInfoPayload`, l'horloge jitter (10 bits) et `bHasServerFrameTime` tombent
juste, et le premier bunch du canal de contrôle parse proprement (ChIndex 0, bOpen,
bReliable = NMT_Hello). Sans ce fix, le `ChIndex` sortait en vrac (~1 049 000) et le
serveur droppait/plantait.

## Le mur final : chiffrement DTLS-PSK

Le client **exige** le chiffrement. Établi par reverse-engineering :

- `NMT_Hello` porte `EncryptionToken` = le **PlayFab user_id** du joueur
  (ex. `92EBCFE8C3EAF3AC`). Vu dans l'URL de connexion du client :
  `...?EntityToken=<JWT>?EncryptionToken=92EBCFE8C3EAF3AC`.
- Pile PacketHandler du client (ses propres logs) :
  `[DTLSHandlerComponent, StatelessConnectHandlerComponent]`.
- Le exe embarque les suites **`ECDHE-PSK-AES256-*`, `DHE-PSK-AES256-GCM-SHA384`**,
  la cvar **`DTLS.PreSharedKeys`**, et `DTLSPSKClientCallback` / `DTLSPSKServerCallback`
  → **DTLS en mode PSK** (clé pré-partagée 32 octets, identité = user_id).
- Compression : **OodleNetwork** compilé, mais **aucun dictionnaire `.udic`** →
  pass-through (les paquets ne sont ni compressés ni chiffrés au niveau paquet ;
  entropie faible + longues suites de zéros le confirment).

Proposer un challenge en clair (sans `NMT_EncryptionAck`) ne marche pas : le client
ferme le canal de contrôle juste après.

Pour finir il faudrait : (1) un **serveur DTLS-PSK** collé au framing du
`DTLSHandlerComponent` d'UE, et (2) la **PSK de 32 octets** dérivée par le client à
partir du user_id/EntityToken.

## Pourquoi la clé est inaccessible (statique)

L'exe `Prospect-Win64-Shipping.exe` est **packé/chiffré** (protection anti-triche,
BattlEye) :

- **Entropie de `.text` = 8.000** (maximum = aléatoire/chiffré ; du code normal ≈ 6.3).
- `.rdata` = 4.96 (normal → les strings restent lisibles, d'où les découvertes ci-dessus).
- Un scan brut du `.text` (76 Mo) ne trouve que **~76 instructions** → niveau du bruit :
  ce n'est pas du code sur disque, c'est du chiffré déchiffré au runtime.

Conséquence : Ghidra / radare2 n'analysent que du ciphertext ; **aucune référence** aux
fonctions de chiffrement n'est trouvable statiquement. La dérivation de la PSK vit dans
ce code chiffré.

**Seule voie restante (non tentée)** : dump mémoire au runtime. Le jeu tourne sous
Proton/Linux (BattlEye n'a pas de driver kernel sous Linux) → un autre process Linux
peut lire `/proc/<pid>/mem` et récupérer le `.text` **déchiffré**, puis l'analyser dans
Ghidra pour retrouver la dérivation. Zone grise ToS, plusieurs étapes.

## Fichiers clés

- `src/Prospect.Server.Game/Program.cs` — hôte du serveur de jeu (map Station, GameSession).
- `src/Prospect.Unreal/Net/UNetConnection.cs` — `ReceivedPacket` : fix du bit de header
  (`bCycleExtraHeaderBit`), adopt des séquences client, drop gracieux des bunches.
- `src/Prospect.Unreal/Runtime/UWorld.cs` — `NotifyControlMessage` : Hello/Login ;
  le `else` du bloc `NMT.Hello` documente le mur DTLS-PSK.

## Verdict

On a amené un serveur dédié gameplay The Cycle plus loin qu'aucun projet public connu
(le projet communautaire deiteris/Prospect n'émule que les services en ligne, pas le
netcode de jeu). Le mur restant — DTLS-PSK dont la clé est derrière un packer
anti-triche — est un chantier crypto + RE dynamique d'un autre ordre de grandeur.
