Dans les années 80, une société inconnue appelée Binary Systems a publié le jeu Starflight. Le jeu met le joueur dans le rôle d'un capitaine de vaisseau envoyé pour explorer la galaxie. Il n'y a pas de chemin défini, permettant aux joueurs de basculer librement entre l'exploitation minière, le combat de navire à navire et la diplomatie extraterrestre. L'intrigue plus large du jeu émerge lentement, à mesure que le joueur découvre qu'une ancienne race d'êtres provoque l'éclatement des étoiles et détruit toutes les créatures vivantes. Le jeu a été largement salué par les critiques contemporains et modernes et est l'un des premiers exemples de jeu bac à sable. Le jeu a influencé la conception de nombreux autres jeux pendant des décennies après sa sortie.
Pour en savoir plus sur le jeu, consultez les liens suivants :
Vous pouvez acheter le jeu chez GoG
La première fois que j’ai entendu parler du jeu, j’ai eu envie d’y jouer. Cependant, j'étais trop jeune et je ne parlais pas anglais. 20 plus tard, j'ai réessayé et ce fut une expérience très agréable. L'exploration est amusante, le scénario est épique et se termine par une surprise, c'est l'une des meilleures que j'ai vécues. Bien sûr, le jeu n’a pas bien vieilli, mais vous pouvez sentir le dévouement des développeurs envers le jeu. Il y a un aspect artistique dans ce jeu ainsi qu'une attention particulière portée aux détails par un artisan.
Autant jouer à ce jeu vraiment incroyable est amusant, autant l’ingénierie inverse de ce jeu l’est aussi. Vous suivez les traces des développeurs et vivez leurs processus de réflexion comme si c'était à nouveau l'année 1985. Pour ce jeu, attendez-vous à l'inattendu. Normalement, lorsque vous faites de l'ingénierie inverse sur un jeu aussi ancien, vous devez recevoir des dizaines de milliers de lignes de code assembleur pur, que vous pouvez analyser avec les outils habituels tels que IDA Pro. Mais pas cette fois. En fait, pour ce jeu, vous pouvez jeter les outils habituels. Ils sont inutiles. Vous êtes seul. La raison en est que Starflight a été écrit en Forth, une langue que je connaissais à peine.
Forth est le langage avec le minimalisme ultime en matière de syntaxe. Il n’y a pas plus de syntaxe que l’espace entre les « mots ». Vous pouvez écrire un lecteur et un interprète Forth en quelques lignes de code.
Dans une langue moderne, vous écrivez quelque chose comme
print ( 2 + 3 )
pour imprimer le résultat de 2+3. Dans Forth, cependant, cela ressemble à ceci.
2 3 + .
Forth est une machine à empiler, avec une notation polonaise inversée. L'interprétation est la suivante
La syntaxe est simple et l'interpréteur est simple. "2", "3", "+" et "." sont simplement appelés « mots ». Il n'y a pas de distinction syntaxique entre les données et le code. Certainement un langage à la hauteur des limites des premiers ordinateurs personnels.
Lorsque vous disséquez l'exécutable STARFLT.COM, il révèle des composants internes fantastiques.
Comme expliqué ci-dessus, Forth est une machine à empiler. En tant que mécanisme de codage, il utilise le threading indirect, une méthode très économe en espace pour stocker votre code compilé. Le code threadé a une forme qui consiste essentiellement entièrement en appels à des sous-programmes. Le threading indirect utilise des pointeurs vers des emplacements qui à leur tour pointent vers le code machine.
Disons que votre pointeur d'instruction pointe vers l'adresse 0x1000 et contient la valeur 16 bits Read16(0x1000)=0x0f72.
0x1000 : dw 0x0f72
La valeur 0x0f72 est l'équivalent codé du quatrième mot « + ». Rappelez-vous la description ci-dessus. Le mot « + » fait apparaître les deux dernières entrées de la pile, les additionne et repousse le résultat au sommet de la pile. Selon le threading indirect, cette valeur de 16 bits 0x0f72 est un pointeur vers un emplacement qui à son tour pointe vers le code machine. Lorsque vous lisez le contenu de la mémoire Read16(0x0f72), vous obtenez le pointeur vers 0x0f74. Et en effet, lorsque vous regardez cet emplacement mémoire et que vous le démontez, vous recevez ce qui suit
0x0f72 : dw 0x0f74
0x0f74 : pop ax
0x0f75 : pop bx
0x0f76 : add ax , bx
0x0f78 : push ax
0x0f79 : lodsw
0x0f7a : mov bx , ax
0x0f7c : jmp word ptr [ bx ]
Les quatre premières instructions effectuent exactement les opérations que le mot « + » devrait effectuer. Les trois dernières instructions assembleur commençant par "lodsw" augmentent le pointeur d'instruction et passent au code suivant.
Continuons. Maintenant, le pointeur d'instruction pointe vers 0x1002
0x1002 : dw 0x53a3
La lecture de l'adresse 0x53a3 révèle
0x53a3 : dw 0x1d29
0x53a5 : dw 0x0001
et le code correspondant
0x1d29 : inc bx
0x1d2a : inc bx
0x1d2b : push bx
0x1d2c : lodsw
0x1d2d : mov bx , ax
0x1d2f : jmp word ptr [ bx ]
A ce moment, le registre bx contient l'adresse de mot 0x53a3. Ce code place donc simplement l'adresse 0x53a5 en haut de la pile. Ce que nous avons fait, c'est fournir au programme un pointeur vers une variable. La variable a le contenu 0x0001. Le quatrième mot « @ » extrait l'adresse de la pile, lit son contenu et le repousse sur la pile.
Jusqu'à présent, j'ai pu identifier 6 256 mots contenant soit du code, soit des données.
Et c’est en fait tout ce que vous devez savoir sur la structure du code. Comme vous pouvez le constater, cela peut être un encodage économe en espace, mais en termes de vitesse, c'est une catastrophe. Toutes les quelques instructions de code machine, vous devez passer à un bloc de code différent.
L’équivalent du threading indirect en C ressemblerait à ceci.
uint16_t instruction_pointer = start_of_program_pointer ;
void Call ( uint16_t word_adress )
{
// the first two byte of the word's address contain
// the address of the corresponding code, which must be executed for this word
uint16_t code_address = Read16 ( word_address );
switch ( code_address )
{
.
.
.
case 0x0f74 : // word '+'
Push16 ( Pop16 () + Pop16 ());
break ;
.
.
.
}
}
void Run ()
{
while ( 1 )
{
uint16_t word_address = Read16 ( instruction_pointer );
instruction_pointer += 2 ;
Call ( word_address );
}
}
Le code exécuté pour un mot spécifique a accès à 5 variables majeures (16 bits)
Le désassemblage transpile le code FORTH en code de style C. La plupart du code transpilé est compilé. Pour comprendre ce que fait le programme, jetez un œil au tableau suivant. Il prend le "bytecode" (qui sont principalement des pointeurs 16 bits) en entrée et le transforme en C.
Quatrième code :
: .C ( -- )
Display context stack contents.
CR CDEPTH IF CXSP @ 3 + END-CX
DO I 1.5@ .DRJ -3 +LOOP
ELSE ." MT STK"
THEN CR ;
EXIT
Transformation:
Pointeurs 16 bits | AVANT | C |
---|---|---|
: .C ( -- ) | void DrawC() { | |
unsigned short int i, imax; | ||
0x0642 | CR | Exec("CR"); |
0x75d5 | CDEPTH | CDEPTH(); |
0x15fa 0x0020 | SI | if (Pop() != 0) { |
0x54ae | CXSP | Push(Read16(pp_CXSP) + 3); |
0xbae | @ | |
0x3b73 | 3 | |
0x0f72 | + | |
0x4ffd | FIN-CX | Push(Read16(cc_END_dash_CX)); |
0x15b8 | FAIRE | i = Pop(); |
imax = Pop(); | ||
do { | ||
0x50e0 | je | Push(i); |
0x4995 | 1.5@ | _1_dot_5_at_(); |
0x81d5 | .DRJ | DrawDRJ(); |
0x175d 0xfffd | -3 | Push(-3); |
0x155c 0xffff | + BOUCLE | int step = Pop(); |
i += step; | ||
if (((step>=0) && (i>=imax)) || ((step<0) && (i<=imax))) break; | ||
} while(1); | ||
0x1660 0x000b | AUTRE | } else { |
0x1bdc | "MT STK" | PRINT("MT STK", 6); |
0x06 | ||
0x4d | 'M' | |
0x54 | 'T' | |
0x20 | ' ' | |
0x53 | 'S' | |
0x54 | 'T' | |
0x4b | 'K' | |
ALORS | } | |
0x0642 | CR | Exec("CR"); |
0x1690 | SORTIE | } |
Le jeu se décline en 3 fichiers
Contenu de STARA.com
entrée | taille | description |
---|---|---|
ANNUAIRE | 4096 | contient le répertoire de STARA et STARB |
ELO-CPIC | 4816 | |
GAZ-CPIC | 3120 | |
MEC-CPIC | 2848 | |
MYS-CPIC | 6064 | |
NOM-CPIC | 1136 | |
SPE-CPIC | 1888 | |
THR-CPIC | 2480 | |
VEL-CPIC | 4672 | |
VPR-CPIC | 1248 | |
MIN-CPIC | 2096 | |
ÉCLABOUSSER | 16384 | Image |
MED-PIC | 2048 | Image |
PHAZES | 6144 | |
HUM-PIC | 480 | Image |
VEL-PIC | 432 | Image |
THR-PIC | 272 | Image |
ELO-PIC | 608 | Image |
ET-PIC | 640 | Image |
SAUVEGARDER | 124000 | |
MUSIQUE | 4960 | Superposition de codes |
TERRE | 1152 | Carte de la planète Terre |
GALAXIE | 6304 | |
CRÉDITS | 16384 | image |
COP-CPIC | 2928 | |
POLICES | 768 | |
CGA | 3600 | Routines de code machine pour la carte graphique CGA |
EGA | 3600 | Routines de code machine pour la carte graphique EGA |
Contenu de STARB.COM
entrée | taille | description |
---|---|---|
ANNUAIRE | 4096 | contient le répertoire de STARA et STARB |
EXEMPLE | 150528 | Arborescence regroupant l'essentiel du contenu du jeu |
BOÎTE | 1024 | Tableau |
TRANSPORT BANCAIRE | 144 | Tableau |
MEMBRE D'ÉQUIPAGE | 128 | Tableau |
NAVIRE | 1936 | Tableau |
ÉLÉMENT | 544 | Tableau |
ARTEFACT | 1584 | Tableau |
PLANÈTE | 1360 | Tableau |
SPÉCIMEN | 448 | Tableau |
BIO-DONNÉES | 448 | Tableau |
TPORT-PIC | 2416 | Image |
BPORT-PIC | 3984 | Image |
ANALYSER-TEXTE | 3200 | Tableau |
BOUTONS | 944 | Tableau |
ICÔNE1 : 1 | 912 | |
ICÔNE1:2 | 912 | |
ICÔNE1:4 | 912 | |
NOM-ICÔNE | 736 | |
DPART-OV | 1552 | Superposition de codes |
RÉGIONS | 176 | Tableau |
CRÉATURE | 17024 | Tableau |
CHKFLIGHT-OV | 960 | Superposition de codes |
FRACT-OV | 4640 | Superposition de codes |
ICONP-OV | 832 | Superposition de codes |
SITE-OV | 1888 | Superposition de codes |
HYPERMSG-OV | 4112 | Superposition de codes |
GPOLY | 368 | |
FACETTE | 288 | |
SOMMET | 416 | |
BLT-OV | 864 | Superposition de codes |
DIVERS-OV | 1440 | Superposition de codes |
BANQUE-OV | 1520 | Superposition de codes |
ASSCREW-OV | 2800 | Superposition de codes |
PERSONNEL-OV | 4192 | Superposition de codes |
SHIPGRPH-OV | 2112 | Superposition de codes |
CONFIG-OV | 3072 | Superposition de codes |
TDEPOT-OV | 4800 | Superposition de codes |
PORTMENU-OV | 3120 | Superposition de codes |
VITA-OV | 3552 | Superposition de codes |
HP-OV | 4832 | Superposition de codes |
LP-VO | 5280 | Superposition de codes |
ENVOYÉ-OV | 4784 | Superposition de codes |
TV-OV | 3472 | Superposition de codes |
COMM-OV | 7232 | Superposition de codes |
COMSPEC-OV | 2864 | Superposition de codes |
SEED-OV | 2400 | Superposition de codes |
LISTES | 720 | Superposition de codes |
DÉPLACER-OV | 3808 | Superposition de codes |
INGÉNIEUR | 2320 | Superposition de codes |
MÉDECIN | 1280 | Superposition de codes |
ORBITE-OV | 6640 | Superposition de codes |
CAPITAINE | 5952 | Superposition de codes |
SCIENCE | 3952 | Superposition de codes |
NAVIGATEUR | 880 | Superposition de codes |
BOUTONS DE NAVIRE | 1984 | |
CARTE-OV | 4160 | Superposition de codes |
HYPER-OV | 7168 | Superposition de codes |
ANALYSE-OV | 2560 | Superposition de codes |
LANCEMENT-OV | 1360 | Superposition de codes |
EFFET FLUX | 464 | |
OP-OV | 4400 | Superposition de codes |
ARTICLES-OV | 6016 | Superposition de codes |
LSYSICON | 752 | |
MSYSICON | 448 | |
SSYSICON | 176 | |
COMPORTEMENT-OV | 5360 | |
CMAP | 1008 | |
INSTALLER | 800 | |
GUÉRISON-OV | 1232 | Superposition de codes |
RÉPARATION-OV | 1696 | Superposition de codes |
JEU-OV | 5920 | Superposition de codes |
PLSET-OV | 2400 | Superposition de codes |
CARTES-OV | 2240 | Superposition de codes |
VES-BLT | 4528 | |
TEMPÊTE-OV | 1232 | Superposition de codes |
COMPOSÉS | 176 | Tableau |
IT-OV | 1936 | Superposition de codes |
COMBAT-OV | 6192 | Superposition de codes |
DOMMAGES-OV | 2752 | Superposition de codes |
LAND-OV | 1088 | Superposition de codes |
STATISTIQUES | 64 | Tableau |
STP-OV | 1440 | Superposition de codes |
Placez les fichiers du jeu Starflight original dans les dossiers starflt1-in
et starflt2-in
et exécutez make
. Vous devriez obtenir deux exécutables ( disasOV1
et disasOV2
), qui produisent le contenu dans les dossiers starflt1-out
et starflt2-out
. La sortie générée fait partie de ce référentiel.