Wolf Medallion

Comment construire The Witcher

Autopsie technique d'un jeu que même son studio ne peut plus reconstruire

En juin 2026, CD Projekt Red a révélé avoir perdu le code source de The Witcher 1. Nous avons ouvert le jeu, reverse-engineeré ses formats d'archive propriétaires, décompilé ses scripts, et extrait ses assets. Chaque image, extrait de code, voix, musique et donnée sur cette page a été directement extraite des fichiers du jeu, sans aucune source externe.

▼ DESCENDRE DANS LE TERRIER ▼
01 — Architecture

L'Architecture

The Witcher 1 tourne sur une version lourdement modifiée du moteur Aurora de BioWare, la même base que Neverwinter Nights. CD Projekt l'a rebaptisée « Electron » et l'a poussée bien au-delà de ses origines.

La pile technologique

Lua Scripts
GUI, combat, mini-jeux, animations (420 KB pour le poker aux dés !)
NWScript
IA, quêtes, dialogues, spawn, histoire (178 KB de définitions API)
Moteur Electron (C++)
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

L'arbre des fichiers

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 + Lua sources
  └── *.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

Le format KEY/BIF

Le format d'archive est hérité de BioWare, mais CDPR l'a modifié. La variante Witcher utilise des entrées de 26 octets au lieu de 22 dans le fichier KEY (16 octets de nom + 2 de type + 4 d'index + 4 d'index BIF). On a dû reverse-engineer le format pour extraire quoi que ce soit.

Construit le

2009, jour 111 (21 avril) — date de compilation de l'Enhanced Edition

34 110 ressources

Réparties en 45 types différents : textures, scripts Lua, dialogues, sons, templates GFF, journaux de quête...

02 — Scripts

Le Double Cerveau

The Witcher utilise deux langages de script simultanément : NWScript (hérité de Neverwinter Nights) pour la logique de jeu, et Lua pour l'interface et les systèmes temps-réel.

NWScript
Lua
API Moteur

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

Écrit par Arkadiusz Sito chez CD Projekt Red. Un système d'IA complet avec machine à états, gestion de groupes, formations, et perception :

// 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 créatures

Chaque PNJ a un profil comportemental. Les commentaires sont restés en polonais dans le code :

// 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> Aktualizuje licznik napastnikow
void _AdjustAttackers(int nDelta, 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 complet du mini-jeu de poker aux dés, avec des notes de développement en polonais absolument savoureuses :

--[[ 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é :)"

+ mozna kliknac kostke w locie a ona wtedy wroci
  do puli zamiast wpasc na baze :)
  → "on peut cliquer un dé en vol et il retourne
     dans le pool au lieu d'atterrir sur la base :)"

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

Le système de combat — combatsystem.luc, 32 Ko

Le fichier .luc est du bytecode Lua compilé, mais les définitions d'attaque sont en Lua source lisible :

-- Définitions d'attaque du Ghoul
DefAttack {
    WeaponType = "Monster",
    Level = "Basic",
    Name = "m0_ghoul_att",
    NumberOfHits = 2,
    Damage = {
        Medium = "Attack",
        Min = 20, Max = 24
    },
    Alternates = {
        { -- attaque spéciale : empoisonnement
            Helper = {
                Damage = { Min = 36, Max = 48 },
                DefenderEffects = {
                    { Type = "Poisoning",
                      BaseIntensity = 105 },
                }
            },
            Conditions = { Chance = 0.28 }
        },
    }
}

Les fonctions moteur de CDPR

4 252 lignes de définitions API. Au-delà des fonctions Aurora/BioWare standard, CDPR a ajouté des dizaines de fonctions spécifiques au Witcher :

// Adds current grease ability to item. U can remove it with ClearGrease.
// - sAbility - nazwa identyfikujaca zdolnosc
// - nMinutes - czas trwania zdolnosci 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);
// tw> wyjscie z fistfightu
void LeaveFistfightMode(object oCreature);

// ds> Otwiera panel "przelewu" zlota (np. do przekupstwa innego creature'a).
void OpenPlayerBribePanel(object oPlayer,
     object oTarget, int nAmount);

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

void SlotDlaTomka01();
void SlotDlaTomka02();
void SlotDlaMichala5();
void SlotDlaMichala6();

Les initiales des développeurs sont visibles dans les commentaires de l'API :

as>
Arkadiusz Sito — IA, combat, perception
tw>
Tomasz W. — Moteur, dialogues, physique
ds>
Inconnu — UI, système de corruption
03 — Bestiaire

Le Bestiaire Interactif

Les statistiques réelles de chaque monstre, extraites directement du code source Lua. Cliquez sur un monstre pour voir ses données complètes.

04 — Dialogues

L'Arbre de Dialogue

Les dialogues sont stockés en format GFF (Generic File Format) de BioWare, variante V3.3. Chaque fichier .dlg contient un arbre de nœuds NPC et de réponses joueur, liés par des index. Texte original en polonais, traduit ci-dessous.

Zoltan Chivay
Abigail
05 — Combat

L'Alchimie du Combat

Le système de combat repose sur des multiplicateurs de dégâts par type de médium (acier vs argent), des probabilités d'attaques alternatives, et un système d'huile de lame.

Acier contre Argent

Chaque monstre a un Steel_Mult et un Silver_Mult. C'est la mécanique centrale : l'épée d'acier pour les humains, l'argent pour les monstres. Ce n'est pas du lore, c'est un multiplicateur de dégâts codé en dur.

Comment ça marche dans le code
-- 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,  -- les attaques physiques non-épée : 25%
}

Le système d'huile (AddGreaseAbility)

La fameuse mécanique d'application d'huile sur les lames. Dans le code moteur, c'est une fonction C++ exposée au NWScript :

// 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 alternatives

Chaque monstre a une attaque de base et des alternatives déclenchées par probabilité. L'Alp, par exemple :

Attaque de base

2 coups, 24-30 dégâts chacun
100% de chance (fallback)

Attaque lourde

1 coup, 48-60 dégâts
19% de chance

Étourdissement

1 coup, 48-60 dégâts + Stun (intensité 105)
32% de chance

Mise à terre

1 coup, 48-60 dégâts + Knockdown
46% de chance

La table 2DA des effets d'attaque

Fichier attackeffects.2da, qui définit les effets visuels (traînées, sang, blessures) par type de coup :

Type Lueur Traînée Sang Blessures
Paradefx_parry_metalNon
Normalfx_sword_g_01fx_wpntrail01fx_blood_lNon
Critiquefx_sword_g_01fx_wpntrail01fx_bleeding00Oui
Finisherfx_sword_g_01fx_wpntrail01fx_bleeding01Oui + fx supplémentaire
Combat à mains nuesfx_blood_z (tête)Non
06 — Factions

Le Système de Factions

93 profils d'affiliation encodent toute la politique du monde de The Witcher. Chaque PNJ porte des drapeaux d'appartenance, d'hostilité, et de peur.

Les affiliations

⚔️
Ordre de la Rose Ardente
AFFILIATION_ORDER
👑
Témérie
AFFILIATION_TEMERIA
🛡️
Gardes
AFFILIATION_GUARD
🧑‍🌾
Roturiers
AFFILIATION_COMMONER
🐺
Sorceleur
AFFILIATION_WITCHER
🔮
Sorcier
AFFILIATION_SORCERER
🏹
Scoia'tael
OUTLAW_SCOIATAEL
🐍
Salamandre
OUTLAW_SALAMANDER
🗡️
Bandits
OUTLAW_BANDIT
🧝
Non-humains
AFFILIATION_INHUMAN
🐟
Vodyanoi
AFFILIATION_VODIANOI
🕷️
Kikimores (phéromones)
KIKIMORA_PHEROMONES

Les comportements

Agressif
Fonce au combat dès la perception
Défensif
Se bat uniquement si attaqué
Lâche
Fuit dès que la situation tourne mal

Le système d'alcool

Oui, il y a un système d'alcool codé dans les profils. Chaque PNJ peut avoir une tolérance à l'alcool :

const int PROFILE_TYPE_ALCOHOL_MILD_3    = 27;   // léger, 3 verres
const int PROFILE_TYPE_ALCOHOL_MILD_5    = 28;   // léger, 5 verres
const int PROFILE_TYPE_ALCOHOL_MILD_7    = 29;   // léger, 7 verres
const int PROFILE_TYPE_ALCOHOL_MEDIUM_3  = 30;   // moyen, 3 verres
const int PROFILE_TYPE_ALCOHOL_MEDIUM_5  = 31;   // moyen, 5 verres
const int PROFILE_TYPE_ALCOHOL_STRONG_7  = 35;   // fort, 7 verres
07 — Archéologie

Archéologie de Développeurs

Le code est truffé de traces humaines : commentaires en polonais, fonctions placeholder, fautes d'orthographe immortalisées dans l'API, notes de frustration dans les TODO.

Les « slots » réservés

Dans l'API du moteur (4 252 lignes de fonctions C++), on trouve des emplacements vides réservés à des développeurs individuels :

void SlotDlaTomka01(); — littéralement « Emplacement pour Tomek #1 »
void SlotDlaTomka02();
void SlotDlaMichala5(); — « Emplacement pour Michał #5 »
nwscriptdefn.nss — API du moteur Electron

C'est la version game dev de réserver des constantes dans un enum « au cas où ». Sauf qu'ici, c'est des fonctions moteur entières réservées par prénom.

La faute de frappe éternelle

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

« Becouse » au lieu de « Because ». Gravé dans l'API binaire du moteur. Impossible à corriger sans casser tous les scripts qui l'appellent.

as> Arkadiusz Sito — Le système de pluie

Les notes de bug du poker aux dés

Le fichier minigame_dices.lua (420 Ko !) est une capsule temporelle de game dev. Le développeur documentait ses bugs directement dans le code source :

Bug 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 :) »

Le smiley à la fin est le détail qui tue. C'est l'humour résigné d'un dev qui sait que ce bug va rester. minigame_dices.lua — ~2007
« 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 :) »

Marqué comme + fixé. Le correctif : « przesunalem tez obiekt fizyczny modeli do wyznaczonych punktow i juz gra » — « j'ai aussi déplacé l'objet physique des modèles aux points désignés et maintenant ça marche ». minigame_dices.lua — BUGZORY section

Les commentaires bilingues

Le code mélange librement polonais et anglais, parfois dans la même ligne :

// ds> Otwiera panel "przelewu" zlota (np. do przekupstwa innego creature'a).
//     "Ouvre le panneau de transfert d'or (par ex. pour corrompre une créature)"
void OpenPlayerBribePanel(object oPlayer, object oTarget, int nAmount);

// msl> Zerowanie poziomu postaci. Jej talentów, doświadczenia i poziomu.
//      "Remise à zéro du niveau du personnage. Ses talents, expérience et niveau."
void ResetXP(object oCreature);

// Zwraca 1 jezeli aktualnie zaladowany modul to savegame.
// "Retourne 1 si le module actuellement chargé est une sauvegarde."
int IsModuleSaveGame();

Les modules en polonais

Les noms internes des chapitres sont en polonais :

bagna.adv
« Les marais » — Chapitre 2
upiory.adv
« Les vampires/spectres » — Chapitre 3
wesele.adv
« Le mariage » — Chapitre 5
podstepy.adv
« Les stratagèmes » — Intrigues
wiedzminkolaj.adv
« Sorceleur-Noël » — Contenu spécial ?
m1_module.adv
185 Mo — Le plus gros chapitre
08 — Musique

La Bande Originale

Composée par Adam Skorupa, la bande originale de The Witcher mélange orchestration slave, chœurs médiévaux et instruments folkloriques polonais. Extraite directement du répertoire Soundtracks du jeu.