Recycle a confirmé : 'basic aes', clé server-side déductible client, by-the-book reimpl, pas de handshake DTLS. Remplace le GCM par AES-256-ECB + PKCS#7 + flag bit + termination bit (mirror du source UE 4.27). Flux : SetKey -> EncryptionAck clair -> Enable -> Challenge chiffré. Supprime AesGcmHandlerComponent.
The Cycle: Frontier — serveur privé (émulateur Prospect)
Émulateur des services en ligne de The Cycle: Frontier (jeu retiré de Steam en
septembre 2022), permettant de rejouer en solo sur un serveur auto-hébergé.
Fork de deiteris/Prospect, figé sur le
Build 8 / client Saison 2, buildé depuis les sources et déployé en conteneur via
une CI Gitea.
Ce n'est pas du multijoueur. Le raid tourne côté client (station solo) : chacun joue sa propre instance. Le serveur partage la progression, les comptes et les boutiques, pas la partie. Le vrai multi (squad en raid, voix de proximité) suppose un serveur de jeu Unreal dédié — voir Hors-périmètre & R&D.
Sommaire
- Comment ça marche
- Structure du dépôt
- Build & lancement du serveur
- Certificat TLS
- CI/CD
- Config du client (switch de serveur + certificat)
- État des fonctionnalités
- Hors-périmètre & R&D
- Crédits & licence
Comment ça marche
Le client officiel parle à PlayFab. On l'intercepte côté client et on le redirige vers notre serveur, qui réimplémente juste ce qu'il faut de PlayFab (auth Steam, CloudScript, UserData/TitleData, matchmaking solo).
flowchart LR
subgraph client [Poste de jeu]
L[Prospect.Client.Loader<br/>injecte l'agent] --> A[Prospect.Agent<br/>hooke l'URL PlayFab]
A -- lit --> B[backend.txt]
A --> G[Jeu (TCF)]
end
G -- HTTPS / SignalR --> API[Prospect.Server.Api<br/>émulateur PlayFab]
API --> M[(MongoDB)]
Prospect.Client.Loaderlance le jeu en injectant l'agent.Prospect.Agenthooke l'URL de l'API PlayFab et la remplace par le contenu debackend.txt(placé dansProspect/Binaries/Win64). Absent → fallbackhttps://127.0.0.1:8443.Prospect.Server.Apiémule PlayFab (auth Steam → JWT, CloudScript, données joueur) et pousse le temps-réel via SignalR. Les données vivent dans MongoDB.
Toute la redirection du client tient donc dans une seule valeur (backend.txt) — gérée
par l'outil Prospect.Client.Config.
Structure du dépôt
| Projet | Rôle |
|---|---|
Prospect.Server.Api |
Cœur : émulateur PlayFab (ASP.NET 8). Controllers Client/CloudScript/Multiplayer, services Auth/UserData/TitleData/Database(Mongo)/Qos, hub SignalR. |
Prospect.Steam |
Validation du ticket Steam (auth). |
Prospect.Client.Loader |
Loader C++ : lance le jeu et injecte l'agent. |
Prospect.Agent |
Agent C++ injecté : hooke l'URL PlayFab → backend.txt. |
Prospect.Client.Config |
Utilitaire multi-OS : écrit backend.txt, importe le certificat, lance le jeu. Voir son README. |
Prospect.Server.Game |
Serveur de jeu dédié (squelette, R&D). |
Prospect.Unreal[.Generator/.Tests] |
Réimplémentation C# du netcode Unreal (R&D serveur dédié). |
utils/ |
generate_ssl.py — génération du certificat auto-signé. |
Build config Season 2 Release obligatoire pour l'API (le code sélectionne la saison
via #if SEASON_2_RELEASE / SEASON_3_RELEASE → sinon #error Unsupported build type).
Build & lancement du serveur
Le serveur tourne en conteneur. L'image est buildée depuis les sources par le
Dockerfile (multi-stage SDK .NET 8 → runtime aspnet 8, publish en
Season 2 Release).
Build de l'image
docker build -t the-cycle .
Lancement
Il faut une instance MongoDB joignable et un certificat TLS monté (voir section suivante). Variables d'environnement :
| Variable | Rôle |
|---|---|
DatabaseSettings__ConnectionString |
URI de connexion MongoDB. |
DatabaseSettings__DatabaseName |
Base à utiliser (ex. ProspectDb). |
AuthTokenSettings__Secret |
Secret de signature des JWT émis par le serveur. |
PlayFabSettings__SignalRURL |
URL SignalR telle que le client doit l'atteindre (voir gotcha ci-dessous). |
Kestrel__Certificates__Default__Path |
Chemin du .pfx dans le conteneur. |
Kestrel__Endpoints__Https__Url |
ex. https://0.0.0.0:8443. |
SteamWebApiKey |
(optionnel) clé Steam Web API pour récupérer les pseudos ; inerte si absente. |
docker run -d --name the-cycle-api \
-e DatabaseSettings__ConnectionString="mongodb://user:pass@HOST:27017/?authSource=ProspectDb" \
-e DatabaseSettings__DatabaseName="ProspectDb" \
-e AuthTokenSettings__Secret="<secret>" \
-e PlayFabSettings__SignalRURL="https://<host-public>:8443/signalr/?hub=pubsub" \
-e Kestrel__Endpoints__Https__Url="https://0.0.0.0:8443" \
-e Kestrel__Certificates__Default__Path="/certs/certificate.pfx" \
-v "$PWD/certs:/certs:ro" \
-p 8443:8443 \
the-cycle
⚠️
PlayFabSettings__SignalRURL= l'URL que le CLIENT doit joindre, pas127.0.0.1. Le serveur y renvoie le client pour l'event de matchmaking ; s'il pointe sur127.0.0.1, le déploiement en raid timeout. Mets le hostname/IP public du serveur.
Dev local (.NET)
dotnet build src/Prospect.Server.Api/Prospect.Server.Api.csproj -c "Season 2 Release"
dotnet run --project src/Prospect.Server.Api -c "Season 2 Release"
Certificat TLS
La connexion est en HTTPS et le client valide le certificat → il doit être auto-signé et
fait confiance côté client. Générer le .pfx avec utils/generate_ssl.py.
⚠️ Le SAN doit contenir
DNS:<ip>ETIP:<ip>, en plus des hostnames. Le HTTP du jeu (libcurl) accepte l'IP en SAN IP, mais le WebSocket (libwebsockets) valide l'IP contre les SAN DNS → sansDNS:<ip>, la connexion SignalR échoue (Hostname mismatch err=62). Inclure aussi2EA46.playfabapi.com,localhost,127.0.0.1et tous les hostnames publics utilisés dansbackend.txt.
Côté client, l'import du certificat est automatisé par
Prospect.Client.Config.
CI/CD
.gitea/workflows/build.yml — sur push main / preprod
(ou workflow_dispatch) :
- build de l'image depuis le
Dockerfile; - push sur le registry
git.nfteam.ovh/neckfire/the-cycle; - notification du résultat (ntfy).
Modèle de branches :
| Branche | Tag image | Usage |
|---|---|---|
preprod |
:preprod (+ :preprod-<sha>) |
banc de test — valider un build avant de merger |
main |
:latest (+ :<sha>) |
production |
Workflow type : coder → push preprod → tester sur le serveur preprod → PR preprod → main
→ la CI republie :latest. Le déploiement applique la nouvelle image
(docker compose pull && docker compose up -d).
Prospect.Client.Config(outil client, cross-OS) n'est pas buildé par cette CI — voir sa section publication.
Config du client (switch de serveur + certificat)
L'outil Prospect.Client.Config (binaire ProspectServerSwitcher, multi-OS
Linux/Proton + Windows) fait tout le boulot côté client :
- écrit
backend.txt(presets prod / preprod ou URL libre) ; - récupère le certificat en direct depuis le serveur ciblé (TLS) et le rend fiable :
- Windows : import dans Autorités de certification racines de confiance (utilisateur) ;
- Linux/Proton : import direct dans le préfixe Wine du jeu via
wine reg import(carwine certutilest cassé sous Proton) ;
- lance le jeu.
# menu interactif
ProspectServerSwitcher
# scriptable
ProspectServerSwitcher --folder "<...>/Prospect/Binaries/Win64" --set preprod
ProspectServerSwitcher --set https://mon-serveur:8443
Détails complets, gotchas Proton (préfixe non-Steam) et commandes de publication des binaires autonomes : src/Prospect.Client.Config/README.md.
Installation complète pas-à-pas pour un nouveau joueur (télécharger le client S2, le LoaderPack, importer le certificat) : FRIENDS-INSTALL.md.
État des fonctionnalités
Fonctionne :
- Login Steam, EULA, tutoriel
- Station solo (S2/S3) : onboarding, matchmaking & déploiement solo
- Inventaire & loadout, stash, vente, réparation
- Contrats / quêtes — y compris les objectifs kills et de zone (auto-crédités : pas de serveur dédié pour remonter les events runtime du raid client-hosted)
- Progression des factions
- Season pass : claim + gain d'XP de saison (niveau Fortuna)
- Boutiques d'items (Korolev / ICA / Osiris / QuickShop / CraftingStation)
- Aurum Shop (cosmétiques) + rotation daily/weekly
- Craft, Quarters, solde joueur, connexion quotidienne
- Apparence & emotes
- Assurance : débit de la prime au déploiement + payout à la mort
- Stats de carrière (valeurs à 0 — non traçables sans serveur de jeu)
- Présence des amis (en ligne / en raid)
- Pseudos réels via Steam Web API (le client n'envoie que le SteamID)
- Cartes : Bright Sands, Crescent Falls, Tharis Island
Non implémenté / hors-périmètre :
- Squad / multi dans le même raid — nécessite un serveur de jeu dédié
- Voix de proximité (login/join Vivox = placeholders)
- Free loadouts & presets (Saison 3 uniquement)
- Achat de cosmétiques (endpoint d'achat vanity distinct, non câblé)
- Catalogue des récompenses Fortuna (DataTable côté client, dans des paks chiffrés)
- Défis quotidiens Fortuna
Hors-périmètre & R&D
Un serveur de jeu Unreal dédié (Prospect.Server.Game + Prospect.Unreal, branche
game-server) est en cours de reverse-engineering pour, à terme, permettre le vrai multi.
État : handshake stateless UE, séquençage et décodage des bunches franchis, canal de
contrôle ouvert, NMT_Hello parsé. Bloqué sur le chiffrement DTLS-PSK du client
(clé dérivée du user_id), derrière un exe packé (BattlEye) → la dérivation n'est pas
extractible statiquement. Détails dans NETCODE-RND.md (branche game-server).
Le « lobby squad » via l'API seule n'est pas faisable : l'invitation d'amis est gérée 100 % côté client Steam.
Crédits & licence
Fork de deiteris/Prospect (lui-même issu du
projet Prospect original). Voir LICENSE. Usage privé.