# 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`](https://github.com/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](#hors-périmètre--rd). --- ## Sommaire - [Comment ça marche](#comment-ça-marche) - [Structure du dépôt](#structure-du-dépôt) - [Build & lancement du serveur](#build--lancement-du-serveur) - [Certificat TLS](#certificat-tls) - [CI/CD](#cicd) - [Config du client (switch de serveur + certificat)](#config-du-client-switch-de-serveur--certificat) - [État des fonctionnalités](#état-des-fonctionnalités) - [Hors-périmètre & R&D](#hors-périmètre--rd) - [Crédits & licence](#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). ```mermaid flowchart LR subgraph client [Poste de jeu] L[Prospect.Client.Loader
injecte l'agent] --> A[Prospect.Agent
hooke l'URL PlayFab] A -- lit --> B[backend.txt] A --> G[Jeu (TCF)] end G -- HTTPS / SignalR --> API[Prospect.Server.Api
émulateur PlayFab] API --> M[(MongoDB)] ``` - **`Prospect.Client.Loader`** lance le jeu en injectant l'agent. - **`Prospect.Agent`** hooke l'URL de l'API PlayFab et la remplace par le contenu de **`backend.txt`** (placé dans `Prospect/Binaries/Win64`). Absent → fallback `https://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`](#config-du-client-switch-de-serveur--certificat). --- ## 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](src/Prospect.Client.Config/README.md). | | **`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`](Dockerfile) (multi-stage SDK .NET 8 → runtime aspnet 8, publish en `Season 2 Release`). ### Build de l'image ```bash 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. | ```bash docker run -d --name the-cycle-api \ -e DatabaseSettings__ConnectionString="mongodb://user:pass@HOST:27017/?authSource=ProspectDb" \ -e DatabaseSettings__DatabaseName="ProspectDb" \ -e AuthTokenSettings__Secret="" \ -e PlayFabSettings__SignalRURL="https://: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**, pas `127.0.0.1`. > Le serveur y renvoie le client pour l'event de matchmaking ; s'il pointe sur `127.0.0.1`, > le déploiement en raid **timeout**. Mets le hostname/IP public du serveur. ### Dev local (.NET) ```bash 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:` ET `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** → sans `DNS:`, la connexion SignalR échoue > (`Hostname mismatch err=62`). Inclure aussi `2EA46.playfabapi.com`, `localhost`, > `127.0.0.1` et tous les hostnames publics utilisés dans `backend.txt`. Côté client, l'import du certificat est automatisé par [`Prospect.Client.Config`](#config-du-client-switch-de-serveur--certificat). --- ## CI/CD [`.gitea/workflows/build.yml`](.gitea/workflows/build.yml) — sur push `main` / `preprod` (ou `workflow_dispatch`) : 1. build de l'image depuis le `Dockerfile` ; 2. push sur le registry `git.nfteam.ovh/neckfire/the-cycle` ; 3. notification du résultat (ntfy). **Modèle de branches :** | Branche | Tag image | Usage | |---|---|---| | `preprod` | `:preprod` (+ `:preprod-`) | banc de test — valider un build avant de merger | | `main` | `:latest` (+ `:`) | 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` (car `wine certutil` est cassé sous Proton) ; - lance le jeu. ```bash # 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](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](FRIENDS-INSTALL.md)**. --- ## État des fonctionnalités **Fonctionne :** - [x] Login Steam, EULA, tutoriel - [x] Station solo (S2/S3) : onboarding, matchmaking & déploiement **solo** - [x] Inventaire & loadout, stash, vente, réparation - [x] 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) - [x] Progression des factions - [x] Season pass : claim + gain d'XP de saison (niveau Fortuna) - [x] Boutiques d'items (Korolev / ICA / Osiris / QuickShop / CraftingStation) - [x] Aurum Shop (cosmétiques) + rotation daily/weekly - [x] Craft, Quarters, solde joueur, connexion quotidienne - [x] Apparence & emotes - [x] Assurance : débit de la prime au déploiement + payout à la mort - [x] Stats de carrière (valeurs à 0 — non traçables sans serveur de jeu) - [x] Présence des amis (en ligne / en raid) - [x] Pseudos réels via Steam Web API (le client n'envoie que le SteamID) - [x] 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`](https://github.com/deiteris/Prospect) (lui-même issu du projet Prospect original). Voir [`LICENSE`](LICENSE). Usage privé.