In den 80er Jahren veröffentlichte eine unbekannte Firma namens Binary Systems das Spiel Starflight. Das Spiel versetzt den Spieler in die Rolle eines Raumschiffkapitäns, der die Galaxie erkunden soll. Es gibt keinen festgelegten Weg, so dass die Spieler frei zwischen Bergbau, Schiff-gegen-Schiff-Kampf und außerirdischer Diplomatie wechseln können. Die umfassendere Handlung des Spiels erschließt sich langsam, als der Spieler entdeckt, dass eine uralte Rasse von Wesen dafür sorgt, dass Sterne aufflammen und alle Lebewesen zerstören. Das Spiel wurde sowohl von zeitgenössischen als auch modernen Kritikern weithin gelobt und ist eines der frühesten Beispiele eines Sandbox-Spiels. Das Spiel beeinflusste noch Jahrzehnte nach seiner Veröffentlichung das Design zahlreicher anderer Spiele.
Um mehr über das Spiel zu erfahren, klicken Sie auf die folgenden Links:
Sie können das Spiel bei GoG kaufen
Als ich zum ersten Mal von dem Spiel hörte, wollte ich es spielen. Allerdings war ich zu jung und konnte kein Englisch. 20 später versuchte ich es noch einmal und es war eine sehr angenehme Erfahrung. Die Erkundung macht Spaß, die Handlung ist episch und endet mit einer Überraschung, die zu den besten gehört, die ich je erlebt habe. Sicher, das Spiel ist nicht gut gealtert, aber man kann die Hingabe der Entwickler an das Spiel spüren. Dieses Spiel hat sowohl einen künstlerischen Aspekt als auch die Liebe zum Detail eines Handwerkers.
So sehr das Spielen dieses wirklich erstaunlichen Spiels Spaß macht, so sehr macht auch das Reverse Engineering dieses Spiels Spaß. Sie treten in die Fußstapfen der Entwickler und erleben deren Denkprozesse, als wären wir wieder im Jahr 1985. Erwarten Sie bei diesem Spiel das Unerwartete. Wenn Sie ein so altes Spiel zurückentwickeln, benötigen Sie normalerweise zehntausende Zeilen reinen Assembler-Codes, den Sie mit den üblichen Tools wie IDA Pro analysieren können. Aber dieses Mal nicht. Eigentlich kann man für dieses Spiel die üblichen Werkzeuge wegwerfen. Sie sind nutzlos. Du bist auf dich allein gestellt. Der Grund dafür ist, dass Starflight in Forth geschrieben wurde, einer Sprache, die ich kaum kannte.
Forth ist die Sprache mit dem ultimativen Minimalismus hinsichtlich der Syntax. Es gibt nicht mehr Syntax als den Abstand zwischen „Wörtern“. Sie können einen Forth-Reader und -Interpreter grundsätzlich in wenigen Codezeilen schreiben.
In einer modernen Sprache schreibt man so etwas wie
print ( 2 + 3 )
um das Ergebnis von 2+3 auszudrucken. In Forth sieht es jedoch so aus.
2 3 + .
Forth ist eine Stapelmaschine mit umgekehrter polnischer Notation. Die Interpretation ist wie folgt
Die Syntax ist einfach und der Interpreter ist einfach. „2“, „3“, „+“ und „.“ werden einfach „Wörter“ genannt. Es gibt keinen syntaktischen Unterschied zwischen Daten und Code. Sicherlich eine Sprache, die den Einschränkungen der frühen Heimcomputer gerecht wurde.
Wenn Sie die ausführbare Datei STARFLT.COM analysieren, kommen einige fantastische Interna zum Vorschein
Wie oben erläutert, ist Forth eine Stapelmaschine. Als Codierungsmechanismus verwendet es indirektes Threading, eine sehr platzsparende Methode zum Speichern Ihres kompilierten Codes. Threaded-Code hat eine Form, die im Wesentlichen ausschließlich aus Aufrufen von Unterprogrammen besteht. Beim indirekten Threading werden Zeiger auf Speicherorte verwendet, die wiederum auf Maschinencode verweisen.
Nehmen wir an, Ihr Befehlszeiger zeigt auf die Adresse 0x1000 und enthält den 16-Bit-Wert Read16(0x1000)=0x0f72.
0x1000 : dw 0x0f72
Der Wert 0x0f72 ist das codierte Äquivalent des Forth-Wortes „+“. Denken Sie an die obige Beschreibung. Das Wort „+“ öffnet die letzten beiden Stapeleinträge, addiert sie und legt das Ergebnis wieder oben auf den Stapel. Laut indirektem Threading ist dieser 16-Bit-Wert 0x0f72 ein Zeiger auf einen Ort, der wiederum auf Maschinencode verweist. Wenn Sie den Speicherinhalt Read16(0x0f72) lesen, erhalten Sie den Zeiger auf 0x0f74. Und tatsächlich, wenn man sich diesen Speicherort ansieht und zerlegt, erhält man Folgendes
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 ]
Die ersten vier Anweisungen führen genau die Operationen aus, die das Wort „+“ ausführen soll. Die letzten drei Assembler-Anweisungen beginnend mit „lodsw“ erhöhen den Anweisungszeiger und springen zum nächsten Code.
Lasst uns weitermachen. Jetzt zeigt der Befehlszeiger auf 0x1002
0x1002 : dw 0x53a3
Das Lesen der Adresse 0x53a3 verrät
0x53a3 : dw 0x1d29
0x53a5 : dw 0x0001
und den dazugehörigen Code
0x1d29 : inc bx
0x1d2a : inc bx
0x1d2b : push bx
0x1d2c : lodsw
0x1d2d : mov bx , ax
0x1d2f : jmp word ptr [ bx ]
Zu diesem Zeitpunkt enthält das Register bx die Wortadresse 0x53a3. Dieser Code schiebt also einfach die Adresse 0x53a5 oben auf den Stapel. Wir haben dem Programm einen Zeiger auf eine Variable bereitgestellt. Die Variable hat den Inhalt 0x0001. Das vierte Wort „@“ würde die Adresse vom Stapel entfernen, ihren Inhalt lesen und ihn zurück auf den Stapel legen.
Bisher konnte ich 6256 Wörter identifizieren, die entweder Code oder Daten enthalten.
Und das ist eigentlich alles, was Sie über die Codestruktur wissen müssen. Wie Sie sehen, kann dies eine platzsparende Kodierung sein, aber in Bezug auf die Geschwindigkeit ist sie eine Katastrophe. Alle paar Maschinencodeanweisungen müssen Sie zu einem anderen Codeblock springen.
Das Äquivalent des indirekten Threadings in C würde so aussehen.
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 );
}
}
Der für ein bestimmtes Wort ausgeführte Code hat Zugriff auf 5 Hauptvariablen (16-Bit).
Der Disassembler transpiliert den FORTH-Code in Code im C-Stil. Der größte Teil des transpilierten Codes wird kompiliert. Um zu verstehen, was das Programm tut, werfen Sie einen Blick auf die folgende Tabelle. Es nimmt den „Bytecode“ (hauptsächlich 16-Bit-Zeiger) als Eingabe und wandelt ihn in C um.
Vierter 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:
16-Bit-Zeiger | WEITER | C |
---|---|---|
: .C ( -- ) | void DrawC() { | |
unsigned short int i, imax; | ||
0x0642 | CR | Exec("CR"); |
0x75d5 | CDEPTH | CDEPTH(); |
0x15fa 0x0020 | WENN | if (Pop() != 0) { |
0x54ae | CXSP | Push(Read16(pp_CXSP) + 3); |
0xbae | @ | |
0x3b73 | 3 | |
0x0f72 | + | |
0x4ffd | END-CX | Push(Read16(cc_END_dash_CX)); |
0x15b8 | TUN | i = Pop(); |
imax = Pop(); | ||
do { | ||
0x50e0 | ICH | Push(i); |
0x4995 | 1,5@ | _1_dot_5_at_(); |
0x81d5 | .DRJ | DrawDRJ(); |
0x175d 0xfffd | -3 | Push(-3); |
0x155c 0xffff | +SCHLEIFE | int step = Pop(); |
i += step; | ||
if (((step>=0) && (i>=imax)) || ((step<0) && (i<=imax))) break; | ||
} while(1); | ||
0x1660 0x000b | ANDERS | } else { |
0x1bdc | „MT STK“ | PRINT("MT STK", 6); |
0x06 | ||
0x4d | 'M' | |
0x54 | 'T' | |
0x20 | ' ' | |
0x53 | 'S' | |
0x54 | 'T' | |
0x4b | 'K' | |
DANN | } | |
0x0642 | CR | Exec("CR"); |
0x1690 | AUSFAHRT | } |
Das Spiel ist in 3 Dateien erhältlich
Inhalt von STARA.com
Eintrag | Größe | Beschreibung |
---|---|---|
VERZEICHNIS | 4096 | enthält Verzeichnis von STARA und 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 | |
SPRITZEN | 16384 | Bild |
MED-BILD | 2048 | Bild |
PHASEN | 6144 | |
HUM-BILD | 480 | Bild |
VEL-PIC | 432 | Bild |
DHR-BILD | 272 | Bild |
ELO-BILD | 608 | Bild |
UND-BILD | 640 | Bild |
SPEICHERN | 124000 | |
MUSIK | 4960 | Code-Overlay |
ERDE | 1152 | Karte des Planeten Erde |
GALAXIS | 6304 | |
KREDITE | 16384 | Bild |
COP-CPIC | 2928 | |
SCHRIFTARTEN | 768 | |
CGA | 3600 | Maschinencode-Routinen für die CGA-Grafikkarte |
EGA | 3600 | Maschinencode-Routinen für die EGA-Grafikkarte |
Inhalt von STARB.COM
Eintrag | Größe | Beschreibung |
---|---|---|
VERZEICHNIS | 4096 | enthält Verzeichnis von STARA und STARB |
BEISPIEL | 150528 | Baumstruktur mit den meisten Inhalten des Spiels |
KASTEN | 1024 | Tisch |
BANKTRANS | 144 | Tisch |
CREWMITGLIED | 128 | Tisch |
SCHIFF | 1936 | Tisch |
ELEMENT | 544 | Tisch |
ARTEFAKT | 1584 | Tisch |
PLANET | 1360 | Tisch |
PROBE | 448 | Tisch |
BIO-DATEN | 448 | Tisch |
TPORT-BILD | 2416 | Bild |
BPORT-BILD | 3984 | Bild |
ANALYSE-TEXT | 3200 | Tisch |
TASTEN | 944 | Tisch |
ICON1:1 | 912 | |
ICON1:2 | 912 | |
ICON1:4 | 912 | |
ICON-NAME | 736 | |
DPART-OV | 1552 | Code-Overlay |
REGIONEN | 176 | Tisch |
KREATUR | 17024 | Tisch |
CHKFLIGHT-OV | 960 | Code-Overlay |
FRACT-OV | 4640 | Code-Overlay |
ICONP-OV | 832 | Code-Overlay |
SITE-OV | 1888 | Code-Overlay |
HYPERMSG-OV | 4112 | Code-Overlay |
GPOLY | 368 | |
FACETTE | 288 | |
SCHEITEL | 416 | |
BLT-OV | 864 | Code-Overlay |
MISC-OV | 1440 | Code-Overlay |
BANK-OV | 1520 | Code-Overlay |
ASSCREW-OV | 2800 | Code-Overlay |
PERSONAL-OV | 4192 | Code-Overlay |
SHIPGRPH-OV | 2112 | Code-Overlay |
KONFIG-OV | 3072 | Code-Overlay |
TDEPOT-OV | 4800 | Code-Overlay |
PORTMENU-OV | 3120 | Code-Overlay |
VITA-OV | 3552 | Code-Overlay |
HP-OV | 4832 | Code-Overlay |
LP-OV | 5280 | Code-Overlay |
GESENDET-OV | 4784 | Code-Overlay |
TV-OV | 3472 | Code-Overlay |
KOMM-OV | 7232 | Code-Overlay |
COMMSPEC-OV | 2864 | Code-Overlay |
SEED-OV | 2400 | Code-Overlay |
LISTICONS | 720 | Code-Overlay |
MOVE-OV | 3808 | Code-Overlay |
INGENIEUR | 2320 | Code-Overlay |
ARZT | 1280 | Code-Overlay |
ORBIT-OV | 6640 | Code-Overlay |
KAPITÄN | 5952 | Code-Overlay |
WISSENSCHAFT | 3952 | Code-Overlay |
NAVIGATR | 880 | Code-Overlay |
SCHIFFSKNÖPFE | 1984 | |
MAP-OV | 4160 | Code-Overlay |
HYPER-OV | 7168 | Code-Overlay |
ANALYSE-OV | 2560 | Code-Overlay |
LAUNCH-OV | 1360 | Code-Overlay |
FLUX-EFFEKT | 464 | |
OP-OV | 4400 | Code-Overlay |
ARTIKEL-OV | 6016 | Code-Overlay |
LSYSICON | 752 | |
MSYSICON | 448 | |
SSYSICON | 176 | |
VERHALTEN-OV | 5360 | |
CMAP | 1008 | |
INSTALLIEREN | 800 | |
HEAL-OV | 1232 | Code-Overlay |
REPARATUR-OV | 1696 | Code-Overlay |
SPIEL-OV | 5920 | Code-Overlay |
PLSET-OV | 2400 | Code-Overlay |
KARTEN-OV | 2240 | Code-Overlay |
VES-BLT | 4528 | |
STURM-OV | 1232 | Code-Overlay |
VERBINDUNGEN | 176 | Tisch |
IT-OV | 1936 | Code-Overlay |
COMBAT-OV | 6192 | Code-Overlay |
SCHADEN-OV | 2752 | Code-Overlay |
LAND-OV | 1088 | Code-Overlay |
PSTATS | 64 | Tisch |
STP-OV | 1440 | Code-Overlay |
Legen Sie die Dateien des ursprünglichen Starflight-Spiels in die Ordner starflt1-in
und starflt2-in
und führen Sie make
aus. Sie sollten zwei ausführbare Dateien ( disasOV1
und disasOV2
) erhalten, die den Inhalt in den Ordnern starflt1-out
und starflt2-out
erzeugen. Die generierte Ausgabe ist Teil dieses Repositorys.