En los años 80, una empresa desconocida llamada Binary Systems publicó el juego Starflight. El juego pone al jugador en el papel de un capitán de nave espacial enviado a explorar la galaxia. No existe un camino establecido, lo que permite a los jugadores cambiar libremente entre minería, combate barco a barco y diplomacia alienígena. La trama más amplia del juego emerge lentamente, a medida que el jugador descubre que una antigua raza de seres está provocando que las estrellas brillen y destruyan a todas las criaturas vivientes. El juego ha sido ampliamente elogiado por críticos tanto contemporáneos como modernos, y es uno de los primeros ejemplos de un juego sandbox. El juego influyó en el diseño de muchos otros juegos durante décadas después de su lanzamiento.
Para saber más sobre el juego consulta los siguientes enlaces:
Puedes comprar el juego en GoG.
La primera vez que oí hablar del juego quise jugarlo. Sin embargo, yo era demasiado joven y no podía hablar inglés. 20 después lo intenté nuevamente y fue una experiencia muy placentera. La exploración es divertida, la historia es épica y termina con una sorpresa, esa es una de las mejores que he experimentado. Claro, el juego no ha envejecido bien, pero puedes sentir la devoción de los desarrolladores por el juego. Hay un aspecto artístico en este juego, así como la atención al detalle de un artesano.
Por mucho que jugar a este juego realmente asombroso sea divertido, también lo es la ingeniería inversa. Sigues los pasos de los desarrolladores y experimentas sus procesos de pensamiento como si volviéramos al año 1985. Para este juego espera lo inesperado. Normalmente, cuando realizas ingeniería inversa en un juego tan antiguo, tienes que recibir diez mil líneas de código ensamblador puro, que puedes analizar con las herramientas habituales como IDA Pro. Pero no esta vez. En realidad, para este juego puedes desechar las herramientas habituales. Son inútiles. Estás solo. La razón es que Starflight fue escrito en Forth, un idioma que apenas conocía.
El cuarto es el lenguaje con el máximo minimalismo en cuanto a sintaxis. No hay más sintaxis que el espacio entre "palabras". Puedes escribir un lector e intérprete de Forth básicamente en unas pocas líneas de código.
En un lenguaje moderno se escribe algo como
print ( 2 + 3 )
para imprimir el resultado de 2+3. Sin embargo, en Forth se ve así.
2 3 + .
Forth es una máquina de apilar, con notación polaca inversa. La interpretación es la siguiente.
La sintaxis es simple y el intérprete es simple. "2", "3", "+" y "." se llaman simplemente "palabras". No existe distinción sintáctica entre datos y código. Sin duda, un lenguaje que estuvo a la altura de las limitaciones de las primeras computadoras domésticas.
Cuando analizas el ejecutable STARFLT.COM, revela algunos aspectos internos fantásticos.
Como se explicó anteriormente, Forth es una máquina apiladora. Como mecanismo de codificación, utiliza subprocesos indirectos, un método que ahorra mucho espacio para almacenar el código compilado. El código enhebrado tiene una forma que esencialmente consiste enteramente en llamadas a subrutinas. El subproceso indirecto utiliza punteros a ubicaciones que a su vez apuntan al código de máquina.
Digamos que su puntero de instrucción apunta a la dirección 0x1000 y contiene el valor de 16 bits Read16(0x1000)=0x0f72.
0x1000 : dw 0x0f72
El valor 0x0f72 es el equivalente codificado de la cuarta palabra '+'. Recuerde la descripción anterior. La palabra '+' muestra las dos últimas entradas de la pila, las suma y coloca el resultado nuevamente en la parte superior de la pila. Según el subprocesamiento indirecto, este valor de 16 bits 0x0f72 es un puntero a una ubicación que a su vez apunta al código de máquina. Cuando lees el contenido de la memoria Read16(0x0f72), obtienes el puntero a 0x0f74. Y de hecho, cuando miras esta ubicación de memoria y la desmontas, recibes lo siguiente
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 ]
Las primeras cuatro instrucciones realizan exactamente las operaciones que debería realizar la palabra "+". Las últimas tres instrucciones del ensamblador que comienzan con "lodsw" aumentan el puntero de instrucción y saltan al siguiente código.
Sigamos. Ahora el puntero de instrucción apunta a 0x1002.
0x1002 : dw 0x53a3
La lectura de la dirección 0x53a3 revela
0x53a3 : dw 0x1d29
0x53a5 : dw 0x0001
y el codigo correspondiente
0x1d29 : inc bx
0x1d2a : inc bx
0x1d2b : push bx
0x1d2c : lodsw
0x1d2d : mov bx , ax
0x1d2f : jmp word ptr [ bx ]
En este momento el registro bx contiene la dirección de palabra 0x53a3. Entonces este código simplemente coloca la dirección 0x53a5 en la parte superior de la pila. Lo que hemos hecho es proporcionar al programa un puntero a una variable. La variable tiene el contenido 0x0001. La cuarta palabra '@' sacará la dirección de la pila, leerá su contenido y la devolverá a la pila.
Hasta ahora pude identificar 6256 palabras que contienen código o datos.
Y eso es todo lo que necesitas saber sobre la estructura del código. Como puede ver, esta puede ser una codificación que ahorra espacio, pero en términos de velocidad es una catástrofe. Cada pocas instrucciones de código de máquina, debe saltar a un bloque de código diferente.
El equivalente de subprocesamiento indirecto en C se vería así.
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 );
}
}
El código ejecutado para una palabra específica tiene acceso a 5 variables principales (16 Bits)
El desensamblador transpila el código ADELANTE a código estilo C. La mayor parte del código transpilado se compila. Para comprender qué hace el programa, consulte la siguiente tabla. Toma el "código de bytes" (que son principalmente punteros de 16 bits) como entrada y lo transforma en C.
Cuarto código:
: .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
Transformación:
Punteros de 16 bits | ADELANTE | do |
---|---|---|
: .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 | FINAL-CX | Push(Read16(cc_END_dash_CX)); |
0x15b8 | HACER | i = Pop(); |
imax = Pop(); | ||
do { | ||
0x50e0 | I | Push(i); |
0x4995 | 1.5@ | _1_dot_5_at_(); |
0x81d5 | .DRJ | DrawDRJ(); |
0x175d 0xfffd | -3 | Push(-3); |
0x155c 0xffff | +BUCLE | int step = Pop(); |
i += step; | ||
if (((step>=0) && (i>=imax)) || ((step<0) && (i<=imax))) break; | ||
} while(1); | ||
0x1660 0x000b | DEMÁS | } else { |
0x1bdc | "MT STK" | PRINT("MT STK", 6); |
0x06 | ||
0x4d | 'METRO' | |
0x54 | 'T' | |
0x20 | ' ' | |
0x53 | 'S' | |
0x54 | 'T' | |
0x4b | 'K' | |
ENTONCES | } | |
0x0642 | CR | Exec("CR"); |
0x1690 | SALIDA | } |
El juego viene en 3 archivos.
Contenido de STARA.com
entrada | tamaño | descripción |
---|---|---|
DIRECTORIO | 4096 | contiene directorio de STARA y 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 | |
CHAPOTEO | 16384 | Imagen |
MED-PIC | 2048 | Imagen |
FASES | 6144 | |
HUM-PIC | 480 | Imagen |
VEL-PIC | 432 | Imagen |
THR-PIC | 272 | Imagen |
ELO-PIC | 608 | Imagen |
Y-PIC | 640 | Imagen |
AHORRAR | 124000 | |
MÚSICA | 4960 | Superposición de código |
TIERRA | 1152 | Mapa del planeta tierra |
GALAXIA | 6304 | |
CRÉDITOS | 16384 | imagen |
COP-CPIC | 2928 | |
FUENTES | 768 | |
CGA | 3600 | Rutinas de código de máquina para la tarjeta gráfica CGA |
EGA | 3600 | Rutinas de Código de Máquina para la tarjeta gráfica EGA |
Contenido de STARB.COM
entrada | tamaño | descripción |
---|---|---|
DIRECTORIO | 4096 | contiene directorio de STARA y STARB |
INSTANCIA | 150528 | Estructura de árbol con la mayor parte del contenido del juego. |
CAJA | 1024 | Mesa |
TRANSFERENCIA BANCARIA | 144 | Mesa |
MIEMBRO DE LA TRIPULACIÓN | 128 | Mesa |
BUQUE | 1936 | Mesa |
ELEMENTO | 544 | Mesa |
ARTEFACTO | 1584 | Mesa |
PLANETA | 1360 | Mesa |
MUESTRA | 448 | Mesa |
BIO-DATOS | 448 | Mesa |
TPORT-PIC | 2416 | Imagen |
BPORT-PIC | 3984 | Imagen |
ANALIZAR-TEXTO | 3200 | Mesa |
BOTONES | 944 | Mesa |
ICONO1:1 | 912 | |
ICONO1:2 | 912 | |
ICONO1:4 | 912 | |
ICONO-NOMBRE | 736 | |
DPART-OV | 1552 | Superposición de código |
REGIONES | 176 | Mesa |
CRIATURA | 17024 | Mesa |
CHKFLIGHT-OV | 960 | Superposición de código |
FRACT-OV | 4640 | Superposición de código |
ICONP-OV | 832 | Superposición de código |
SITIO-OV | 1888 | Superposición de código |
HIPERMSG-OV | 4112 | Superposición de código |
GPOLY | 368 | |
FACETA | 288 | |
VÉRTICE | 416 | |
BLT-OV | 864 | Superposición de código |
MISC-OV | 1440 | Superposición de código |
BANCO-OV | 1520 | Superposición de código |
TORNILLO-OV | 2800 | Superposición de código |
PERSONAL-OV | 4192 | Superposición de código |
SHIPGRPH-OV | 2112 | Superposición de código |
CONFIG-OV | 3072 | Superposición de código |
TDEPOT-OV | 4800 | Superposición de código |
PORTMENU-OV | 3120 | Superposición de código |
VITA-OV | 3552 | Superposición de código |
HP-OV | 4832 | Superposición de código |
LP-OV | 5280 | Superposición de código |
ENVIADO-OV | 4784 | Superposición de código |
TV-OV | 3472 | Superposición de código |
COM-OV | 7232 | Superposición de código |
COMMSPEC-OV | 2864 | Superposición de código |
SEMILLA-OV | 2400 | Superposición de código |
LISTICONOS | 720 | Superposición de código |
MOVER-OV | 3808 | Superposición de código |
INGENIERO | 2320 | Superposición de código |
DOCTOR | 1280 | Superposición de código |
ÓRBITA-OV | 6640 | Superposición de código |
CAPITÁN | 5952 | Superposición de código |
CIENCIA | 3952 | Superposición de código |
NAVEGADOR | 880 | Superposición de código |
BOTONES DE ENVÍO | 1984 | |
MAPA-OV | 4160 | Superposición de código |
HIPER-OV | 7168 | Superposición de código |
ANALIZAR-OV | 2560 | Superposición de código |
LANZAMIENTO-OV | 1360 | Superposición de código |
EFECTO FLUJO | 464 | |
OP-OV | 4400 | Superposición de código |
ARTÍCULOS-OV | 6016 | Superposición de código |
LSYSICON | 752 | |
MSYSICON | 448 | |
SSYSICON | 176 | |
COMPORTAMIENTO-OV | 5360 | |
CMAP | 1008 | |
INSTALAR | 800 | |
SANAR-OV | 1232 | Superposición de código |
REPARACIÓN-OV | 1696 | Superposición de código |
JUEGO-OV | 5920 | Superposición de código |
PLSET-OV | 2400 | Superposición de código |
MAPAS-OV | 2240 | Superposición de código |
VES-BLT | 4528 | |
TORMENTA-OV | 1232 | Superposición de código |
COMPUESTOS | 176 | Mesa |
IT-OV | 1936 | Superposición de código |
COMBATE-OV | 6192 | Superposición de código |
DAÑO-OV | 2752 | Superposición de código |
TIERRA-OV | 1088 | Superposición de código |
PSTATS | 64 | Mesa |
STP-OV | 1440 | Superposición de código |
Coloque los archivos del juego Starflight original en las carpetas starflt1-in
y starflt2-in
y ejecute make
. Debería obtener dos ejecutables ( disasOV1
y disasOV2
), que producen el contenido de las carpetas starflt1-out
y starflt2-out
. La salida generada es parte de este repositorio.