OneRP — Viser des milliers de joueurs au même endroit, là où la concurrence plafonne à ~500
Faire interagir des milliers de personnes en simultané sur le même monde virtuel : en architecture HTTP classique, chaque action déclenche un déluge de requêtes et la latence cumulée tue l'expérience. Voici pourquoi j'ai supprimé toutes les requêtes REST — et comment j'ai pensé la défense anti-triche en parallèle.
Cet article décortique le projetOneRPEn bref
Faire interagir des milliers de personnes sur le même monde, là où un serveur GTA RP haut de gamme plafonne autour de 500 : en HTTP classique, la latence cumulée tue l'expérience. Deux décisions structurent OneRP — zéro REST (état réactif poussé via Fusion) pour tenir la charge, et une défense en profondeur anti-triche dont la seule couche qui bloque vraiment est l'autorité serveur. Ce que ça montre : penser un système à l'échelle dans un runtime hostile (le binaire est décompilable), et la maturité de traiter la sécurité comme un spectre, pas comme un mur.
Le contexte : un runtime client hostile
Développer pour un runtime de jeu moddé, c'est développer dans un environnement où tout le monde peut décompiler votre code. Les DLL C# se décompilent en quelques secondes avec ILSpy ou dnSpy. Les joueurs le font. Les concurrents aussi. Comment protéger un logiciel commercial dans ces conditions ?
OneRP est le successeur de SARP/V-Multi — mais avec 9 ans d'expérience en plus. (Sur l'histoire de cette lignée, de SA-MP à la fusion avec GTA Network, voir L'écosystème multijoueur GTA, vu de l'intérieur.) L'architecture a été repensée de A à Z pour résoudre ce problème fondamental.
Avant d'aller plus loin, un mot sur la cible chiffrée : 2 048 connexions concurrentes est l'objectif de dimensionnement du framework, pas un benchmark publié — la barre que je me suis fixée sachant qu'un serveur GTA RP haut de gamme classique plafonne autour de 500. Les chiffres ci-dessous sont à lire dans cet esprit : des ordres de grandeur d'ingénierie, pas des mesures marketing.
Zéro endpoint REST
La première décision radicale : éliminer les API REST.
Sur un serveur GTA RP classique, l'architecture type :
- Le client veut afficher le solde bancaire
- Il envoie
GET /api/bank/balance - Le serveur répond avec le JSON
- Un autre joueur transfère de l'argent → le solde affiché est faux
Avec ActualLab Fusion, les données se synchronisent automatiquement :
[ComputeMethod]
public virtual async Task<decimal> GetBalance(int accountId)
{
var account = await db.Accounts.FindAsync(accountId);
return account?.Balance ?? 0;
}
Quand le solde change, tous les clients abonnés sont notifiés en temps réel. Pas de polling. Pas de WebSocket custom. Le framework détecte les dépendances entre méthodes et propage les invalidations automatiquement.
Concrètement : des dizaines de ComputeServices Fusion, zéro endpoint REST. Le chemin de lecture passe par un cache réactif en mémoire ; quand rien n'a changé, une lecture est servie sans retoucher la base ni rejouer le calcul — l'ordre de grandeur est sub-milliseconde côté cache, pas une latence end-to-end que je présenterais comme un benchmark. La synchronisation est invisible du code applicatif.
La sécurité en défense en profondeur
Dans un monde où le DLL est décompilé dès la première mise à jour, la sécurité ne peut pas reposer sur l'obscurcissement. Aucun mécanisme client n'est imperméable seul — un attaquant déterminé finit toujours par contourner une protection isolée. La stratégie est donc d'empiler des couches dont l'autorité réelle vit côté serveur, là où le tricheur n'a pas la main.
Authentification et session
- Token de session signé : lié à des attributs de la session (et non à un secret embarqué dans le binaire, qui serait trivialement extrait).
- Challenge-response : le serveur envoie périodiquement des défis que le client doit résoudre correctement.
- Heartbeat versionné : un battement de cœur régulier qui transporte la version du client, pour refuser les versions obsolètes ou bricolées.
Intégrité côté serveur
- L'autorité reste serveur : c'est le point central. Le client ne fait que demander ; le serveur recalcule et compare l'état déclaré à l'état qu'il calcule lui-même. Une valeur impossible (solde, position, vitesse) est rejetée à la source.
- Vérification d'assemblies : contrôle des binaires chargés côté client pour repérer les altérations grossières.
- Rotation de secrets : les secrets de session ne sont pas figés dans le binaire et tournent dans le temps, ce qui réduit la durée de vie d'un secret extrait par décompilation.
Réponse graduée
- Détection statistique : on accumule un score de suspicion plutôt que de bannir au premier signal — moins de faux positifs.
- Dégradation progressive : plusieurs niveaux de réponse, volontairement bruités, pour que le tricheur ne puisse pas distinguer un bug d'une détection.
- Audit trail : chaque action suspecte est loguée pour analyse manuelle.
Un scénario d'attaque concret
Prenons le cas classique : un joueur décompile le client, trouve la variable du solde bancaire, la passe à 1 000 000 € en mémoire, puis tente d'acheter un véhicule à 900 000 €.
Client (non fiable) Serveur (autorité)
────────────────── ──────────────────
balance local = 1 000 000 (triché)
"BuyVehicle(id: 42)" ───────▶ recalcule le VRAI solde depuis la base
→ 12 400 € (état autoritaire)
12 400 < 90 000 ⇒ REFUS
◀─────── "InsufficientFunds" + score suspicion +1
HUD repasse à 12 400 € (Fusion)
Le point-clé : le client ne dit jamais au serveur combien il a — il demande une action, et le serveur tranche à partir de l'état qu'il calcule lui-même. La valeur trichée en mémoire ne sert à rien, parce qu'elle n'est jamais l'autorité. La modification locale est au mieux cosmétique (et le mismatch déclaré/calculé alimente le score de suspicion).
La règle qui rend tout le reste secondaire
Si le serveur recalcule systématiquement l'état qu'il pourrait recevoir du client, alors aucune triche mémoire côté client ne peut créer de valeur. L'obscurcissement, la vérification d'assemblies, la rotation de secrets ne font que ralentir l'attaquant — l'autorité serveur est la seule chose qui le bloque.
Architecture multi-tenant
OneRP est un SaaS : plusieurs serveurs de jeu partagent la même infrastructure. L'isolation garantit qu'un serveur ne peut physiquement jamais accéder aux données d'un autre :
// Chaque requête EF Core est filtrée automatiquement
modelBuilder.Entity<Vehicle>()
.HasQueryFilter(v => v.TenantId == _currentTenant.Id);
Même avec du code buggy ou compromis, les données ne fuient jamais entre tenants.
Des dizaines de mini-apps React in-game
HUD, inventaire, banque, téléphone, tablette, crafting, administration, éditeur de monde... le framework compte près de 48 mini-applications temps réel. Chacune est du React 19 rendu via un navigateur embarqué dans le client lourd, et toutes sont connectées au serveur par le même pipeline Fusion — pas de requêtes HTTP, pas de polling.
Le stack complet : .NET 9/10, EF Core, MySQL, React 19, Vite 7, Tailwind CSS 4, MessagePack pour la sérialisation, JWT pour l'authentification.
Ce que j'en retiens
-
Le temps réel simplifie le code. Supprimer les API REST a réduit la complexité. Plus de gestion de cache, plus de synchronisation manuelle, plus de bugs de données périmées.
-
La sécurité est un spectre, pas un mur. On empile les couches précisément parce qu'aucune n'est imperméable seule — et parce que la seule autorité qu'un tricheur ne peut pas falsifier est celle qui vit côté serveur.
-
L'architecture Fusion est réutilisable. Le même pattern « zéro REST, état réactif » est ensuite parti sur d'autres SaaS — je le raconte côté produit dans Pourquoi j'ai supprimé toutes les API REST de mes SaaS. L'investissement initial se rentabilise sur chaque nouveau projet.
Le cadrage produit complet (framework, anti-triche, runtime contraint) est sur la page projet OneRP.
Florian Sola
Développeur Senior C#/.NET/GenAI — Lead Tech & Architecte logiciel · 9 ans d'expérience
La suite logique
Ce sujet ressemble à ce que vous devez livrer ? Parlons-en.
Je prends des sujets techniques critiques — du cadrage à la production, sans dette ni dépendance après le transfert. Le plus rapide pour voir si ça colle : 30 minutes en visio.
Réponse sous 24 h — souvent bien avant.