Build & Deploy / build (push) Successful in 32s
Replace the upstream Windows/local-run README with a dev-focused one: how the client redirection works (loader+agent+backend.txt), repo structure, building and running the API in a container, the TLS cert SAN gotcha, the Gitea CI/CD branch model (preprod:preprod / main:latest), the Prospect.Client.Config switch+cert tool, and an up-to-date feature status. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
250 lines
10 KiB
Markdown
250 lines
10 KiB
Markdown
# 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<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.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="<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**, 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:<ip>` ET `IP:<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:<ip>`, 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-<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`
|
|
(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é.
|