Tutorial de Assembler de Adam Hyde 1.0

PARTE 5
Traduzido por Renato Nunes Bastos


Versão   :  1.3
Data       :  15-03-1996 / online by Renato 01-11-1998
Contato  :  blackcat@vale.faroc.com.au
                 http://www.faroc.com.au/~blackcat
;Renato  :  rnbastos@ig.com.br 
                 http://www.geocities.com/SiliconValley/Park/3174


Sumário das Instruções | Clocks/instrução | Planos da memória VGA | Desenhando Linhas


Bem, outra semana ou algo assim parece ter passado... Outra semana que eu deveria ter usado para fazer algo útil. De qualquer modo, parece que estes tutoriais estão ganhando um pouco mais de popularidade, o que é bom.

Eu tenho recebido alguns códigos demo de alguém que parece ter achado um uso para os tutoriais. Por favor, se você tentar algo com ajuda do tutorial, ou de seu jeito, mande para mim. Eu gosto de ver o que as pessoas têm feito do meu trabalho, ou apenas quão criativo vocês todos são. Se você escrever algo que eu ache útil para os outros aprenderem, ou apenas é maneiro, eu vou colocar na minha página da web.

Note que eu incluí um demo starfield neste tutorial dessa semana.Você pode rodar STARS.EXE, ou olhar STARS.PAS que é o código fonte. É só um simples demo, mas pode ser usado para fazer alguns efeitos interessantes.

Agora, esta semana vamos inicialmente listar um sumário de todas as instruções que você já deveria ter aprendido até agora, e umas novas. Então vamos ver como a VGA é arrumada, e cobrir uma rotina simples de linha.


SUMÁRIO DO CONJUNTO DE INSTRUÇÕES

                                   Descrição: Esta instrução soma <FONTE>
                                                    e <DEST> e soma o valor armazenado no
                                                    flag de vai-um, que será um ou zero,
                                                    a <DEST> também.

                                   Basicamente, DEST = DEST + FONTE + CF

                                   EX.: ADD AX, BX

                                   Descrição: Esta instrução soma <FONTE>
                                                    e <DEST>, armazenando o resultado em
                                                    <DEST>.

                                   EX.: ADD AX, BX

                                   Descrição: Esta instrução realiza uma
                                                    comparação bit a bit de <DEST> e
                                                    <FONTE>, armazenando o resultado em
                                                    <DEST>.

                                   EX.: AND 0, 0     = 0
                                           AND 0, 1     = 0
                                           AND 1, 0     = 0
                                           AND 1, 1     = 1

                                   Descrição: Esta instrução testa
                                                    <BIT NUMBER> de <DEST> que pode
                                                     ser um registrador de 16 ou 32 bits ou
                                                     posição da memória. Se <DEST> é um
                                                     número de 16 bits então o <BIT NUMBER> pode
                                                     ser de 0 a 15, senão, se <DEST> é um
                                                     número de 32 bits, então o <BIT NUMBER>
                                                     pode ter um valor de 0 a 31.

                                   O valor em <BIT NUMBER> de <DEST> é
                                   então copiado para o flag de vai-um.

                                   EX.: BT   AX, 3
                                           JC    EraIgualAUm

                                   Descrição: Esta instrução simplesmente
                                                    chama uma subrotina. Em termos mais
                                                    técnicos, ela põe o endereço da próxima
                                                    instrução, IP, na pilha, e seta IP, o
                                                    registro de instruções, para o valor
                                                    especificado por <DEST>.

                                   EX.: CALL MyProc

                                    Descrição: Esta instrução extende o
                                                     byte em AL para AX.

                                    EX.: MOV   AL, 01h
                                           CBW
                                           ADD    BX, AX   ; Faz algo com AX

                                    Descrição: Esta instrução zera o flag
                                                     de vai-um no registrador de flags.

                                    EX.: CLC

                                    Descrição: Esta instrução limpa o
                                                     flag de direção no registrador de flags
                                                     para 0. Quando o flag de direção é 0,
                                                     qualquer instrução de strings incrementa
                                                     os registradores de índice SI e DI.

                                    EX.: CLD

                                    Descrição: Esta instrução limpa o flag de
                                                      interrupção no registrador de flags para
                                                      0, assim desabilitando interrupções de
                                                      hardware.

                                    EX.: CLI

                                    Descrição: Esta instrução checa o valor
                                                     atual no flag de vai-um.
                                                     Se for 0 - transforma em 1 e se for
                                                     1 - passa a ser 0.

                                    EX.: BT   AX, 1    ; Testa o bit 1 de AX
                                            JC    EraUm
                                            JMP   Fim

                                        EraUm:
                                        CMC            ; Retorna CF para 0

                                        Fim:

                                    Descrição: Esta instrução compara
                                                     <VALOR1> e <VALOR2> e reflete a comparação
                                                      nos flags.

                                    EX.: CMP AX, BX

                                    Veja também as intruções Jcc.

                                    Descrição: Esta instrução extende a
                                                     word em AX para o par DX:AX.

                                    EX.: CWD

                                    Descrição: Esta instrução subtrai
                                                     um do valor em <VALOR> e armazena
                                                     o resultado em <VALOR>.

                                    EX.: DEC AX

                                    Descrição: Esta instrução divide
                                                     <VALOR> por, ou AX para byte, DX:AX para
                                                     word ou EDX:EAX para doubleword.

                                    Para byte, o quociente é retornado em
                                    AL e o resto em AH, para word o
                                    quociente é retornado em AX e o
                                    resto em DX e para DWORD, o
                                    quociente volta em EAX e o
                                    resto em EDX.

                                    EX.: MOV   AX, 12
                                            MOV    BH, 5
                                            DIV    BH
                                            MOV    Quociente, AL
                                            MOV    Resto, AH

                                    Descrição: Esta instrução lê um valor
                                                     de uma das 65536 portas de hardware
                                                     especificada no <ACUMULADOR>.

                                    AX e AL são comumente usados para portas
                                    de entrada, e DX é comumente usado para
                                    identificar a porta.

                                    EX.: IN    AX, 72h
                                            MOV    DX, 3C7h
                                            IN     AL, DX

                                    Descrição: Esta instrução soma um ao
                                                      número em <VALOR>, e armazena
                                                       o resultado em <VALOR>.

                                    EX.: MOV   AX, 13h   ; AX = 13h
                                            INC    AX        ; AX = 14h

                                    Descrição: Esta instrução salva os valores
                                                     correntes dos flags e IP na pilha, e então
                                                     chama a <INTERRUPÇÃO> baseada no valor de AH.

                                    EX.:    MOV   AH, 00h   ; Seta o modo de vídeo
                                              MOV    AL, 13h   ; Modo 13h
                                              INT    10h             ; Gera interrupção

    Eu não vou repetir eu mesmo todos os 32, dê uma olhada no Tutorial Três a lista completa deles. Tenha em mente que seria uma boa idéia chamar CMP, OR, DEC ou algo semelhante antes de usar uma dessas instruções. :)

   EX.: DEC   AX
           JZ     AX_Chegou_A_Zero

                                    Descrição: Esta instrução simplesmente
                                                     carrega um novo valor, <DEST>, em IP,
                                                     assim transferindo o controle para outra
                                                      parte do código.

                                    EX.: JMP   MyLabel

                                    Descrição: Esta instrução copia os
                                                     bytes mais baixos do registrador de flags
                                                     para AH. O conteúdo de AH vai parecer
                                                     com algo semeelhante depois que
                                                     a instrução for executada:

Flag SF ZF -- AF -- PF -- CF
Bit 07 06 05 04 03 02 01 00

            Você pode agora testar os bits individualmente, ou realizar
            uma instrução similar à seguinte para pegar um flag apenas:

          EX.: LAHF
                  SHR    AH, 6
                  AND   AH, 1   ; AH contém o flag ZF.

                                    Descrição: Esta instrução carrega o
                                                     endreço de memória que <FONTE> significa,
                                                     em <DEST>.

                                    EX.: eu uso  LEA SI, Str  numa das minhas
                                            procedures que põe uma string na
                                            tela bem rápido.

                                    Descrição: Esta instrução é uma forma
                                                     do loop For...Do que existe na maioria
                                                     das linguagens de alto nível. Basicamente
                                                     ele volta ao label, ou segmento de memória
                                                     até que CX = 0.

                                    EX.: MOV   CX, 12

                                         FazAlgumaCoisa:
                                           ;...
                                           ;...
                                           ;... Isto será repetido 12 vezes

                                         LOOP FazAlgumaCoisa

                                    Descrição: Esta instrução existe de
                                                     várias formas. Todas aceitam  mesma
                                                     sintaxe, em que <FONTE> especifica um
                                                     ponteiro de 48 bits, consistindo de um
                                                     offset de 32 bits e um seletor de 16 bit.
                                                     O offset de 32 bis é caregado em
                                                     <DEST>, e o seletor é carregado no
                                                     registrador de segmento especificado por seg.

                                    As seguintes formas existem:

                                    LDS
                                    LES
                                    LFS      * 32 bits
                                    LGS      * 32 bits
                                    LSS

                                    EX.: LES   SI, Um_Ponteiro

                                    Descrição: Esta instrução copia
                                                     <FONTE> em <DEST>.

                                    EX.: MOV   AX, 3Eh
                                            MOV    SI, 12h

                                    Descrição: Esta instrução multiplica
                                                     <FONTE> pelo acumulador, que depende
                                                     do tamanho de <FONTE>.

                                    Se <FONTE> é um byte então:

                                    * AL é o multiplicando;
                                    * AX é o produto.

                                    Se <FONTE> é uma word então:

                                    * AX é o multiplicando;
                                    * DX:AX é o produto.

                                    Se <FONTE> é uma doubleword então:

                                    * EAX é o multiplicando;
                                    * EDX:EAX é o produto.

                                    OBS.: Os flags são inalterados
                                               excetos para OF e CF, que são
                                               zerados se o byte alto, word ou
                                               dword do produto for 0.

                                    EX.: MOV   AL, 3
                                            MUL    10
                                            MOV    Resultado, AX

                                    Descrição: Esta instrução subtrai
                                                     <VALOR> de 0, resultando na negação
                                                     do complemento a dois de <VALOR>.

                                    EX.: MOV   AX, 03h
                                            NEG    AX       ; AX = -3

                                    Descrição: Esta instrução inverte o
                                                     estado de cada bit no operando.

                                    EX.: NOT   CX

                                    Descrição: Esta instrução realiza uma
                                                     operação de OU booleano entre cada bit de
                                                     <DEST> e <FONTE>, guardando o resultado
                                                     em <DEST>.

                                    EX.: OR 0, 0     = 0
                                            OR 0, 1     = 1
                                            OR 1, 0     = 1
                                            OR 1, 1     = 1

                                    Descrição: Esta instrução manda para a
                                                     saída o valor do acumulador para <PORTA>.
                                                     Usando o registrador DX para pasar a porta
                                                     OUT, você pode acessar 65,536 portas.

                                    EX.: MOV   DX, 378h
                                            OUT    DX, AX

                                    Descrição: Esta instrução pega o valor
                                                     atual do topo da pilha e coloca no
                                                     <REGISTRADOR>.

                                    EX.: POP   AX

                                    Descrição: Esta instrução pega todos os
                                                     registradores de uso geral de 16 bits
                                                     da pilha, exceto SP.

                                    É o mesmo que:

                                    POP    AX
                                    POP    BX
                                    POP    CX
                                    ...

                                    EX.: POPA

                                    Descrição: Esta instrução pega o
                                                     byte baixo dos flags da pilha.

                                    EX.: POPF

                                    Descrição: Esta instrução põe
                                                     <REGISTRADOR> na pilha.

                                    EX.: PUSH  AX

                                    Descrição: Esta instrução põe todos os
                                                     registradores de uso geral de 16 bits
                                                     na pilha.

                                    É o mesmo que:

                                    PUSH   AX
                                    PUSH   BX
                                    PUSH   CX
                                    ...

                                    EX.: PUSHA

                                    Descrição: Esta instrução põe o
                                                     byte baixo dos flags na pilha.

                                    EX.: PUSHF

                                    Descrição: Esta instrução repetirá
                                                     a intrução seguinte o número de vezes
                                                     especificado em CX.

                                    EX.: MOV   CX, 6
                                            REP    STOSB    ; Armazena 6 bytes

                                    Descrição: Esta instrução retorna IP
                                                     ao valor que ele tinha antes da última
                                                     instrução CALL. RET, ou RETF para um
                                                     jump distante (far), deve ser chamado
                                                    quando se usa assembler puro.

                                    EX.: RET

                                    Descrição: Esta instrução roda
                                                     <DEST> <VALOR> vezes. Uma rodada é
                                                      realizada shiftando <DEST> uma vez, então
                                                      transfere-se o bit que saiu para
                                                      a posição de mais baixa ordem de <DEST>.

                                    EX.: ROL   AX, 3

                                    Descrição: Esta instrução roda
                                                     <DEST> <VALOR> vezes. Uma rodada é
                                                      realizada shiftando <DEST> uma vez, então
                                                      transfere-se o bit que saiu para
                                                      a posição de mais alta ordem de <DEST>.

                                    EX.: ROR    BX, 5

                                    Descrição: Esta instrução carrega o
                                                     conteúdo do registrador AH nos bits
                                                     7, 6, 4, 2 e 0 do registrador de flags.

                                    EX.: SAHF

                                    Descrição: Esta instrução subtrai
                                                     <FONTE> de <DEST>, e decrementa
                                                     <DEST> de uma unidade de o flag de vai-um
                                                     estiver setado, armazenando o resultado
                                                     em <DEST>.

                                   Basicamemte, <DEST> = <DEST> - <FONTE> - CF

                                    EX.: SBB   AX, BX

                                   Descrição: Esta instrução desloca <DEST>
                                                    à esquerda de <VALUE> unidades.
                                                    Eu não vou entrar em detalhes
                                                    sobre a teoria disso de novo.
                                                    Se você não tem certeza do que esta
                                                    instrução faz, por favor, leia o Tutorial Quatro.

                                   EX.: SHL   AX, 5

                                   Descrição: Esta instrução desloca <DEST>
                                                    à direita de <VALUE> unidades. Por favor
                                                    veja o Tutorial Quatro para a teoria dos
                                                    shifts.

                                   EX.: SHR   DX, 1

                                   Descrição: Esta instrução seta o valor
                                                    do carry flag para um.

                                   EX.: STC

                                   Descrição: Esta instrução seta o
                                                    valor do flag de direção para um. Isto
                                                    instrui a todas operações a decrementar
                                                    os registradores de índice.

                                   EX.: STD
                                           REP STOSB   ; DI está sendo decrementado
                   

                                   Descrição: Esta instrução seta o
                                                    valor do flag de interrupção para um, assim
                                                    permitindo que interrupções de hardware
                                                   ocorram.

                                   EX.: CLI      ; Pára interrupções
                                            ...         ; Realiza uma função crucial
                                           STI       ; Habilita interrupções

                                   Descrição: Esta instrução existe nas
                                                    seguintes formas:

                                   STOSB         - Armazena um byte                 - AL
                                   STOSW        - Armazena uma word             - AX
                                   STOSD         - Armazena uma doubleword  - EAX

                                   As instruções escrevem o conteúdo atual
                                   do acumulador para a posição de memória
                                   apontada por ES:DI. Então ela incrementa
                                   ou decrementa DI de acordo com o operando
                                   usado, e o valor do flag de direção.

                                   Ex.: MOV   AX, 0A000h
                                        MOV    ES, AX
                                        MOV    AL, 03h
                                        MOV    DI, 0
                                        STOSB           ; Armazena 03 em ES:DI,
                                                       ; que é o topo da tela
                                                       ; em modo 13h

                                   Descrição: Esta instrução subtrai
                                                    <FONTE> de <DEST>, armazenando o resultado
                                                    em <DEST>.

                                   EX.: SUB   ECX, 12

                                   Descrição: Esta instrução realiza uma
                                                    operação AND  bit-a-bit em  <FONTE> e
                                                    <DEST>. O resultado refflete nos flags,
                                                    e eles são são setados como se fizéssemos
                                                    um AND.

                                   EX.: TEST   AL, 0Fh   ; Checa se algum
                                                                       ; bit está setado
                                                                       ; no mais baixo
                                                                       ; nibble de AL

                                   Descrição: Esta instrução troca os
                                                    valores de <VALOR1> e <VALOR2>.

                                   EX.: XCHG   AX, BX

                                   Descrição: Esta instrução realiza
                                                    um OU exclusivo bit-a-bit em
                                                    <FONTE> e <DEST>. A operação é
                                                    definida como segue:

                                   XOR    0, 0    = 0
                                   XOR    0, 1    = 1
                                   XOR    1, 0    = 1
                                   XOR    1, 1    = 0

                                   EX.: XOR   AX, BX


Ufa! Que montão existem, e nós só vimos as básicas! Não se espera que você entenda cada uma delas.  Você provavelmente viu expressões como 'Complemento a Dois', e pensou - "Que é que essa merda quer dizer?".

Não se preocupe com isso por enquanto.  Vamos continuar normalmente, e introduzir as novas instruções acima uma por uma, explicando-as quando o fizermos. Se você já as entende, isso é um bônus. Você vai notar que havia muitas instruções acima, do 8086. Há, na verdade, poucos casos em que é necessário usar uma instrução do 386 ou 486, muito menos do Pentium.

De qualquer modo, antes de avançar com a VGA, eu vou só listar a velocidade em que cada instrução acima é executada, assim você pode usar isso para ver como as rotinas em Assembler são rápidas.

Instrução Clocks no 386 Clocks no 486
ADC 2 1
ADD 2 1
AND 2 1
BT 3 3
CALL 7+m 3
CBW 3 3
CLC 2 2
CLD 2 2
CLI 5 3
CMC 2 2
CMP 2 1
CWD 2 3
DEC 2 1
DIV
- Byte
- Word
- DWord
9-14 13-18
9-22 13-26
9-38 13-42
IN 12/13 14
INC 2 1
INT depende depende
Jcc
  - em loop
  - não loop
7+m 3
3 1
JMP 7+m 3
LAHF 2 3
LEA 2 1
LOOP 11 6
Lseg 7 6
MOV 2 1
MUL
   - Byte
   - Word
   - DWord
9-14 13-18
9-22 13-26
9-38 13-42
NEG 2 1
NOT 2 1
OR 2 1
OUT 10/11 16
POP 4 1
POPA 24 9
POPF 5 9
PUSH 2 1
PUSHA 18 11
PUSHF 4 4
REP depende depende
RET 10+m 5
ROL 3 3
ROR 3 3
SAHF 3 2
SBB 2 1
SHL 3 3
SHR 3 3
STC 2 2
STD 2 2
STI 3 5
STOS 4 5
SUB 2 1
TEST 2 1
XCHG 3 3
XOR 2 1

Obs.: m = Número de componentes na próxima instrução executada.

Ugh, eu nunca quero ver outro clock de novo! Agora, continuemos com o divertido - VGA!


Você provavlmente já notou que sua placa de vídeo tem mais que 256K de RAM. (se não tem, então estes tutoriais não são provavelmente para você.) Mesmo que você tenha só 256K de RAM, como meu velho 386, você ainda é capaz de entrar no modo 13h - 320x200x256. Porém, isto levanta algumas quetões. Multiplique 320 por 200 e você vai notar que você só precisa de 64,000 bytes de memória para armazenar uma tela simples. (A VGA na verdade nos dá 64K, que é 65,536 bytes para quem não sabe.) O que aconteceu com os restantes 192K?

Bem, a VGA é na verdade arrumada em planos de bits, como isso:
                           |---------------------------|
                        |--------------------------|
                    |--------------------------|    |
                |--------------------------|    |
                |                                        |
                |                                        |
                |                                        |
                |              64000                |
                |                                        |
                |                                        |
                |---------------------------|

Cada plano sendo de 64000 bytes.  Aqui está como isso funciona:

Um pixel em 0, 0 é mapeado no plano 0 no offset 0;
Um pixel em 1, 0 é mapeado no plano 1 no offset 0;
Um pixel em 2, 0 é mapeado no plano 2 no offset 0;
Um pixel em 3, 0 é mapeado no plano 3 no offset 0;
Um pixel em 4, 0 é mapeado no plano 0 no offset 1   ... e assim por diante...

Por causa de os pixels serem encadeados através dos 4 planos, é impossível usar múltiplas páginas no modo 13h sem ter que to usar uma tela virtual, ou algo do tipo.

O mapeamento automático dos pixels é feito todo pela placa de vídeo, de modo que você pode trabalhar de olhos fechados sem saber dos 4 planos de bits se você quiser.

Vamos ver como você pode contornar essa situação, entrando num modo especial, conhecido como Modo X, mais tarde, mas por enquanto, vamos só ver o que podemos fazer no velho modo 13h.


DESENHANDO LINHAS

Passamos um pouco da tamanho do tamanho que eu tinha planejado para este tutorial, e eu pretendo falar do Algoritmo de Retas de Bresenham, mas isso vai ter que esperar a semana que vem. No entanto, vou cobrir como desenhar um linha reta horzontal simples em Assembler.

Um Rotina em Assembler para Retas Horizontais:

Primeiramente vamos precisar de apontar ES para a VGA.

Isso deve resolver:

   MOV   AX, 0A000h
   MOV   ES, AX

Agora, precisaremos de ler os valores de X1, X2 e Y nos registradores, então algo assim deveria funcionar:

   MOV   AX, X1    ; AX é igual ao valor X1 agora
   MOV   BX, Y      ; BX é igual ao valor Y agora
   MOV   CX, X2    ; CX é igual ao valor X2 agora

Será preciso calcular o tamanho da linha, então vamos usar CX para guardar isso, sendo que:  i) CX já tem o valor de X2, e  ii) vamos usar uma instrução REP, que usará CX como contador.

   SUB   CX, AX    ; CX = X2 - X1

Agora vamos precisar de calcular o DI para o primeiro pixel que plotaremos, então vamos fazer o que fizemos na rotina de PutPixel:

   MOV   DI, AX      ; DI = X1
   MOV   DX, BX    ; DX = Y
   SHL   BX, 8         ; Shift Y à esquerda 8
   SHL   DX, 6         ; Shift Y à esquerda 6
   ADD   DX, BX    ; DX = Y SHL 8 + Y SHL 6
   ADD   DI, DX     ; DI = Y x 320 + X

Temos o offset do primeiro pixel agora, então tudo o que temos que fazer é colocar a cor que queremos desenhar em AL, e usar STOSB para plotar o resto da linha.

   MOV   AL, Cor   ; Move a cor a plotar em AL
   REP   STOSB      ; Plota CX pixels

Note que usamos STOSB porque ele vai incrementar DI para nós, assim economizando um monte de MOV's e INC's. Agora, dependendo de que linguagem você vai usar para implementar, você vai chegar a algo assim:

   void Draw_Horizontal_Line(int x1, int x2, int y, unsigned char color)
   {
   _asm
      {
      mov   ax, 0A000h
      mov   es, ax         ; Aponta ES para a VGA

      mov   ax, x1         ; AX = X1
      mov   bx, y          ; BX = Y
      mov   cx, x2         ; CX = X2

      sub   cx, ax         ; CX = Diferença de X2 e X1

      mov   di, ax         ; DI = X1
      mov   dx, bx         ; DX = Y
      shl   bx, 8          ; Y SHL 8
      shl   dx, 6          ; Y SHL 6
      add   dx, bx         ; DX = Y SHL 8 + Y SHL 6
      add   di, dx         ; DI = Offset do primeiro pixel

      mov   al, color     ; Põe a cor a plotar em AL
      rep   stosb          ; Desenha a linha
      }
   }


Agora já vimos como desenhar uma linha horizontal simples. A rotina acima não é cegamente rápida, mas não é de toda má. Só de mudar o cálculo da parte de DI como na PutPixel que eu dei no Tutorial Dois, já dobraria a velocidade desta rotina.

Minha própria rotina de linha horizontal á provavelmente cerca de 4 a 5 vezes mais rápida que esta, assim, no futuro, eu vou lhe mostrar como otimizar essa rotina por completo. Semana que vem vamos ver como pegar e acertar a palette, e como podemos desenhar círculos. Sinto muito se não fiz isso nesse tutorial, mas ele cresceu um pouco demais...

COISAS PARA FAZER:

   1) Escreva um rotina para linhas verticais baseada na rotina acima. Dica:
       Você precisa incrementar DI de 320 em algum lugar.

   2) Volte à lista das instruções de Assembler, e aprenda quantas puder.

   3) Dê uma olhda no Starfield que eu escrevi, e tente corrigir os bugs dele.
      Veja o que você pode fazer com ele.


Desculpem-me de novo se não incluí as coisas que eu disse que ia escrever na semana passada, mas como eu disse, o tutorial simplesmente cresceu, e eu estou um pouco atrasado com uns projetos em que eu devia estar trabalhando.

No próximo tutorial vamos ver:

Se você deseja ver um tópico discutido num tutorial no futuro, escreva-me, e  eu vou ver o que eu posso fazer.


Não perca!!! Baixe o tutorial da próxima semana na minha homepage:

Vejo vocês na próxima semana!

- Adam.
- Renato Nunes Bastos