Introduction : Le fonctionnement du modding sous le moteur Unity
Le moteur de jeu Unity alimente une immense part de la production vidéoludique contemporaine sur PC, depuis les jeux de survie coopératifs comme Valheim et Lethal Company jusqu'aux simulations complexes telles que Cities: Skylines II et Dyson Sphere Program. Contrairement aux moteurs propriétaires s'appuyant sur des archives binaires monolithiques ou au système de plugins déclaratifs de Bethesda, les jeux développés sous Unity reposent sur l'écosystème logiciel .NET et C#.
La modification d'un jeu Unity n'implique généralement pas le remplacement destructif des archives de données stockées sur le disque. Le modding moderne sous Unity s'opère par injection dynamique d'assemblys en mémoire vive et interception de méthodes lors de l'exécution. Au cœur de cette infrastructure se trouvent BepInEx (Bepis Injector Extensible), un injecteur et chargeur de plugins open source, associé à la bibliothèque Harmony, développée par Andreas Pardeike.
Cependant, les créateurs de mods et les joueurs se heurtent fréquemment à des incompatibilités bloquantes en installant des mods sur différents jeux. L'origine de cette complexité réside dans le modèle de compilation du moteur : la profonde divergence architecturale entre le compilateur historique Mono et le compilateur anticipé IL2CPP (Intermediate Language to C++). Comprendre comment BepInEx et Harmony gèrent ces deux environnements est indispensable pour résoudre les plantages, les crochets inopérants et les conflits d'ordre de chargement.
La scission des moteurs de script : architecture Mono contre IL2CPP
Lors de la compilation et du déploiement d'un jeu Unity, les développeurs sélectionnent obligatoirement l'une des deux cibles d'exécution suivantes :
[Code source C# : Scripts de logique de jeu]
│
▼
[Compilateur C# Roslyn / Unity] ---> Common Intermediate Language (Bytecode CIL)
│
├─────────────────────────────────────────┐
▼ ▼
[Filière Backend Mono] [Filière Backend IL2CPP]
- Empaquette le CIL en assemblys managés - Transpilateur IL2CPP convertit CIL en C++
- Fichier principal : Assembly-CSharp.dll - Compilateur C++ génère GameAssembly.dll
- Exécuté par la machine virtuelle JIT Mono - Métadonnées extraites dans global-metadata.dat
- Réflexion complète, décompilation aisée - Code machine pur, absence de CLR/JIT natif
Le moteur d'exécution Mono
Dans un jeu compilé avec Mono, l'ensemble des règles de jeu, de l'intelligence artificielle et des contrôleurs d'entités est regroupé dans des assemblys managés .NET, notamment Managed/Assembly-CSharp.dll.
- Machine virtuelle d'exécution : Au lancement du jeu, la machine virtuelle Mono JIT (Just-In-Time) intégrée charge et interprète le bytecode CIL à la volée.
- Préservation des métadonnées : Les définitions de classes, les signatures de fonctions, les champs privés et les structures de types restent intégralement conservés dans les métadonnées de l'assembly.
- Accessibilité pour le modding : Des outils tels que dnSpy ou ILSpy permettent de décompiler
Assembly-CSharp.dllavec une fidélité presque parfaite au code source d'origine. Le moteur .NET permet d'intercepter et de modifier dynamiquement n'importe quelle méthode via la réflexion standard.
Le moteur d'exécution IL2CPP
Dans un jeu exploitant IL2CPP, la chaîne d'outils applique une transformation radicalement différente :
- Transpilation vers C++ : Le bytecode CIL est pris en charge par l'outil AOT (Ahead-of-Time) d'Unity, qui convertit l'intégralité du code C# en milliers de fichiers sources C++ natifs.
- Binaire natif pour la plateforme : Ces sources C++ sont compilées directement en une bibliothèque binaire machine, généralement nommée
GameAssembly.dll. - Extraction des métadonnées : L'environnement d'exécution CLR traditionnel est totalement supprimé. Les définitions de types et les chaînes littérales sont isolées dans un fichier binaire distinct nommé
global-metadata.dat. - Impact sur le modding : Il n'existe aucun assembly managé conventionnel à charger. Déposer des bibliothèques de mods C# classiques échoue immédiatement, car aucun interpréteur .NET n'est présent dans le processus pour les exécuter.
L'architecture d'amorçage de BepInEx
Pour injecter du code externe dans un exécutable Unity sans modifier son binaire, BepInEx recourt à la technique du détournement de proxy DLL (DLL proxy hijacking).
La chaîne d'injection Doorstop
Sous Windows, le chargeur de l'OS inspecte en priorité le dossier local de l'exécutable pour trouver les bibliothèques dynamiques requises avant de consulter les répertoires système (C:\Windows\System32). BepInEx exploite cet ordonnancement :
- La DLL proxy : BepInEx place une bibliothèque factice dans le dossier racine du jeu, sous le nom d'un composant Windows standard tel que
winhttp.dll,version.dlloudoorstop.dll. - Interception du chargement : Dès le lancement du jeu, Windows charge la fausse
winhttp.dlllocale à la place de la véritable bibliothèque système. - Amorçage du moteur : La DLL proxy active Unity Doorstop, qui charge immédiatement en mémoire vive les fichiers
BepInEx.Preloader.dlletBepInEx.Core.dllavant même que les routines d'initialisation du moteur Unity ne prennent la main. - Détection des extensions : Une fois l'environnement opérationnel, BepInEx ouvre une console de débogage (si configurée), démarre la journalisation et analyse le dossier
BepInEx/plugins/pour charger les mods compatibles.
BepInEx 5 contre BepInEx 6 : Le pont d'interopérabilité IL2CPP
- BepInEx 5 : La version de référence conçue exclusivement pour les jeux basés sur Mono. Elle s'appuie directement sur l'interpréteur Mono interne du jeu pour exécuter les extensions héritant de
BaseUnityPlugin. - BepInEx 6 : Une refonte architecturale majeure conçue pour gérer à la fois les titres Mono et IL2CPP. Pour les jeux IL2CPP, BepInEx 6 intègre la solution Il2CppInterop (anciennement Unhollower). Lors du premier démarrage, le système analyse
GameAssembly.dlletglobal-metadata.dat, puis génère automatiquement des assemblys C# managés d'interopérabilité dans le dossierBepInEx/interop/. Ces wrappers permettent aux développeurs de mods d'écrire du code C# orienté objet classique tout en relayant les appels vers la mémoire C++ native.
Harmony : Interception dynamique et modification des méthodes en mémoire
Après l'initialisation assurée par BepInEx, les mods s'appuient sur Harmony pour altérer la logique du jeu sans modifier un seul fichier sur le stockage. Harmony opère en réécrivant à chaud les prologues des méthodes compilées dans la mémoire JIT.
Les quatre catégories fondamentales de crochets Harmony
[Appel de la méthode d'origine : ex. PlayerController.TakeDamage(float amount)]
│
▼
[Crochet Prefix Harmony] ---> Renvoie true (poursuivre) OU Renvoie false (ignorer l'original)
│
▼
[Exécution de la méthode d'origine : Calcul et déduction des points de vie]
│
▼
[Crochet Postfix Harmony] ---> Lit/Modifie la valeur de retour (__result) ou l'état
│
▼
[Crochet Finalizer Harmony] ---> Intercepte les exceptions non gérées et évite le crash
- Prefix (
[HarmonyPrefix]) :- S'exécute immédiatement avant le déclenchement de la méthode d'origine du jeu.
- Peut inspecter et altérer les arguments d'entrée (par exemple, ajuster la valeur
float amountavant le calcul des dégâts). - Offre la possibilité de court-circuiter l'exécution : si le Prefix renvoie
false, la méthode originale du jeu est purement ignorée.
- Postfix (
[HarmonyPostfix]) :- S'exécute dès la fin de l'exécution de la méthode d'origine.
- Permet de lire et de réécrire la valeur de retour via le paramètre spécial
ref __result. - Constitue la méthode idéale pour déclencher des événements secondaires (comme débloquer un succès après la fabrication d'un objet).
- Transpiler (
[HarmonyTranspiler]) :- La méthode d'interception la plus fine et technique. Elle capture le bytecode CIL d'une méthode sous forme de séquence d'instructions (
IEnumerabledeCodeInstruction) avant la compilation JIT. - Permet d'insérer, supprimer ou remplacer des instructions opérationnelles spécifiques (comme convertir une constante de
ldc.i4.0enldc.i4.1).
- La méthode d'interception la plus fine et technique. Elle capture le bytecode CIL d'une méthode sous forme de séquence d'instructions (
- Finalizer (
[HarmonyFinalizer]) :- Agit comme un bloc de protection dynamique
try/finallyautour de la méthode interceptée. Il intercepte les exceptions non gérées, empêchant ainsi une erreur de mod de fermer brusquement le jeu vers le bureau.
- Agit comme un bloc de protection dynamique
Matrice de dépannage : Résolution des pannes courantes de modding Unity
Lorsque des mods refusent de se charger ou provoquent des fermetures inattendues du jeu, appliquez la grille d'analyse suivante pour diagnostiquer les défaillances architecturales :
| Symptôme de panne | Cause architecturale sous-jacente | Indicateur de diagnostic | Mesure corrective |
|---|---|---|---|
| Console absente / Mods ignorés | La DLL proxy est bloquée ou ignorée par le chargeur OS | Aucun fichier BepInEx/LogOutput.log créé | Vérifier la présence de winhttp.dll à la racine ; contrôler les droits du dossier |
| Fermeture immédiate au lancement (IL2CPP) | Utilisation de BepInEx 5 sur un jeu compilé en IL2CPP | GameAssembly.dll présent à la racine mais BepInEx 5 installé | Supprimer BepInEx et déployer BepInEx 6 x64 spécialisé pour IL2CPP |
| Cache d'interopérabilité périmé | Mise à jour du jeu sans régénération des wrappers | Erreur TypeLoadException ou MissingMethodException dans les logs | Supprimer le dossier BepInEx/interop/ pour forcer une régénération propre |
| Collision de crochets Prefix | Plusieurs mods interceptent la même fonction avec retour false | Un mod fonctionne correctement, l'autre perd ses fonctionnalités | Réorganiser l'ordre de chargement ; éviter les annulations agressives en Prefix |
| Blocage par système anti-triche | EAC ou BattlEye bloquent l'injection de la DLL proxy | Le jeu se ferme avec un avertissement de fichier non approuvé | Exécuter le jeu hors ligne ou appliquer les paramètres de lancement moddé officiels |
Lors de la mise en place d'ordres de chargement de mods, du débogage de conflits d'assemblys ou du développement d'extensions C# personnalisées pour différents moteurs de jeu, consulter les analyses techniques de 365Finds garantit une parfaite adéquation avec les contraintes d'exécution réelles. Envisager le modding Unity sous l'angle de la gestion de mémoire, de l'injection de proxys et du patchage non destructif de bytecode permet de pérenniser vos installations face aux mises à jour régulières des jeux.