Demonstração da análise de código Second Reality


Em 23 de julho de 2013, o código fonte da demonstração da Segunda Realidade (1993) foi publicado . Como muitos, eu estava ansioso para ver o interior da demo que nos inspirou muito ao longo dos anos.

Eu esperava ver o caos monolítico do assembler, mas, em vez disso, para minha surpresa, descobri uma arquitetura complexa que combina graciosamente várias linguagens. Eu nunca vi esse código antes, representando perfeitamente dois aspectos integrais do desenvolvimento de uma demonstração:

  • Trabalho em equipe.
  • Ofuscação.

Como sempre, formei um artigo para minhas anotações: espero que isso poupe a alguém algumas horas e talvez inspire outros a ler mais código-fonte e se tornarem engenheiros mais experientes.

Parte 1: Introdução


Demo


Antes de embarcar no código, darei um link para capturar a demo lendária em vídeo em HD (Michael Hut). Hoje, essa é a única maneira de avaliar completamente a demonstração sem falhas gráficas (nem o DOSBox pode iniciá-la corretamente).


Primeiro contato com o código


O código fonte é postado no GitHub. Basta digitar um comando git:

git clone git@github.com:mtuomi/SecondReality.git

A princípio, o conteúdo é confuso: 32 pastas e uma misteriosa U2.EXEque não inicia no DosBox.


A demo teve o título de trabalho "Unreal 2" (o primeiro "Unreal" foi a demo anterior da Future Crew, lançada para a primeira Assembléia em 1992). E somente durante o processo de desenvolvimento o nome foi alterado para "Segunda Realidade". Isso explica o nome do arquivo “U2.EXE”, mas não por que o arquivo não funciona ...

Se você executar o CLOC , obteremos métricas interessantes:

    -------------------------------------------------------------------------------
                                                   
    -------------------------------------------------------------------------------
    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.
  • Tem dezessete makefiles. Por que não apenas um?

Iniciar demonstração


É difícil descobrir, mas a demo lançada pode ser lançada no DosBox: você precisa renomeá-la U2.EXEe executá-la no lugar certo.

Quando aprendi sobre o funcionamento interno do código, ele começou a parecer muito lógico:

        CD PRINCIPAL
        MOVE U2.EXE DATA / SECOND.EXE
        DADOS DO CD
        SECOND.EXE

E pronto!


Arquitetura


Nos anos 90, as demos eram distribuídas principalmente em disquetes. Após descompactar, era necessário instalar dois arquivos grandes: SECOND.EXEe REALITY.FC:

    . 08/08/2013 16:40
    .. <DIR> 08/01/2013 16:40
    FCINFO10 TXT 48.462 04-10-1993 11:48
    FILE_ID DIZ 378 04-10-1993 11:30
    README 1ST 4.222 04-10-1993 12:59
    REALITY FC 992.188 07-10-1993 12:59 
    SEGUNDO EXE 1.451.093 07-10-1993 13:35
        5 arquivos 2.496.343 bytes.
        2 Dir (s) 262,111,744 Bytes grátis.

Com base na minha experiência em desenvolvimento de jogos, sempre espero que o quadro todo fique assim:

  • SECOND.EXE: mecanismo com todos os efeitos em um arquivo executável.
  • REALITY.FC: Ativos (música, efeitos sonoros, imagens) em um formato proprietário / criptografado no WADjogo Doom.

Mas, depois de ler, MAIN/PACK.Cdescobri que estava enganado: o mecanismo da Segunda Realidade é apenas um servidor Loader e Interrupt (chamado DIS). Cada demonstração de cena (também chamada de "PARTE") é um executável do DOS totalmente funcional. Cada peça é carregada pelo carregador Loader e lançada uma após a outra. As peças são armazenadas no formato criptografado no final SECOND.EXE:


  • REALITY.FC contém duas composições musicais tocadas durante a demo (para preenchimento de ofuscação, preenchimento e marcador adicionados no início).
  • SECOND.EXE contém o carregador de inicialização e o Demo Interrupt Server (DIS).
  • Após o final SECOND.EXE, 32 partes (PARTE) da demonstração são adicionadas como arquivos executáveis ​​do DOS (criptografados).

Essa arquitetura oferece muitas vantagens:

  • : PART , _start (450 ).
  • EXE SECOND.EXE -.
  • : Loader DIS 20 . DOS .
  • : PART PART .
  • / : , PART ( ), : EXE , .
  • Qualquer linguagem pode ser usada para programação PART: no código encontramos C, Assembly ... e Pascal.

Leitura recomendada


Os três pilares para entender o código fonte da Segunda Realidade são arquitetura VGA, assembler e PC (programação PIC e PIT). Aqui estão alguns links incrivelmente úteis:


Parte 2: Segundo mecanismo de realidade


Conforme discutido na Parte 1, o fundamento da Segunda Realidade consiste em:

  • O carregador de inicialização como um executável do DOS.
  • Gerenciador de memória (pool de pilhas simples)
  • Servidor de interrupção de demonstração (DIS).

Nesta parte, darei recomendações aos programadores que desejam ler o mecanismo e o gerenciador de inicialização (o DIS será discutido na próxima parte).

Código do motor


O código do mecanismo é 100% ASM, mas está muito bem escrito e bastante bem documentado:


No pseudo-código, pode ser escrito assim:

    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

Todas as etapas são fáceis de ler:

  1. Defina o servidor de interrupção DIS como interrupção 0fch.
  2. Substituição de chamadas do sistema DOS por interrupção 021h(para obter mais detalhes, consulte a seção "Modos de desenvolvimento e produção" ).
  3. Baixe músicas para uma placa de som via memória EMS.
  4. Música em execução.
  5. Executando cada parte da demonstração.
  6. Feito!

Detalhes dos procedimentos 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.

Gerenciador de memória


Havia muitas lendas de que o Second Reality usa um gerenciador de memória complexo via MMU; não havia rastros no mecanismo. O gerenciamento de memória é realmente transferido para o DOS: o mecanismo inicia liberando toda a RAM e depois distribuindo-a mediante solicitação . O único truque complicado é a capacidade de alocar RAM a partir do final do heap: isso é feito usando o valor de retorno do malloc DOS quando é solicitada muita RAM .

Parte 3: DIS


O Demo Interrupt Server (DIS) fornece uma ampla gama de serviços para cada PARTE: da troca de dados entre diferentes PARTES à sincronização com o VGA.

Serviços DIS


No tempo de execução, PART, o servidor DIS fornece serviços para ele. Uma lista de recursos pode ser encontrada em DIS/DIS.H.

Os serviços mais importantes:

  • Troca entre diferentes PART ( dis_msgarea): DIS fornece três buffers de 64 bytes cada para que o PART possa receber parâmetros do carregador do PART anterior.
  • Emulação de cobre ( dis_setcopper): simulador Amiga Copper que permite executar operações que são alteradas pelo estado do VGA.
  • Modo Dev / Prod ( dis_indemo): permite que a PART saiba que está sendo executada no modo DEV (o que significa que deve inicializar o vídeo) ou iniciada a partir do gerenciador de inicialização no modo PROD.
  • Contagem de quadros VGA ( _dis_getmframe)
  • Aguardando o retorno VGA ( dis_waitb).

Código do servidor de interrupção de demonstração


O código-fonte DIS também é 100% ASM ... e muito bem comentado:

  • DIS/DIS.ASM(manipulador de interrupção definido como int 0fch).
  • DIS/DISINT.ASM (Próprios procedimentos DIS).
  • Como a Segunda Realidade também é parcialmente escrita em C, o código possui uma interface para C: DIS/DIS.He DIS/DISC.ASM.

Como funciona


DIS é definido como um manipulador de interrupção para int programático 0fch. O melhor de tudo é que ele pode ser executado internamente SECOND.EXEquando a demonstração estiver em execução ou como um programa residente ( TSR ) no modo Dev. Essa flexibilidade permite testar individualmente diferentes demos do PART durante o desenvolvimento:

                          // Vamos fingir que somos desenvolvedores de FC e queremos iniciar a parte STAR diretamente.
  C: \> CD DDSTARS            
  C: \ DDSTARS> K

  ERRO: DIS não carregado. 

                          // Ops, a PART não pôde encontrar o DIS no int 0fch.
  C: \ DDSTARS> CD .. \ DIS
  C: \ DIS> DIS

  Servidor de demonstração Int (DIS) V1.0 Copyright (C) 1993 The Future Crew
  VERSÃO BETA - Compilado: 26/07/93 03:15:53 
  Instalado (int fc).
  NOTA: Este servidor DIS não suporta sincronização de cobre ou música!
                          // DIS está instalado, vamos tentar novamente.

  C: \ DIS> CD ../DDSTARS
  C: \ DDSTARS> K

E pronto!


Cobre



"Copper" é o coprocessador que os desenvolvedores da demo de Amiga adoraram. Fazia parte do conjunto de chips original e permitia executar um fluxo de comandos programável sincronizado com o equipamento de vídeo. Não havia tal coprocessador no PC, e a Future Crew teve que escrever um simulador de cobre rodando dentro do DIS.

A equipe do FC usou os chipsets de hardware para PC 8254-PIT e 8259-PIC para simular o cobre. Ela criou um sistema sincronizado com a frequência VGA , capaz de iniciar procedimentos em três locais do feixe vertical para trás :

  • Local 0: depois de ligar a tela (aproximadamente na linha de digitalização 25)
  • Local 1: imediatamente após a varredura do feixe reverso (É POSSÍVEL EVITÁ-LO)
  • Local 2: no feixe reverso do feixe de varredura

Como isso pode ser lido MAIN/COPPER.ASM(e veja o diagrama abaixo):

  1. O timer do chip 8254 está configurado para disparar o IRQ0 com a frequência desejada.
  2. O manipulador de interrupção 8h (chamado pelo PIC 8259 após receber o IRQ0) é substituído por um procedimento aqui intti8.

Nota: o serviço de contagem de quadros DIS é realmente fornecido pelo simulador de cobre.

Parte 4: Modos Dev e Prod


Lendo o código fonte da Second Reality, você fica mais impressionado com quanta atenção a equipe prestou à mudança contínua do DEV para o PROD.

Modo de Desenvolvimento



No modo Desenvolvimento, cada componente da demonstração era um arquivo executável separado.

  • O DIS foi carregado no TSR residente e acessado através de uma interrupção 0cfh.
  • O carregador de inicialização causou uma interrupção do DOS 21hpara abrir, ler, pesquisar e fechar arquivos.

Essa configuração do DEV possui as seguintes vantagens:

  • Cada codificador e artista poderia trabalhar no arquivo executável e testá-lo separadamente, sem afetar o restante da equipe.
  • A demonstração completa a qualquer momento pode ser testada usando uma pequena SECOND.EXE(sem adicionar todos os EXEs ao final). O executável de cada PARTE foi carregado usando uma interrupção 021hdo DOS de um arquivo separado.

Produção (modo de demonstração)



No modo Produção, o pequeno SECOND.EXE(contendo o carregador de inicialização), o DIS e as partes da demonstração como EXEs separados foram combinados em um único SECOND.EXE.

  • O acesso ao DIS ainda foi feito por interrupção 0fch.
  • A API de interrupção do DOS 21h foi corrigida por suas próprias rotinas da Future Crew, que abrem arquivos a partir do final de um arquivo grande SECOND.EXE.

Essa configuração do PROD tem uma vantagem em termos de tempo de carregamento e proteção contra engenharia reversa ... mas, mais importante, do ponto de vista da programação ou do carregamento de PART, NADA muda ao mudar de DEV para PROD.

Parte 5: PARTE separada


Cada um dos efeitos visuais da Segunda Realidade é um executável do DOS totalmente funcional. Eles são chamados de PART e todos eles 23. Essa solução arquitetônica permitiu prototipagem rápida, desenvolvimento paralelo (já que o FC provavelmente não tinha ferramentas de controle de versão) e livre escolha de idiomas (ASM, C e até Pascal são encontrados na fonte).

PARTE separada


Uma lista de todas as PART / EXEs pode ser encontrada no código-fonte do mecanismo: U2.ASM . Aqui está uma descrição curta mais conveniente de todas as 23 partes (com a localização do código fonte, embora os nomes possam ser muito confusos):

TítuloArquivo executávelCodificadorCaptura de telaFonte
STARTMUS.EXEPRINCIPAL / STARTMUS.C
START.EXEINCÊNDIOSSTART / MAIN.c
Parte ocultaDDSTARS.EXEINCÊNDIOSDDSTARS / STARS.ASM
Alkutekstit iALKU.EXEINCÊNDIOSALKU / MAIN.C
Alkutekstit IIU2A.EXEPSIVISU / C / CPLAY.C
Alkutekstit IIIPAM.EXETRUG / WILDFIREPAM /
BEGLOGO.EXEBEG / BEG.C
GlenzGLENZ.EXEPSIGLENZ /
DottitunneliTUNNELI.EXETRUGTUNNELI / TUN10.PAS
TechnoTECHNO.EXEPSITECHNO / KOEA.ASM
PanicfakePANICEND.EXEPSIPânico
Vuori-scrollMNTSCRL.EXEFOREST / READ2.PAS
Estrelas dos sonhos do desertoDDSTARS.EXETRUG
LentePSI
RotazoomerLNS & ZOOM.EXEPSILENTE /
PlasmaINCÊNDIOS
PlasmacubePLZPART.EXEINCÊNDIOSPLZPART /
MiniVectorBallsMINVBALL.EXEPSIDOTS /
PeilipalloscrollRAYSCRL.EXETRUGWATER / DEMO.PAS
3D sinusfield3DSINFLD.EXEPSICOMAN / DOLOOP.C
JellypicJPLOGO.EXEPSIJPLOGO / JP.C
Vetor parte II 'U2E.EXEPSIVISU / C / CPLAY.C
Legendas / Agradecimentos
ENDLOGO.EXEEND / END.C
CRED.EXEINCÊNDIOSCRÉDITOS / PRINCIPAL.C
ENDSCRL.EXEENDSCRL / MAIN.C

Parece que cada desenvolvedor teve sua própria especialização, que poderia ser compartilhada em uma parte. Isso é especialmente perceptível na primeira cena com rolagem, navios e explosões (Alkutekstit). Embora isso pareça um efeito contínuo, na verdade são três arquivos executáveis ​​escritos por três pessoas diferentes:

Sequência de Alkutekstit (créditos)
ALKU by WILDFIREU2A by PSIPAM por TRUG / WILDFIRE

Ativos


Os ativos de imagem ( .LBM) são gerados usando o Deluxe Paint , um editor de bitmap extremamente popular nos anos 90. Curiosamente, eles são convertidos em uma matriz de bytes e compilados dentro do PART. Como resultado disso, o arquivo exe também baixa todos os ativos. Além disso, isso complica a engenharia reversa.

Entre os conjuntos legais de ativos estão a famosa CITY e SHIP da última cena 3D:



Unidade interna PART


Como todos foram compilados nos executáveis ​​do DOS, no PART, qualquer idioma poderia ser usado:


Quanto ao uso de memória, eu li muito sobre o MMU na Wikipedia e em outros sites ... mas, de fato, cada parte poderia usar qualquer coisa, porque após a execução, era completamente descarregada da memória.

Ao trabalhar com VGA, cada parte usou seu próprio conjunto de truques e trabalhou em sua resolução. Em todos eles, não foram utilizados o Modo 13h e nem o ModoX, mas um modo 13h modificado com resolução própria. O arquivo SCRIPT geralmente menciona 320x200 e 320x400.

Infelizmente, ao analisar o PART, a leitura do código-fonte se torna uma tarefa assustadora: a qualidade do código e dos comentários diminui drasticamente. Talvez isso tenha acontecido por causa da pressa ou porque cada PARTE trabalhou em seu próprio desenvolvedor (ou seja, não havia necessidade "real" de comentários ou compreensão do código), mas o resultado foi algo completamente confuso:


Algoritmos sofisticados não são apenas difícil de entender até mesmo os nomes de variáveis ( a, b, co[]...). O código seria muito mais legível se os desenvolvedores nos deixassem dicas nas notas de lançamento. Como resultado, não dediquei muito tempo ao estudo de cada parte; A exceção foi o mecanismo 3D responsável pelo U2A.EXE e U2E.EXE.

Motor 3D Segunda Realidade




Enfim, decidi estudar em detalhes o mecanismo 3D, que era usado em duas partes: U2A.EXEe U2E.EXE.

O código fonte é C com procedimentos otimizados para montadores (especialmente preenchimento e sombra do Gouro):

  • CITY.C (código principal).
  • VISU.C (biblioteca visu.lib).
  • AVID.ASM (vídeo otimizado do assembler (limpeza, cópia de tela etc.)).
  • ADRAW.ASM (objetos de desenho e truncamento).
  • ACALC.ASM (matrizes e cálculos rápidos de pecado / cos).


A arquitetura desses componentes é bastante notável: a biblioteca VISUexecuta todas as tarefas complexas, por exemplo, carregando ativos: objetos 3DS, materiais e fluxos (movimentos de câmera e navio).

O mecanismo classifica os objetos que precisam ser desenhados e os renderiza usando o algoritmo do artista. Isso leva a uma grande quantidade de redesenho, mas como as travas VGA permitem gravar 4 pixels ao mesmo tempo, não é tão ruim.

Um fato interessante: o mecanismo realiza transformações de maneira "antiga": em vez de usar matrizes 4x4 homogêneas comuns, usa matrizes de rotação 3 * 3 e um vetor de deslocamento.

Aqui está um resumo no pseudo-código:

      main(){

            scenem=readfile(tmpname);  // Load materials
            scene0=readfile(tmpname);  // Load animation

            for(f=-1,c=1;c<d;c++){  //Load objects
              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 // waitb for retrace via copper simulator interrupt call 
                vid_clear();
                
                // parse animation stream, update objects
                for(;;){}

                vid_cameraangle(fov); // Field of vision

                // Calc matrices and add to order list (only enabled objects)
                for(a=1;ac<conum;a++) if(co[a].on) /* start at 1 to skip camera */
                    calc_applyrmatrix(o->r,&cam);

                // Zsort via Bubble Sort
                for(a=0;ac<ordernum;a++)
                    for(b=a-1;b>=0 && dis>co[order[b]].dist;b--)

                // Draw
                for(a=0;ac<ordernum;a++)
                    vis_drawobject(o);
              }
            }
            return(0);
     }

Portas para sistemas modernos


Após o lançamento deste artigo, muitos desenvolvedores começaram a portar Second Reality para sistemas modernos. Claudio Matsuoka começou a criar a sr-port , uma porta C para Linux e OpenGL ES 2.0, que até agora parece bastante impressionante. Nick Kovacs fez um ótimo trabalho no PART PLZ, portando-o para C (agora faz parte do código-fonte sr-port), bem como para o javascript :


All Articles