domingo, 15 de julho de 2012

Adaptação de jogos de fita para Beta 48 (parte 5)

Dando sequência a esta série, eu estarei mostrando como adaptar jogos de fitas protegidas para disquetes Beta 48. Um conhecimento de código de máquina e assembly Z80 será requisito obrigatório no acompanhamento desta parte. Os comandos BASIC para carregar programas e dados da fita cassete podem ser facilmente pirateados, por isso os desenvolvedores de jogos foram criando diferentes esquemas de proteção. 

Para cada dado salvo através do SAVE do BASIC, existem dois blocos de dados armazenados na fita, separados por um pequeno intervalo de tempo. O primeiro bloco, chamado de cabeçalho (header), contém dados como tipo (BASIC, DATA ou CODE), comprimento e outros dados dependendo do que está gravado. O bloco seguinte contém os dados propriamente ditos que serão lidos e armazenados na RAM (bloco de dados). Tanto um bloco de cabeçalho como de dados são compostos por um byte leader, seguido dos bytes a serem carregados e, por fim, um byte de paridade. Por exemplo, um bloco de 6912 bytes estará armazenado na fita como uma sequência de 6914 bytes. O byte leader, que é o primeiro, não é carregado na RAM mas simplesmente conferido por especificar o tipo de bloco (0 para cabeçalho e 255 para dados). O 2º ao 6913º bytes são lidos e carregados de fato na memória. Por fim o último byte, de paridade, não é gravado na RAM, mas serve simplesmente para detectar algum eventual erro que possa ter ocorrido. Maiores detalhes podem ser vistos neste artigo do TK-WIKI. 

Como o BASIC só admite gravações em fita conforme descrito acima, basta alterar qualquer um desses detalhes na gravação que tornará o bloco irreconhecível a não ser por linguagem de máquina. O Cybernoid II será usado como exemplo para ilustrar esta técnica. 


Foi utilizado o lado 2 da fita deste jogo, pois o intuito era adaptar a versão com som para Explorer (vide Google Drive ou 4 Shared), cujo arquivo imagem em formato TZX é "Cybernoid 2 - Side 2.tzx". Neste jogo há dois blocos sem cabeçalhos (headerless) que não são reconhecidos por um LOAD comum do BASIC.

A listagem do programa carregador em BASIC da fita, que pode ser obtida com as técnicas já apresentadas, tem o seguinte conteúdo:

  10 CLEAR 24999
  20 PAPER 0: BORDER 0: INK 6
  30 CLS : LET a=PEEK (14446)
  40 IF a=255 THEN GO SUB 120
  50 PRINT : PRINT : PRINT
  60 PRINT " NOTE THE SELECTION KEY CHANGES"
  70 PRINT : PRINT
  80 PRINT "        Y = SMART BOMB"
  90 PRINT "        U = TRACERS"
 100 LOAD "cyber"CODE
 110 RANDOMIZE USR 25000
 120 PRINT : PRINT : PRINT
 130 PRINT "    THIS IS THE 128K VERSION"
 140 PRINT
 150 PRINT "  PLEASE USE OTHER SIDE OF TAPE"
 160 RETURN

Na linha 100 um programa em código de máquina é carregada e executada pela linha 110, via USR 25000. O disassembly do código de máquina produz:

25000   DI              ;Desabilita interrupção.
25001   LD IX,32768     ;Carrega 6912 bytes em 32768.
25005   LD DE,6912
25008   CALL 25036

A sub-rotina em 25036 é responsável pelo carregamento de um bloco de bytes mas, antes de analisá-la, é interessante conhecer a sub-rotina da ROM do TK90X no endereço 1366 ou #0556, conhecida como LD-BYTES, que serve para carregar um bloco da fita para a memória. O registrador IX deve conter o endereço inicial da RAM onde os dados serão gravados, DE deve especificar o número de bytes a carregar, A deve conter o byte leader (normalmente 0 ou 255, mas aceita qualquer valor) e o flag CARRY deve ser 1. O livro "The Complete Spectrum ROM Disassembly" contém a listagem desta sub-rotina, da qual apresento a parte inicial:

0556 LD-BYTES     INC   D                   This resets the zero flag. (D
                                            cannot hold +FF.)
                  EX    AF,A'F'             The A register holds +00 for a
                                            header and +FF for a block of
                                            data.
                                            The carry flag is reset for
                                            VERIFYing and set for
                                            LOADing.
                  DEC   D                   Restore D to its original value.

                  DI                        The maskable interrupt is now
                                            disabled.
                  LD    A,+0F               The border is made WHITE.
                  OUT   (+FE),A
                  LD    HL,+053F            Preload the machine stack
                  PUSH  HL                  with the address - SA/LD-RET.
                  IN    A,(+FE)             Make an initial read of port '254'
                  RRA                       Rotate the byte obtained but
                  AND   +20                 keep only the EAR bit,
                  OR    +02                 Signal 'RED' border.
                  LD    C,A                 Store the value in the C register.
                                            (+22 for 'off' and +02 for 'on'
                                            - the present EAR state.)
                  CP    A                   Set the zero flag.

Não é necessário tentar entender a sub-rotina LD-BYTES, porém é interessante guardar na memória algumas instruções iniciais, pois ocorrem em vários esquemas de fitas protegidas. No nosso exemplo, examinando o disassembly da sub-rotina abaixo:

25036   LD A,255        ; Valor do byte leader é 255.
25038   SCF             ; Levanta flag CARRY.
25039   INC D           ; Rotina semelhante ao LD-BYTES.
25040   EX AF,AF'
25041   DEC D
25042   LD A,15
25044   OUT (254),A
25046   JP 1378         ; Continua na ROM o carregamento da fita.

Fica claro então que tal sub-rotina faz carregamento de um bloco da fita para a RAM. Voltando para o disassembly a partir de 25000:

25000   DI              ; Desabilita interrupção.
25001   LD IX,32768     ; Define a carga de 6912 bytes em 32768.
25005   LD DE,6912
25008   CALL 25036      ; Carrega o bloco.
25011   LD HL,32768     ; Copia os bytes carregados em 32768 para a
25014   LD DE,16384     ;RAM de vídeo em 16384.
25017   LD BC,6912
25020   LDIR
25022   LD IX,25344     ; Define a carga de 40191 bytes em 25344.
25026   LD DE,40191
25029   LD HL,25344     ; Endereço para iniciar o jogo que é guardado
25032   PUSH HL         ;na pilha do Z80.
25033   JP 25036        ; Carrega o bloco. 

O trecho acima faz o carregamento da tela a partir do endereço 32768, transfere a tela para o 16384 para assim ser exibida na TV, carrega o código de máquina a partir de 25344 e, por fim, roda o jogo a partir do endereço 25344.

Para carregar os dois blocos, deve-se empregar programa em assembly. Uma possibilidade é usar o programa abaixo para carregar a tela:

    ORG 25000
    LD IX,32768
    LD DE,6912
    LD A,255
    SCF
    JP 1366

Monte o programa acima num assembler, rode-o e carregue a tela na RAM. Depois, no DOS, para salvar a tela use o comando: SAVE "CYBER2$"CODE 32768,6912.

Faça a mesma coisa com o bloco de código de máquina, porém com o seguinte programa de carregamento:

    ORG 25000
    LD IX,25344
    LD DE,40191
    LD A,255
    SCF
    JP 1366

No DOS, use para salvar no disco o comando: SAVE "CYBER2C"CODE 25344,40191.

Agora só falta elaborar um programa de carregamento em BASIC (listagem abaixo) e salvar no disco.

10 PAPER 0: BORDER 0: INK 6: CLEAR 24999
20 RAND USR 15363: REM : LOAD "CYBER2$"CODE 16384
30 RAND USR 15363: REM : LOAD "CYBER2C"CODE
40 RAND USR 25344

Assim, a adaptação estará pronta. Do jeito que foi descrito, houve a necessidade de montar programas em assembly para poder carregar os blocos. Existe uma outra forma que faz uso do debugger num emulador. Trata-se de um método muito mais cômodo, embora não possa ser realizado num TK90X real. Entretanto hoje em dia não vejo motivos para insistir em adaptar programas fora de um ambiente de PC moderno. 

4 comentários:

  1. Muito legal!
    Outro dia usei um utilitário do WoS (o writetrd) que grava TAP direto pra um TRD e gerou um arquivo headerless... eu não sabia como ler. Agora acho que consigo. Vou tentar. Muito elucidativo! Obrigado novamente.
    Flavio, vc conhece o WRITETRD?

    ResponderExcluir
    Respostas
    1. Eu nunca usei o WRITETRD, pois normalmente abro um TAP ou TZX no emulador Fuse e copio o conteúdo para o TRD na mão (isto é, usando comandos TR-DOS). Se você conseguir um resultado interessante, pode relatar aqui, o espaço está aberto.

      Excluir
  2. Parabéns pela iniciativa, estou acompanhando!

    ResponderExcluir

Seu comentário é bem vindo, mas peço que use este espaço adequadamente.