El 23 de julio de 2013, se publicó el código fuente de la demo de Second Reality (1993). Como muchos, estaba ansioso por ver el interior de la demostración que nos ha inspirado tanto a lo largo de los años.Esperaba ver el caos monolítico del ensamblador, pero en lugar de eso, para mi sorpresa, descubrí una arquitectura compleja que combina elegantemente varios idiomas. Nunca había visto un código así, que representa perfectamente dos aspectos integrales del desarrollo de una demostración:- Trabajo en equipo.
- Ofuscación.
Como de costumbre, formé un artículo para mis notas: espero que esto ahorre a alguien unas horas y tal vez inspire a otros a leer más código fuente y convertirse en ingenieros más experimentados.Parte 1: Introducción
Manifestación
Antes de embarcarme en el código, les daré un enlace para capturar la legendaria demo en video HD (Michael Hut). Hoy, esta es la única forma de evaluar completamente la demostración sin fallas gráficas (incluso DOSBox no puede iniciarla correctamente).Primer contacto con código
El código fuente se publica en GitHub. Simplemente ingrese un comando git
:git clone git@github.com:mtuomi/SecondReality.git
Al principio, el contenido es confuso: 32 carpetas y una misteriosa U2.EXE
que no se inicia en DosBox.La demo tenía el título de trabajo "Unreal 2" (la primera "Unreal" fue la demo anterior de Future Crew, lanzada para la primera Asamblea en 1992). Y solo durante el proceso de desarrollo el nombre fue cambiado a "Segunda Realidad". Esto explica el nombre del archivo "U2.EXE", pero no por qué el archivo no funciona ...Si ejecuta CLOC , obtendremos métricas interesantes: -------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Assembly 99 3029 1947 33350
C++ 121 1977 915 24551
C/C++ Header 8 86 240 654
make 17 159 25 294
DOS Batch 71 3 1 253
-------------------------------------------------------------------------------
SUM: 316 5254 3128 59102
-------------------------------------------------------------------------------
- «» 50% .
- Doom.
- Tiene diecisiete makefiles. ¿Por qué no solo uno?
Lanzar demo
Es difícil resolverlo, pero la demostración lanzada se puede iniciar en DosBox: debe cambiarle el nombre U2.EXE
y ejecutarlo desde el lugar correcto.Cuando me enteré del funcionamiento interno del código, comenzó a parecer muy lógico: CD PRINCIPAL
MOVER U2.EXE DATA / SECOND.EXE
CD DATA
SEGUNDO.EXE
¡Y voilá!Arquitectura
En los años 90, las demos se distribuían principalmente en disquetes. Después de desempacar, fue necesario instalar dos archivos grandes: SECOND.EXE
y REALITY.FC
: . <DIR> 08/08/2013 16:40
.. <DIR> 01/08/2013 16:40
FCINFO10 TXT 48,462 04-10-1993 11:48
FILE_ID DIZ 378 10/04/1993 11:30
README 1ST 4.222 10/04/1993 12:59
REALITY FC 992.188 07-10-1993 12:59
SEGUNDO EXE 1,451,093 10/07-1993 13:35
5 Archivos 2.496.343 Bytes.
2 Dir (s) 262,111,744 Bytes gratis.
Según mi experiencia en el desarrollo de juegos, siempre espero que toda la imagen se vea así:SECOND.EXE
: motor con todos los efectos en un archivo ejecutable.REALITY.FC
: Activos (música, efectos de sonido, imágenes) en un formato propietario / encriptado a la WAD
Doom.
Pero después de leerlo, MAIN/PACK.C
descubrí que estaba muy equivocado: el motor de Second Reality es solo un cargador y un servidor de interrupción (llamado DIS). Cada demo de escena (también llamada "PARTE") es un ejecutable de DOS completamente funcional. Cada parte es cargada por el cargador Loader y se lanza una tras otra. Las partes se almacenan en forma cifrada al final SECOND.EXE
:REALITY.FC
contiene dos composiciones musicales que se reproducen durante la demostración (para rellenar, ofuscar, relleno y marcador al principio).SECOND.EXE
contiene bootloader y Demo Interrupt Server (DIS).- Después del final
SECOND.EXE
, se agregan 32 partes (PARTE) de la demostración como archivos ejecutables de DOS (encriptados).
Dicha arquitectura ofrece muchas ventajas:- : PART ,
_start
(450 ). - EXE
SECOND.EXE
-. - : Loader DIS 20 . DOS .
- : PART PART .
- / : , PART ( ), : EXE , .
- Se puede usar cualquier lenguaje para la programación de PARTES: en el código encontramos C, Assembly ... y Pascal.
Lectura recomendada
Los tres pilares para comprender el código fuente de Second Reality son VGA, ensamblador y arquitectura de PC (programación PIC y PIT). Aquí hay algunos enlaces increíblemente útiles:Parte 2: Segundo motor de realidad
Como se discutió en la Parte 1, la base de la Segunda Realidad consiste en:- El gestor de arranque como un ejecutable de DOS.
- Administrador de memoria (grupo de pila simple)
- Servidor de interrupción de demostración (DIS).
En esta parte, daré recomendaciones a los programadores que quieran leer el motor y el gestor de arranque (DIS se discutirá en la siguiente parte).Código del motor
El código del motor es 100% ASM, pero está muy bien escrito y bastante bien documentado:En pseudocódigo se puede escribir así: exemus db 'STARTMUS.EXE',0
exe0 db 'START.EXE',0
...
exe23 db 'ENDSCRL.EXE',0
start:
cli ; Disable all interrupts
mov ah,4ah ; Deallocate all memory
call checkall ; Check for 570,000 bytes of mem, 386 CPU and VGA
call file_getexepath
call dis_setint ; Install Demo Interrupt Server on Interrupt 0fch
call file_initpacking ; Check exe signature (no tempering) !
call file_setint ; Replace DOS routines (only OPENFILE, SEEK and READ) on Interrupt 021h
call flushkbd ; Flush the keyboard buffer
call checkcmdline ; check/process commandline
;======== Here we go! ========
call vmode_init ; Init VGA (not necessarly Mode13h or ModeX), each PARTs had its own resolution
mov si,OFFSET exe0
call executehigh ; loaded to high in memory. Used for loading music loaders and stuff.
call _zinit ; Start music
call restartmus
mov si,OFFSET exe1 ;Parameter for partexecute: Offset to exec name
call partexecute
; Execute all parts until exe23
call fademusic
;======== And Done! (or fatal exit) ========
fatalexit:
mov cs:notextmode,0
call vmode_deinit
Todos los pasos son bastante fáciles de leer:- Establezca el servidor de interrupción DIS como interrupción
0fch
. - Reemplazo de llamadas del sistema DOS por interrupción
021h
(para más detalles, consulte la sección "Modos de desarrollo y producción" ). - Descargue música a una tarjeta de sonido a través de la memoria EMS.
- Ejecutando música.
- Realizar cada parte de la demostración.
- ¡Hecho!
Detalles de los procedimientos execute
: execute:
cld
call openfile ; Open the DOS executable for this PART
call loadexe ; loads the specified exe file to memory, does relocations and creates psp
call closefile
call runexe ;runs the exe file loaded previously with loadexe.
; returns after exe executed, and frees the memory
; it uses.
Administrador de memoria
Hubo muchas leyendas de que Second Reality usa un administrador de memoria complejo a través de MMU; no había rastros en el motor. La gestión de la memoria se transfiere realmente a DOS: el motor comienza liberando toda la RAM y luego distribuyéndola a pedido . El único truco complicado es la capacidad de asignar RAM desde el final del montón: se hace utilizando el valor de retorno de malloc DOS cuando se solicita demasiada RAM .Parte 3: DIS
El servidor de interrupción de demostración (DIS) proporciona una amplia gama de servicios para cada PARTE: desde el intercambio de datos entre diferentes PARTES hasta la sincronización con VGA.Servicios de DIS
En tiempo de ejecución, PART, el servidor DIS le proporciona servicios. Puede encontrar una lista de características en DIS/DIS.H
.Los servicios más importantes:- Intercambio entre diferentes PART (
dis_msgarea
): DIS proporciona tres buffers de 64 bytes cada uno para que PART pueda recibir parámetros del cargador de la PART anterior. - Emulation Copper (
dis_setcopper
): simulador de Amiga Copper que le permite realizar operaciones que cambian según el estado de VGA. - Modo Dev / Prod (
dis_indemo
): permite que la PARTE sepa que se está ejecutando en modo DEV (lo que significa que debe inicializar el video) o que se inicia desde el gestor de arranque en modo PROD. - Recuento de tramas VGA (
_dis_getmframe
) - Esperando el respaldo VGA (
dis_waitb
).
Código de demostración del servidor de interrupción
El código fuente DIS también es 100% ASM ... y bastante bien comentado:DIS/DIS.ASM
(controlador de interrupción establecido en int 0fch
).DIS/DISINT.ASM
(Procedimientos DIS en sí mismos).- Dado que Second Reality también está parcialmente escrito en C, el código tiene una interfaz para C:
DIS/DIS.H
y DIS/DISC.ASM
.
Cómo funciona
DIS se establece como un controlador de interrupciones para programático int 0fch
. Lo mejor de esto es que puede ejecutarse internamente SECOND.EXE
cuando se ejecuta la demostración o como un programa residente ( TSR ) en modo Dev. Esta flexibilidad le permite probar individualmente diferentes demos PARTE durante el desarrollo: // Supongamos que somos un desarrollador de FC y queremos comenzar la parte de STAR directamente.
C: \> CD DDSTARS
C: \ DDSTARS> K
ERROR: DIS no cargado.
// Vaya, la PARTE no pudo encontrar el DIS en int 0fch.
C: \ DDSTARS> CD .. \ DIS
C: \ DIS> DIS
Demo Int Server (DIS) V1.0 Copyright (C) 1993 The Future Crew
VERSIÓN BETA - Compilado: 26/07/93 03:15:53
Instalado (int fc).
NOTA: ¡Este servidor DIS no admite sincronización de cobre o música!
// DIS está instalado, intentemos de nuevo.
C: \ DIS> CD ../DDSTARS
C: \ DDSTARS> K
¡Y voilá!Cobre
"Copper" es el coprocesador que los desarrolladores de la demo de Amiga adoraron. Formaba parte del conjunto de chips original y le permitía ejecutar una secuencia programable de comandos sincronizados con equipos de video. No había tal coprocesador en la PC, y Future Crew tuvo que escribir un simulador de cobre que se ejecuta dentro de DIS.El equipo de FC utilizó los conjuntos de chips de hardware para PC 8254-PIT y 8259-PIC para simular Copper. Creó un sistema sincronizado con la frecuencia VGA , capaz de iniciar procedimientos en tres lugares del haz vertical hacia atrás :- Lugar 0: después de encender la pantalla (aproximadamente en la línea de escaneo 25)
- Lugar 1: inmediatamente después del barrido del haz inverso (ES POSIBLE EVITARLO)
- Lugar 2: en el haz inverso del haz de exploración
Se puede leer cómo se hace esto MAIN/COPPER.ASM
(y ver en el diagrama a continuación):- El temporizador del chip 8254 está configurado para activar IRQ0 con la frecuencia deseada.
- El controlador de interrupción 8h (que se llama por el 8259 PIC después de recibir IRQ0) se reemplaza con un procedimiento aquí
intti8
.
Nota: El servicio de conteo de tramas DIS es realmente proporcionado por el simulador de cobre.Parte 4: modos de desarrollo y producción
Al leer el código fuente de Second Reality, le sorprende la cantidad de atención que el equipo prestó al cambio continuo de DEV a PROD.Modo de desarrollo
En el modo Desarrollo, cada componente de la demostración era un archivo ejecutable separado.- DIS se cargó en el TSR residente y se accedió a través de una interrupción
0cfh
. - El gestor de arranque provocó una interrupción de DOS
21h
para abrir, leer, buscar y cerrar archivos.
Esta configuración DEV tiene las siguientes ventajas:- Cada codificador y artista podría trabajar en el archivo ejecutable y probarlo por separado, sin afectar al resto del equipo.
- La demostración completa en cualquier momento podría probarse utilizando una pequeña
SECOND.EXE
(sin agregar todos los EXE al final). El ejecutable de cada PARTE se cargó usando una interrupción de DOS 021h
desde un archivo separado.
Producción (modo demo)
En el modo de producción, el pequeño SECOND.EXE
(que contiene el gestor de arranque), DIS y partes de la demostración como EXE separados se combinaron en uno grueso SECOND.EXE
.- El acceso a DIS todavía se hizo por interrupción
0fch
. - La API de interrupción DOS 21h fue parcheada por sus propias rutinas Future Crew, que abren archivos desde el final de un archivo grande
SECOND.EXE
.
Esta configuración de PROD tiene una ventaja en términos de tiempo de carga y protección contra ingeniería inversa ... pero lo más importante, desde el punto de vista de la programación o carga de PART, nada cambia al cambiar de DEV a PROD.Parte 5: PARTE separada
Cada uno de los efectos visuales de Second Reality es un ejecutable de DOS completamente funcional. Se llaman PART y todos ellos 23. Dicha solución arquitectónica permitió la creación rápida de prototipos, el desarrollo paralelo (ya que FC probablemente no tenía herramientas de control de versiones) y la libre elección de lenguajes (ASM, C e incluso Pascal se encuentran en la fuente).Parte separada
Se puede encontrar una lista de todas las PART / EXEs en el código fuente del motor: U2.ASM . Aquí hay una breve descripción más conveniente de las 23 partes (con la ubicación del código fuente, aunque los nombres pueden ser muy confusos):Parece que cada desarrollador tenía su propia especialización, que podría compartirse en una parte. Esto es especialmente notable en la primera escena con desplazamiento, barcos y explosiones (Alkutekstit). Aunque esto parece un efecto continuo, de hecho son tres archivos ejecutables escritos por tres personas diferentes:Bienes
Los activos de imagen ( .LBM
) se generan con Deluxe Paint , un editor de mapas de bits extremadamente popular en los años 90. Curiosamente, se convierten en una matriz de bytes y se compilan dentro de PART. Como resultado de esto, el archivo exe también descarga todos los activos. Además, esto complica la ingeniería inversa.Entre los geniales conjuntos de activos se encuentran los famosos CITY y SHIP de la última escena 3D:Unidad interna PARTE
Como todos fueron compilados en ejecutables de DOS, en PARTE, cualquier lenguaje podría usarse:En cuanto al uso de la memoria, leí mucho sobre MMU en Wikipedia y otros sitios web ... pero, de hecho, cada parte podría usar cualquier cosa, porque después de la ejecución se descargó completamente de la memoria.Cuando trabajaba con VGA, cada parte usaba su propio conjunto de trucos y trabajaba en su resolución. En todos ellos, no se utilizaron Mode 13h ni ModeX, sino un modo 13h modificado con su propia resolución. El archivo SCRIPT a menudo menciona 320x200 y 320x400.Desafortunadamente, al analizar PART, leer el código fuente se convierte en una tarea desalentadora: la calidad del código y los comentarios disminuye drásticamente. Quizás esto sucedió por apuro o porque cada PARTE trabajó en su propio desarrollador (es decir, no hubo una necesidad "real" de comentarios o comprensión del código), pero el resultado fue algo completamente confuso:Sofisticados algoritmos no sólo son difíciles de entender incluso los nombres de las variables ( a
, b
, co[]
...). El código sería mucho más legible si los desarrolladores nos dejaran pistas en las notas de la versión. Como resultado, no dediqué mucho tiempo a estudiar cada parte; La excepción fue el motor 3D responsable de U2A.EXE y U2E.EXE.Motor 3D Segunda Realidad
De todos modos, decidí estudiar en detalle el motor 3D, que se utilizó en dos partes: U2A.EXE
y U2E.EXE
.El código fuente es C con procedimientos optimizados por ensamblador (especialmente relleno y sombreado Gouro):- CITY.C (código principal).
- VISU.C (biblioteca
visu.lib
). - AVID.ASM (video de ensamblador optimizado (limpieza, copia de pantalla, etc.)).
- ADRAW.ASM (dibujar objetos y truncar).
- ACALC.ASM (matrices y cálculos rápidos de sin / cos).
La arquitectura de estos componentes es bastante notable: la biblioteca VISU
realiza todas las tareas complejas, por ejemplo, carga de activos: objetos 3DS, materiales y flujos (movimientos de cámara y barco).El motor clasifica los objetos que deben dibujarse y los procesa utilizando el algoritmo del artista. Esto lleva a una gran cantidad de redibujos, pero dado que los pestillos VGA le permiten grabar 4 píxeles al mismo tiempo, no es tan malo.Un hecho interesante: el motor realiza transformaciones de una manera "de la vieja escuela": en lugar de utilizar matrices homogéneas comunes de 4x4, utiliza matrices de rotación 3 * 3 y un vector de desplazamiento.Aquí hay un resumen en pseudocódigo: main(){
scenem=readfile(tmpname);
scene0=readfile(tmpname);
for(f=-1,c=1;c<d;c++){
sprintf(tmpname,"%s.%03i",scene,e);
co[c].o=vis_loadobject(tmpname);
}
vid_init(1);
vid_setpal(cp);
for(;;){
vid_switch();
_asm mov bx,1 _asm int 0fch
vid_clear();
for(;;){}
vid_cameraangle(fov);
for(a=1;ac<conum;a++) if(co[a].on)
calc_applyrmatrix(o->r,&cam);
for(a=0;ac<ordernum;a++)
for(b=a-1;b>=0 && dis>co[order[b]].dist;b--)
for(a=0;ac<ordernum;a++)
vis_drawobject(o);
}
}
return(0);
}
Puertos para sistemas modernos
Después del lanzamiento de este artículo, muchos desarrolladores comenzaron a portar Second Reality a los sistemas modernos. Claudio Matsuoka se dedicó a crear sr-port , un puerto C para Linux y OpenGL ES 2.0, que hasta ahora parece bastante impresionante. Nick Kovacs hizo un gran trabajo en PART PLZ, portándolo a C (ahora es parte del código fuente sr-port), así como a javascript :