Nautilus
Resolução gráfica

A questão gráfica, numa conversão de um jogo asm/DOS para Delphi/Windows é bem mais complicada do que parece e exige decisões drásticas. Por exemplo: você vai rodar o jogo em modo janela ou fullscreen?

Se optar pelo modo janela, poderá manter as mesmas dimensões do original mas se optar pelo modo fullscreen vai ter um problemão. A maioria dos jogos VGA foi projetada para rodar em XTs ou ATs com monitores VGA de 14 polegadas. Esses monitores eram pouco melhores que uma boa televisão e portanto as imagens se mostram razoáveis.

Rodar um jogo com essas resoluções em monitores modernos de 17/19 polegadas, tela plana e alta definição é um desastre na certa. As imagens ficam grotescas e o jogador percebe as menores falhas. A opção mais acertada é usar o modo janela, porém o modo janela não permite emulação de determinadas resoluções (principalmente a que estamos usando) e portanto teremos que criar um sistema que simule os mesmos resultados.

Vamos entender primeiro como funciona o modo 640 x 350 x 16 cores. Cada pixel é representado por um valor entre 0 e 15 e que define um índice numa matriz de cores (palete). Veja na listagem abaixo, que cada índice contém a definição RGB da cor.

var
  RegCor: array[0..15] of dword = (
$000000,$0000C0,$00C000,$C00000,
$C000C0,$00C0C0,$C0C000,$808080,
$C0C0C0,$0000FF,$00FF00,$FF0000,
$FF00FF,$00FFFF,$FFFF00,$FFFFFF);

Para manter a compatibilidade e ao mesmo tempo migrar para um sistema gráfico mais sofisticado, vamos adotar a estrutura de buffer, apresentada na seção sobre Delphi e assembler do club TILT. O link para ela é: Asm 4 Dlp. Neste esquema um buffer TBitmap funciona como se fosse a região de memória da tela, no segmento A000h.

var
Buf: TBitmap;
Vid,Dfr: dword; procedure TForm1.FormCreate(Sender: TObject);
begin
Buf:= TBitmap.Create;
Buf.Width:= 640;
Buf.Height:= 350;
Buf.Canvas.Brush.Color:= clBlack;
Buf.PixelFormat:= pf32Bit;
Buf.Canvas.FillRect(bounds(0,0,640,350));
Vid:= Integer(Buf.ScanLine[0]);
Dfr:= Vid-Integer(Buf.ScanLine[1]);
end; procedure TForm1.FormDestroy(Sender: TObject);
begin
Buf.Free;
end;

Temos portanto o início da área de imagem e nele "pokearemos" os pixels exatamente como faríamos na programação asm, no entanto sem ter que se sujeitar ao modelo de plano de cor, das resoluções VGA. Faremos a conversão direta, na hora de setar o pixel, já que o buffer foi configurado para 32bits de profundidade de cor.

Se você deseja entender um pouco mais como é formada uma imagem em modo VGA e como acessar diretamente os planos de cor, recomendo a leitura da seguinte página: Programando em modo VGA..

Agora só falta adaptar os caracteres do alfabeto e para isso usaremos mais uma matriz de bytes, onde cada letra é definida por 8 bytes e portanto cada caracter ocupará 8 x 8 pixels.

var
  Letras: array[0..1439] of byte = (
000,000,000,000,000,000,000,000, //0
000,000,000,000,000,000,000,000,
000,000,000,000,000,000,000,000,
000,000,000,000,000,000,000,000,
192,000,000,000,000,000,000,000,
000,000,000,000,000,000,000,015,
192,048,060,012,015,015,003,192,
015,007,128,030,000,000,000,015,
192,241,240,060,124,015,003,192,
015,030,000,030,000,007,128,015,
192,240,000,060,007,003,255,000,
003,003,195,255,240,007,128,015,
192,241,240,060,124,003,255,000,
003,003,195,255,240,255,252,015,
192,240,060,060,015,015,003,192,
015,030,000,030,000,007,128,015,
192,240,000,060,014,015,003,192,
015,007,128,030,000,007,128,015,
192,000,000,000,000,000,000,000,
000,000,000,000,000,000,000,015,
000,002,004,200,208,224,192,000,
255,255,255,255,255,255,255,255,
000,000,000,255,000,000,000,000,
192,192,192,192,192,192,192,192,
255,000,000,000,000,000,000,000,
000,000,000,000,000,000,000,000, //25 cursor
003,003,003,003,003,003,003,003,
000,000,000,000,000,000,000,255,
255,192,192,192,192,192,192,192,
255,003,003,003,003,003,003,003,
192,192,192,192,192,192,192,255,
003,003,003,003,003,003,003,255,
000,000,000,000,000,000,000,000, //32 espaço
000,048,048,048,048,000,048,000,
000,108,108,000,000,000,000,000,
000,102,255,102,102,255,102,000,
024,126,192,124,006,252,024,000, //36
000,102,108,024,048,102,198,000,
000,112,216,112,218,204,118,000,
000,024,024,048,000,000,000,000,
000,024,048,048,048,048,024,000,
000,048,024,024,024,024,048,000,
000,146,084,056,056,084,146,000,
000,000,024,024,126,024,024,000,
000,000,000,000,000,048,048,096,
000,000,000,000,252,000,000,000, //45
000,000,000,000,000,048,048,000,
000,006,012,024,048,096,192,000,
000,124,206,214,214,230,124,000,
000,024,120,024,024,024,126,000,
000,124,198,006,124,192,254,000,
000,254,006,012,006,198,124,000,
000,030,054,102,254,006,006,000,
000,254,192,252,006,198,124,000,
000,048,096,252,198,198,124,000,
000,126,198,012,024,024,024,000,
000,124,198,254,198,198,124,000,
000,124,198,198,126,012,024,000,
000,000,048,048,000,048,048,000,
000,000,048,048,000,048,048,096,
000,024,048,096,096,048,024,000,
000,000,000,252,000,252,000,000,
000,048,024,012,012,024,048,000,
000,124,198,012,024,000,024,000,
000,124,198,206,206,192,124,000, //64
000,124,198,198,254,198,198,000,
000,252,198,252,198,198,252,000,
000,124,198,192,192,198,124,000,
000,252,198,198,198,198,252,000,
000,254,192,248,192,192,254,000,
000,254,192,248,192,192,192,000,
000,124,198,192,206,198,124,000,
000,198,198,254,198,198,198,000,
000,252,048,048,048,048,252,000,
000,062,012,012,012,204,120,000,
000,204,216,240,216,204,198,000,
000,192,192,192,192,192,254,000,
000,238,254,214,198,198,198,000,
000,230,246,222,206,198,198,000,
000,124,198,198,198,198,124,000,
000,252,198,198,252,192,192,000,
000,124,198,198,198,204,118,000,
000,252,198,198,252,204,198,000,
000,126,192,124,006,198,124,000,
000,252,048,048,048,048,048,000,
000,198,198,198,198,198,124,000,
000,198,198,198,102,060,024,000,
000,198,198,198,214,238,198,000,
000,198,108,056,056,108,198,000,
000,198,108,056,024,024,024,000,
000,254,012,024,048,096,254,000,
000,124,096,096,096,096,124,000,
000,192,096,048,024,012,006,000,
000,124,012,012,012,012,124,000,
000,056,108,198,000,000,000,000,
000,000,000,000,000,000,000,255,
000,048,024,000,000,000,000,000,
000,000,120,012,124,204,122,000,
000,192,192,252,198,198,124,000,
000,000,124,198,192,198,124,000,
000,006,006,126,198,198,124,000, //100
000,000,124,198,254,192,124,000,
000,060,102,240,096,096,096,000,
000,000,124,198,198,126,006,124,
000,192,192,220,230,198,198,000,
000,024,000,120,024,024,124,000,
000,012,000,012,012,204,216,112,
000,192,204,216,240,216,204,000,
000,120,024,024,024,024,124,000,
000,000,236,254,214,198,198,000,
000,000,188,198,198,198,198,000,
000,000,124,198,198,198,124,000,
000,000,252,198,198,252,192,192,
000,000,124,198,198,126,006,014,
000,000,188,198,192,192,192,000,
000,000,126,192,124,006,252,000,
000,048,252,048,048,048,056,000,
000,000,198,198,198,198,122,000,
000,000,198,198,102,060,024,000,
000,000,198,198,214,254,108,000,
000,000,198,108,056,108,198,000,
000,000,198,198,108,024,048,096,
000,000,252,024,048,096,252,000,
000,028,024,048,048,024,028,000,
000,024,024,000,024,024,024,000,
000,112,048,024,024,048,112,000,
000,102,156,000,000,000,000,000,
000,016,040,068,130,254,000,000,
000,124,198,192,192,198,124,048,
000,000,124,198,192,198,124,048,
000,204,102,051,051,102,204,000,
198,000,198,198,198,198,124,000,
096,048,124,198,254,198,198,000,
012,024,124,198,254,198,198,000,
012,024,254,192,248,192,254,000,
024,048,252,048,048,048,252,000,
012,024,124,198,198,198,124,000,
012,024,198,198,198,198,124,000,
056,068,124,198,254,198,198,000,
056,068,254,192,248,192,254,000,
056,068,124,198,198,198,124,000, //140
054,108,124,198,254,198,198,000,
054,108,124,198,198,198,124,000,
000,198,000,198,198,198,122,000,
096,048,120,012,124,204,122,000,
012,024,120,012,124,204,122,000,
012,024,124,198,254,192,124,000,
012,024,000,120,024,024,124,000,
012,024,124,198,198,198,124,000,
012,024,198,198,198,198,122,000,
056,068,120,012,124,204,122,000,
056,068,124,198,254,192,124,000,
056,068,124,198,198,198,124,000,
054,108,120,012,124,204,122,000,
054,108,124,198,198,198,124,000,
000,001,002,100,104,112,096,000,
000,126,066,066,066,066,126,000,
000,126,126,126,126,126,126,000,
255,129,129,129,129,129,129,255,
255,129,189,189,189,189,129,255,
000,000,016,056,124,016,016,016,
000,000,016,016,016,124,056,016,
000,000,004,006,255,006,004,000,
000,000,003,003,000,243,243,000, //163/164
000,000,192,192,000,207,207,000,
000,024,060,126,024,024,024,000, //165/166
000,024,024,024,126,060,024,000,
000,031,031,031,031,031,031,000,
000,224,160,224,000,224,000,000,
240,240,240,111,111,240,240,240,
015,015,015,246,246,015,015,015,
006,062,124,052,062,060,012,000,
000,255,255,255,255,255,255,000,
000,064,112,124,127,124,112,064,
000,002,014,062,254,062,014,002,
000,004,014,031,004,004,000,000,
000,014,014,014,000,000,000,000,
128,128,128,255,128,128,128,000,
000,000,000,255,000,000,000,000,
128,128,128,128,128,128,128,000);

Ainda na inicialização do programa precisamos apontar para o endereço físico da matriz de letras e criar uma variável para conter o código do caracter a sem impresso no buffer de vídeo. Aproveitamos e criamos também uma variável para conter a cor default de escrita e a cor default de fundo:

var
Buf: TBitmap;
Vid,Dfr: dword;
Ink: dword = $00FFFFFF;
Pap: dword = $00000000;
RegCor: array[0..15] of dword = (
$000000,$0000C0,$00C000,$C00000,
$C000C0,$00C0C0,$C0C000,$808080,
$C0C0C0,$0000FF,$00FF00,$FF0000,
$FF00FF,$00FFFF,$FFFF00,$FFFFFF);
Car: byte = 32;
EndLet,EndVid: dword;
Letras: array[0..1439] of byte = (
000,000,000,000,000,000,000,000, //0 ... procedure TForm1.FormCreate(Sender: TObject);
begin
...
EndLet:= integer(@Letras[0]);
EndVid:= Vid;
end;

Agora só falta criar a procedure que irá "imprimir" uma letra no buffer e faremos ela em assembly:

procedure TForm1.Chars;
asm
//[Car] contém o código do caracter que será impresso //na posição atual de tela
push edi
push esi
push ebx
xor eax,eax //zera eax
mov al,[Car] //pega o caracter
mov ecx,8
mul cl //multiplica por 8
mov esi,[EndLet]
add esi,eax //encontra o endereço na matriz
mov edi,[EndVid] //endereço de impressão
@Loop1:
mov al,[esi] //pega 1 byte - linha de pixels
inc esi
push edi
push ecx
mov ecx,8 //prepara para imprimir 8 bits
@Loop2:
rol al,1 //rotaciona para o carry flag
mov ebx,[Pap]
jnc @Loop3 //se 0 usa cor de fundo
mov ebx,[Ink] //se 1 pega a cor de escrita
@Loop3:
mov [edi],Ebx //coloca o pixel no buffer
add edi,4 //próximo pixel
loop @Loop2 //8 vezes até completar a linha
pop ecx
pop edi
sub edi,Dfr //desloca a diferença
loop @Loop1 //8 vezes até completar o caracter
mov edi,[EndVid]
add edi,32
mov [EndVid],edi //próxima posição de caracter
pop ebx
pop esi
pop edi
end;

Ao mandar imprimir um caracter, a variável EndVid deve conter o endereça base, no buffer, onde o caracter será montado. Como cada caracter tem 8 x 8 picels e a tela 640 x 350 pixels podemos ter 80 colunas por 43 linhas de texto como padrão de impressão. Uma impressão de string, em Delphi, poderia ser feita assim:

procedure TForm1.SpeedButton4Click(Sender: TObject);
const
S: string = 'TILT online|';
var
Tm: integer;
begin
//Linha 10, coluna 15
EndVid:= (Vid - (Dfr * 80)) + 120;
for Tm:= 1 to length(S)-1 do begin
Car:= ord(S[Tm]);
Chars;
end;
Refresh;
end; procedure TForm1.Refresh;
begin
Form1.Canvas.Draw(0,0,Buf);
end;

Já a mesma impressão, escrita em assembly ficaria assim:

procedure TForm1.SpeedButton3Click(Sender: TObject);
const
S: string = 'Testando...|';
asm
push ebx
mov al,49
mov [Car],al
call Chars
//Linha 20, coluna 15
mov eax,[Dfr]
mov ecx,88
mul ecx
mov ebx,[Vid]
sub ebx,eax
add ebx,120
mov [EndVid],ebx
mov ebx,S
@Loop1:
mov al,[ebx]
inc ebx
cmp al,'|'
jz @Loop2
mov [Car],al
call Chars
jmp @Loop1
@Loop2:
call Refresh
pop ebx
end;

Divertido, não é mesmo? Se está com preguiça de digitar ou fazer um copy paste, clique aqui e baixe um zip com o fonte destas rotinas.

 
online