Médaillon du loup

Comment construire
The Witcher

Voyage dans les fichiers d'un jeu de 2007

CD Projekt Red ne dispose plus du code source d'origine du premier Witcher. Le jeu, lui, existe toujours — installé, jouable. Ce dossier part de cette version : nous avons ouvert ses archives, décodé leurs formats propriétaires et reconstitué la façon dont il est bâti. Tout ce qui suit — code, images, voix, musique — sort directement de ces fichiers.

Commencer la lecture
01 — Architecture

L'Architecture

The Witcher ne tourne pas sur un moteur maison. En 2003, CD Projekt — alors un petit studio surtout connu pour localiser des jeux en polonais — licencie l'Aurora Engine de BioWare, celui de Neverwinter Nights, et passe quatre ans à le transformer.

Le moteur qui en résulte — Aurora réécrit à environ 80 % — garde le squelette d'origine : son format d'archive KEY/BIF, son langage de script NWScript. Par-dessus, le studio greffe un nouveau rendu, la physique Havok et une couche Lua complète pour l'interface et les mini-jeux.

Compilé le 21 avril 2009 pour l'Enhanced Edition, le jeu empaquette 34 110 ressources dans 19 archives — près de six gigaoctets. Pour comprendre comment il est construit, il faut d'abord savoir comment il range ses fichiers.

Le format d'index a été modifié au passage : chaque entrée du fichier KEY occupe 26 octets là où BioWare en utilisait 22. C'est ce décalage qu'il a fallu retrouver avant de pouvoir ouvrir la moindre archive.

Lua Scripts
GUI, combat, mini-jeux, animations — 420 Ko rien que pour le poker aux dés
NWScript
IA, quêtes, dialogues, apparitions, histoire — 178 Ko de définitions API
Moteur — C++ (Aurora modifié)
hc.dll — 38 Mo de code natif, le cœur du moteur
Archives BIF / KEY
34 110 ressources, 19 archives, ~6 Go de données
Data/
  ├── main.key 887 Ko — Index de 34 110 ressources
  ├── textures00.bif 1 945 Mo — 8 968 textures
  ├── textures01.bif 908 Mo
  ├── meshes00.bif 1 845 Mo — Modèles 3D
  ├── sounds00.bif 427 Mo — Effets sonores
  ├── scripts00.bif 28 Mo — Scripts compilés
  ├── dialogues00.bif 36 Mo — 1 765 dialogues
  ├── items00.bif 47 Mo — Objets
  ├── quests00.bif 8 Mo — Quêtes
  ├── templates00.bif 18 Mo — Modèles d'entités
  ├── mg_poker00.bif 105 Mo — Mini-jeu de poker
  ├── Scripts/ NWScript + sources Lua
  └── *.2da Tables d'équilibrage

System/
  ├── witcher.exe 10 Mo — Exécutable
  ├── hc.dll 38 Mo — Le moteur
  ├── djinni!.exe 14 Mo — Éditeur de niveaux
  └── Scripts/ 4 252 lignes d'API NWScript
02 — Scripts

Les Deux Langages

Pour piloter son monde, The Witcher s'appuie sur deux langages à la fois.

NWScript, hérité de Neverwinter Nights, gère la logique de jeu : intelligence artificielle, quêtes, dialogues, apparition des créatures. Lua prend en charge tout ce qui touche à l'interface et au temps réel — menus, combat, animations, mini-jeux.

Une grande partie de cette logique porte une signature. Les initiales as> ouvrent des centaines de commentaires : ceux d'Arkadiusz Sito, à qui l'on doit notamment le système d'IA — 175 Ko de code à lui seul.

À droite, le code tel qu'il sort des archives. Passez d'un onglet à l'autre pour comparer les deux langages et l'API du moteur.

NWScript
Lua
API Moteur

Le système d'IA inc_ai.nss · 175 Ko

// File: inc_ai.nss
// Author: Arkadiusz Sito
// Copyright (c) CD Projekt Red Studio

void _ResetModifiers()
{
    object oPlayer = GetFirstPC();
    _SetModCurrentTarget(-2, oPlayer);
    _SetModTargetIsPC(-10, oPlayer);
    _SetModCloser(-3, oPlayer);
    _SetModGuardian(-20, oPlayer);
    _SetModDefensive(-20, oPlayer);
    _SetModPCAttacker(-60, oPlayer);
    _SetModMaxAttackers(5, oPlayer);
    _SetModMaxAttackersAmount(4, oPlayer);
}

Les profils de comportement, eux, sont commentés en polonais :

// tw> Sprawdza, jaki behaviour ma ustawiona postac
int GetBehaviourType(object oSource);

// as> Zeruje wszystkie profile zachowan u postaci
void ClearBehaviours(object oSource);

// as> Ustawia konkretny typ zachowania
void SetBehaviour(int nBehaviour, object oSource);

// as> Dodaje postac do druzyny obiektu oLeader
void AddToParty(object oLeader, object oSource);

// as> Okresla nowy cel ataku z uwzgednieniem zasad podzialu przeciwnikow
object DetermineNewTarget(object oIntruder, object oSource);

Le poker aux dés minigame_dices.lua · 420 Ko

Le fichier source du mini-jeu, avec ses notes de développement en polonais :

--[[ TODO:
BUGZORY:
- w momencie kiedy zapauzuje gre przed odpaleniem minigry
  a ona sie zalaczy to nie da sie odpauzowc bo spacja
  nie jest handlowana :)
  → « quand on met pause avant de lancer le mini-jeu
     et qu'il se lance quand même, on ne peut plus
     enlever la pause car espace n'est pas géré :) »

DTODO11 : bug - ai rzuca kostkami 2 razy :)
  → « bug — l'IA lance les dés 2 fois :) »
]]

Les attaques de la goule m0_monster_att.lua

-- Attaque de la goule (m0_ghoul_att)
DefAttack {
    WeaponType = "Monster",
    Level = "Basic",
    Name = "m0_ghoul_att",
    NumberOfHits = 1,
    Damage = {
        Medium = "Attack",
        Min = 20, Max = 34
    },
    Alternates = {
        { -- variante : saignement
            DefenderEffects = {
                { Type = "Bleeding",
                  BaseIntensity = 110 },
            }
        },
    }
}

Les fonctions du moteur

4 252 lignes de définitions. Au-delà des fonctions Aurora d'origine, le studio en a ajouté des dizaines, propres au Witcher :

// Adds current grease ability to item. U can remove it with ClearGrease.
// - sAbility - nazwa identyfikujaca zdolnosc
// - nMinutes - czas trwania w minutach swiata gry. 0 - ability nie konczy sie.
int AddGreaseAbility(string sAbility, object oItem,
                      int nMinutes = 0);
void ClearGrease(object oItem);

// tw> wejscie w fistfight
void EnterFistfightMode(object oCreature);

// as> Finds the best waypoint to hideout of rain.
object GetBestRainHideout(object oSource);
// as> Internal function.
void _DespawnBecouseOfRain(object oSource);
//        ^ « Becouse » — la faute est restée dans le binaire

Les initiales des développeurs se lisent dans les commentaires. Les rôles ci-dessous sont déduits des fonctions que chacun a annotées, non d'un générique officiel :

as>

Arkadiusz Sito — IA, combat, perception

tw>

Tomasz W. — moteur, dialogues, physique

ds>

UI, système de corruption

03 — Bestiaire

Le Bestiaire

Avant d'être une silhouette dans le brouillard, chaque monstre est une fiche de chiffres dans un fichier Lua.

Vitalité, attaque, esquive, parade ; ce qu'il craint, ce qui ne lui fait rien. Ces valeurs, lues telles quelles dans le code, suffisent à expliquer pourquoi un noyeur tombe sous l'argent et encaisse l'acier sans broncher.

Treize créatures, leurs statistiques exactes. Choisissez un nom.

04 — Modèles 3D

Les Modèles 3D

Tout ce qui bouge à l'écran — créature, personnage, arme — part d'un même objet : un maillage 3D rangé dans les archives meshes00.bif.

Le moteur hérite du format binaire d'Aurora : la géométrie est stockée en .mdb, enroulée autour d'une texture dépliée en UV, puis greffée sur un squelette qui porte les animations. Le même squelette sert plusieurs modèles — c'est ce qui permet à deux noyeurs de partager une démarche.

Les poids d'attache de chaque sommet à son os n'étaient documentés nulle part : il a fallu les reconstituer pour redonner vie au maillage. Voici quatre modèles décodés en glTF, rejoués dans le navigateur avec l'une de leurs animations d'origine.

Choisissez un nom à droite. Glissez sur la scène pour tourner autour du modèle.

Glissez pour tourner
Chargement…
Noyeur
Créature aquatique · cr_aquatic_clip_21
05 — Dialogues

L'Arbre de Dialogue

Une conversation, dans The Witcher, n'est pas du texte : c'est un arbre.

Chaque fichier .dlg, au format GFF de BioWare, enchaîne des répliques de PNJ et des réponses du joueur, reliées par des index. Le jeu a été écrit en polonais, puis doublé en plusieurs langues — la voix change, l'arbre reste le même.

Choisissez la langue, puis suivez l'échange réplique par réplique. Chaque ligne de PNJ peut être jouée dans sa voix d'origine.

Zoltan Chivay
Abigail
06 — Cartes de Romance

Les Cartes de Romance

Les cartes que Geralt collectionne au fil de ses rencontres ont été peintes à la main, une par une, en 1024×1024.

Elles n'étaient pas rangées avec les autres textures : il a fallu les retrouver dans une archive localisée à part, séparée parce que certaines régions en recevaient des versions différentes.

Choisissez un nom pour l'afficher en grand.

07 — Combat

L'Alchimie du Combat

L'acier pour les hommes, l'argent pour les monstres. Cette règle, célèbre dans la saga, n'est pas qu'affaire de lore : c'est un multiplicateur de dégâts.

Chaque créature porte un Steel_Mult et un Silver_Mult. Sur un alp, l'acier ne fait qu'un quart des dégâts quand l'argent en inflige le double.

À côté, le système d'huiles enduit la lame via une simple fonction du moteur, et chaque coup tire au sort entre plusieurs variantes — normale, lourde, étourdissement, mise à terre — chacune avec sa probabilité.

Acier contre argent

-- Exemple : l'Alp
MediumResistance = {
    Steel_Mult = 0.25,   -- l'acier ne fait que 25% des dégâts
    Silver_Mult = 2.0,   -- l'argent fait 200% des dégâts
    Attack_Mult = 0.25,  -- attaques non-épée : 25%
}

Le système d'huile AddGreaseAbility

// Ajoute une huile à une arme. Durée en minutes de jeu. 0 = permanent.
int AddGreaseAbility(string sAbility, object oItem, int nMinutes = 0);
void ClearGrease(object oItem);

Les attaques de l'Alp

Attaque de base

2 coups · 24–30 dégâts chacun
100 % (par défaut)

Attaque lourde

1 coup · 48–60 dégâts
19 % de chance

Étourdissement

1 coup · 48–60 dégâts + Stun (105)
32 % de chance

Mise à terre

1 coup · 48–60 dégâts + Knockdown
46 % de chance

Effets visuels par coup attackeffects.2da

TypeLueurTraînéeSangBlessures
Paradefx_parry_metalNon
Normalfx_sword_g_01fx_wpntrail01fx_blood_lNon
Critiquefx_sword_g_01fx_wpntrail01fx_bleeding00Oui
Finisherfx_sword_g_01fx_wpntrail01fx_bleeding01Oui + fx
Mains nuesfx_blood_zNon
08 — Archéologie

Archéologie de Développeurs

Sous les systèmes, il reste des gens. Le code de The Witcher en garde la trace.

Des commentaires en polonais à côté de l'anglais, des fonctions laissées vides au nom d'un collègue, une faute d'orthographe figée dans l'API, des notes de bug griffonnées entre deux accolades.

Rien de tout cela n'était destiné à être lu. C'est précisément ce qui le rend touchant.

Des emplacements réservés par prénom

void SlotDlaTomka01(); — « emplacement pour Tomek nº1 »
void SlotDlaTomka02();
void SlotDlaMichala5(); — « emplacement pour Michał nº5 » nwscriptdefn.nss — API du moteur

Une faute restée gravée

void _DespawnBecouseOfRain(object oSource);
int _IsDespawningBecouseOfRain(object oSource);

« Becouse » au lieu de « Because ». La faute est dans l'API du moteur — la corriger casserait tous les scripts qui appellent ces fonctions. as> Arkadiusz Sito — système de pluie

Les notes de bug du poker

Original : « w momencie kiedy zapauzuje gre przed odpaleniem minigry a ona sie zalaczy to nie da sie odpauzowc bo spacja nie jest handlowana :) »

Traduction : « Quand on met le jeu en pause avant que le mini-jeu démarre et qu'il démarre quand même, on ne peut plus enlever la pause parce que la barre d'espace n'est pas gérée :) » minigame_dices.lua — section BUGZORY
« mozna kliknac kostke w locie a ona wtedy wroci do puli zamiast wpasc na baze :) »

« On peut cliquer sur un dé en plein vol et il retourne dans le pool au lieu d'atterrir sur le plateau :) » — noté + fixé juste en dessous. minigame_dices.lua

« Serious lack of data »

Si witcher.ini est introuvable au démarrage, le moteur n'affiche ni « Fichier introuvable », ni « Erreur de configuration ». Le titre de l'erreur est : « Serious lack of data » — grave manque de données. Quelqu'un, ce jour-là, s'est fait plaisir. séquence d'initialisation du moteur

Le hasard, à la milliseconde près

Au démarrage, le générateur aléatoire est initialisé avec GetTickCount() passé à srand(). La conséquence : le hasard du jeu est déterministe si l'on lance la partie exactement à la même milliseconde. Avis aux speedrunners. séquence d'initialisation du moteur

La fonction de 92 Ko

À une seule adresse se cache une fonction C++ de 94 912 octets ; le décompilateur a renoncé au bout de soixante secondes. C'est, presque à coup sûr, la machine à états du combat : chaque mode, chaque enchaînement, chaque parade, chaque pugilat, réunis au même endroit. Et l'ancien système n'a pas été effacé — m_bUseOldAttackMechanics et m_bUseOldDefenceMechanics le gardent compilé, simplement désactivé. L'équivalent logiciel du numéro de son ex gardé dans le téléphone, « au cas où ». machine à états du combat — 0x0097AC50

Réglages cachés du moteur

Le moteur expose des centaines de paramètres de configuration. Quelques-uns trahissent leur auteur.

m_bDontYouEverTouchMyGamma

« Ne touche jamais à mon gamma. » Un autre système écrasait son réglage ; plutôt qu'un ticket, il a ajouté un booléen. Le nom de la variable est le message.

m_bCrazyLittleThing

Référence à Queen, Crazy Little Thing Called Love (1979). Ce qu'il contrôle reste un mystère — probablement un effet de rendu.

m_bKillAllAnimals

Exactement ce qu'on imagine : tue d'un coup toute la faune. Pratique pour déboguer une quête sans bestioles dans les pattes.

m_bPCImmune

Mode dieu. Un seul booléen, aucune interface.

m_bEnableAreksAsserts

Les assertions personnelles d'un développeur nommé Arek, qu'il pouvait activer sans noyer le reste de l'équipe sous ses diagnostics.

Le clavier qui affichait Geralt

Sept classes CLCD* dorment dans le binaire : le jeu gérait nativement les claviers Logitech G15 et G510 et leur petit écran monochrome. CLCDStreamingText y faisait défiler objectifs de quête et état du combat ; CLCDGfx dessinait quelques graphismes. Jamais annoncé, presque personne n'avait ce clavier — mais quelqu'un, chez CDPR, en possédait un et trouvait classe d'y voir la barre de vie de Geralt. Alors il l'a construit, intégré, et livré à tout le monde. CLCDManager · CLCDStreamingText · CLCDGfx

Des fonctions Lua qui détonnent

L'API Lua compte 2 391 fonctions. La plupart sont raisonnables. Pas toutes.

BelchModel

Fait… roter un modèle 3D. Le nom apparaît dans les gestionnaires d'erreur, preuve que la fonction est bien réelle et appelable.

Amputate

Retrait de membre. Couplée à la table decap.2da, c'est l'implémentation des coups fatals : le script l'appelle avec la cible et le membre.

IsItemGrease

Un test booléen dédié : cet objet est-il une huile de lame ? L'alchimie avait besoin d'un chemin rapide pour cette seule question — on mesure à quel point les huiles étaient centrales.

Le musée des fautes

BECOUSE n'était pas seule. Ces chaînes ont voyagé dans chaque copie du jeu, sur toutes les plateformes, dans les quatorze langues — invisibles, car purement internes.

Chaîne livréeAurait dû être
SPN_MonsterSpanwScriptSpawn
STATE_FADEINGFading
tempoarary module filetemporary
aquired (deux fois)acquired
Verion controlVersion
simultanouselysimultaneously
HasBeganHasBegun

Aucune ne provoque de bug. Aucune n'est visible. Elles sont, à leur façon, la preuve que des gens ont bâti tout cela — vite, le soir, en anglais qui n'était pas leur langue, dans un bureau de Varsovie en 2007.

Les modules .adv, en polonais

Les premiers sont des modules d'aventure indépendants, lancés depuis le menu « Nouvelles aventures ». La campagne principale, elle, s'appuie sur m1_module.adv, m2_module.adv, etc.

bagna.adv

« Les marais » — module d'aventure

upiory.adv

« Les spectres » — module d'aventure

wesele.adv

« Le mariage » — module d'aventure

podstepy.adv

« Les stratagèmes » — module d'aventure

wiedzminkolaj.adv

« Sorceleur-Noël » — module d'aventure

m1_module.adv

185 Mo — chapitre de la campagne principale

09 — Musique

La Bande Originale

Adam Skorupa et Paweł Błaszczak ont composé la musique du jeu : orchestre, chœurs, instruments folkloriques d'Europe centrale.

Huit pistes, extraites intactes du dossier Soundtracks. Lancez-en une — le fond sonore du dossier s'efface pour lui laisser la place.

Extrait et décompilé par Nova & Laurent

Juin 2026 — à partir de The Witcher Enhanced Edition (Steam).

Aucun code source n'a été « volé ». Tout a été extrait des fichiers du jeu acheté légalement, à l'aide d'outils écrits pour décoder les formats binaires propriétaires.