Desta vez eu estou apresentando o esquema de proteção Speedlock 1, na adaptação do jogo Highway Encounter. A série Speedlock é bastante famosa e empregada em numerosos jogos.
A última linha (65417) só contém sujeiras e causará erro de sintaxe. Há uma série de linhas com POKE, mas são distrações, o que realmente importa é a 4ª linha que envolve a variável de sistema P_ERR ou ERR_SP (23613-23614). Esta variável aponta para o elemento da pilha do Z80 com endereço de retorno em caso de erro o BASIC que, neste caso, é alterado para coincidir com o início da área de variáveis BASIC (apontado por VARADD ou VARS, em 23627-23628). Em outras palavras, quando houver um erro no BASIC (causado pela linha 65417), será executada o código de máquina no endereço dado por:
que, no caso, é 24904.
Fazendo disassembly nesse endereço:
observa-se uma sequência de instruções que não fazem muito sentido. O papel delas é confundir a interpretação da listagem.
Descodificadores
O que se pode fazer no emulador é colocar um breakpoint em 24094 e ir executando passo a passo o código de máquina para ver o que acontece com os valores dos registradores (no Fuse, use o botão 'Single Step'). Em certo ponto, acaba-se deparando com um decodificador:
Esta rotina é um laço (loop) que não é implementado com desvios (JP ou JR), mas com engenhosa manipulação de RET e da pilha do Z80. A instrução LDI transfere um byte do endereço apontado por HL para o apontado por DE, seguido da diminuição de BC por 1. Se o BC for 0, o flag P/V será 0 e o RET em 25064 será acionado e a execução salta para endereço apontado por SP; caso contrário, como SP é diminuído em 2, o RET em 25067 é acionado e a execução salta para endereço apontado por SP-2.
Quando a execução é interrompida em 25058, os valores dos registradores são: HL=24317, DE=60522 e BC=587. Significa que 587 bytes são transferidos decodificados a partir de 24317 para 60522.
Para saber os destinos dos desvios via RET, deve-se examinar a pilha de máquina. No caso do Fuse, pode-se usar o Memory broser, ou ainda executar o código de máquina passo a passo. Verifica-se que SP=65364, (SP)=60522 e (SP-2)=25058, portanto a rotina entre 25058 a 25067 é um laço que irá sair para 60522.
Agora basta definir um breakpoint em 60522 e prosseguir. O disassembly resultante mostrará um trecho com instruções sem muito sentido e, a partir de 60558, o mesmo decodificador que listado acima. Para contorná-lo, basta usar a mesma técnica para chegar ao endereço 60568. A partir deste endereço há mais um decodificador, porém é convencional pois usa instruções JP para executar desvios. Analisando a listagem, acaba-se chegando ao trecho que realmente faz o carregamento da fita:
Resumindo, a rotina acima carrega da fita um bloco de 39168 bytes na memória a partir do endereço 16384. Se houver erro de carregamento, o computador é travado. Após carregamento bem sucedido, inicia-se o jogo em 45280.
Proteção com registro R
Na etapa anterior parece que as proteções foram quebradas, mas existe mais uma que diz respeito ao bit 7 do registrador R. Os 7 bits menos significativos (0 a 6) deste registrador é incrementado regularmente, para refrescar as DRAM. Entretanto o bit 7 permanece inalterado, a não ser que um valor seja atribuído pela instrução LD R,A. Normalmente este bit teria valor 0, mas a esta proteção faz com que seja verificado o valor que, se não for 1, trava o computador:
Portanto a rotina de desproteção deverá levantar o bit 7 de R antes de rodar o programa. Este tipo de proteção é encontrado em vários jogos. Se no debugger for apontado R maior que 128, há chances de que esta proteção seja usada.
Depois de desprotegido, o jogo pode ser facilmente adaptado para o disquete, pois há bastante espaço de RAM disponível. Basta salvar separadamente a tela e o bloco de códigos entre 23296-55551 no disco. Um carregador em linguagem de máquina deve se encarregar de mover o bloco para o local correto (pois ele tem que ser carregado em um lugar que não se sobreponha ao BASIC) com LDIR, levantar o bit 7 do registrador R e saltar para o início do jogo.
O procedimento de adaptação é explicado pormenorizadamente no documento contido neste arquivo disponível no Google Drive ou 4 Shared. Os arquivos Hobeta do Highway Encounter também estão lá.
A estrutura parcial do arquivo TZX, visto com TZX Show, é:
Block 1: text description
"Created with Ramsoft MakeTZX"
Block 2: header
Saved name: "H E "
Type: BASIC program
Auto-start line: 0
Program size without variables: 1149
Total length: 1530
Block 3: data bytes
Length: 1530
Block 4: group start
Group name: Speedlock 1 Block 1
Block 5: loop start
Repetitions: 7
Block 6: pure tone, 2165 pulses of 18 T-state period
Block 7: pulse sequences, 714 T, 714 T
Block 8: pure tone, 2165 pulses of 18 T-state period
Block 9: pulse sequences, 714 T, 714 T
...
Carregador BASIC
O carregador BASIC é relativamente grande, devido à presença de código de máquina. Este carregador tem a listagem camuflada e deve ser examinado pelo Basic Lister. A listagem real torna-se:
O carregador BASIC é relativamente grande, devido à presença de código de máquina. Este carregador tem a listagem camuflada e deve ser examinado pelo Basic Lister. A listagem real torna-se:
0 REM Protected by SPEEDLOCK
0 BORDER 0: PAPER 0: INK 0: BRIGHT 1: CLS : POKE 23624,0
0 POKE (PEEK 23641+256*PEEK 23642),PEEK 23649: POKE (PEEK 23641+256*PEEK 23642)+1,PEEK 23650
0 POKE (PEEK 23613+256*PEEK 23614),PEEK 23627: POKE (PEEK 23613+256*PEEK 23614)+1,PEEK 23628
0 POKE PEEK 23662,PEEK 23618: POKE 23663,PEEK 23619: POKE 23664,PEEK 23621
65417 HSGN SQR STR$ ...
A última linha (65417) só contém sujeiras e causará erro de sintaxe. Há uma série de linhas com POKE, mas são distrações, o que realmente importa é a 4ª linha que envolve a variável de sistema P_ERR ou ERR_SP (23613-23614). Esta variável aponta para o elemento da pilha do Z80 com endereço de retorno em caso de erro o BASIC que, neste caso, é alterado para coincidir com o início da área de variáveis BASIC (apontado por VARADD ou VARS, em 23627-23628). Em outras palavras, quando houver um erro no BASIC (causado pela linha 65417), será executada o código de máquina no endereço dado por:
PEEK 23627+256*PEEK 23628
que, no caso, é 24904.
Fazendo disassembly nesse endereço:
24904 LD L,L
LD B,L
LD B,B
... ...
observa-se uma sequência de instruções que não fazem muito sentido. O papel delas é confundir a interpretação da listagem.
Descodificadores
O que se pode fazer no emulador é colocar um breakpoint em 24094 e ir executando passo a passo o código de máquina para ver o que acontece com os valores dos registradores (no Fuse, use o botão 'Single Step'). Em certo ponto, acaba-se deparando com um decodificador:
25058 LD A,R ; Altera byte no endereço apontado
25060 XOR (HL) ;por HL fazendo XOR com conteúdo do
25061 LD (HL),A ;registrador R.
25062 LDI ; Copia byte de (HL) para (DE) e diminui BC.
25064 RET PO ; Salta para (SP) se BC=0,
25065 DEC SP ;senão salta para (SP-2)
25066 DEC SP
25067 RET PE
Esta rotina é um laço (loop) que não é implementado com desvios (JP ou JR), mas com engenhosa manipulação de RET e da pilha do Z80. A instrução LDI transfere um byte do endereço apontado por HL para o apontado por DE, seguido da diminuição de BC por 1. Se o BC for 0, o flag P/V será 0 e o RET em 25064 será acionado e a execução salta para endereço apontado por SP; caso contrário, como SP é diminuído em 2, o RET em 25067 é acionado e a execução salta para endereço apontado por SP-2.
Quando a execução é interrompida em 25058, os valores dos registradores são: HL=24317, DE=60522 e BC=587. Significa que 587 bytes são transferidos decodificados a partir de 24317 para 60522.
Para saber os destinos dos desvios via RET, deve-se examinar a pilha de máquina. No caso do Fuse, pode-se usar o Memory broser, ou ainda executar o código de máquina passo a passo. Verifica-se que SP=65364, (SP)=60522 e (SP-2)=25058, portanto a rotina entre 25058 a 25067 é um laço que irá sair para 60522.
Agora basta definir um breakpoint em 60522 e prosseguir. O disassembly resultante mostrará um trecho com instruções sem muito sentido e, a partir de 60558, o mesmo decodificador que listado acima. Para contorná-lo, basta usar a mesma técnica para chegar ao endereço 60568. A partir deste endereço há mais um decodificador, porém é convencional pois usa instruções JP para executar desvios. Analisando a listagem, acaba-se chegando ao trecho que realmente faz o carregamento da fita:
61049 LD IX,32768 ; Carrega header em 32768.
LD DE,17
CALL 60920
61059 LD IX,16384 ; Carrega bloco 16384,39168 (16384-55551).
LD DE,39168
CALL 60686
LD A,(61035) ; Se byte em 61035 não for 0, resulta crash.
CP 0
61074 JP NZ,60647
61077 LD HL,1343
LD DE,60659
LD BC,375
LDIR
JP 45280 ; Entrada para o jogo.
60647 LD IY,0 ; Entra em loop infinito e apaga a RAM.
60651 LD (IY+117),0
INC IY
JR 60651
Resumindo, a rotina acima carrega da fita um bloco de 39168 bytes na memória a partir do endereço 16384. Se houver erro de carregamento, o computador é travado. Após carregamento bem sucedido, inicia-se o jogo em 45280.
Proteção com registro R
Na etapa anterior parece que as proteções foram quebradas, mas existe mais uma que diz respeito ao bit 7 do registrador R. Os 7 bits menos significativos (0 a 6) deste registrador é incrementado regularmente, para refrescar as DRAM. Entretanto o bit 7 permanece inalterado, a não ser que um valor seja atribuído pela instrução LD R,A. Normalmente este bit teria valor 0, mas a esta proteção faz com que seja verificado o valor que, se não for 1, trava o computador:
37318 LD A,R ;Pega valor do registrado R
SLL A ;e coloca o bit 7 no CARRY.
LD HL,(36609)
LD A,(36631)
LD C,A
JR NC,37318 ;Se bit 7=0, entra em loop infinito.
Portanto a rotina de desproteção deverá levantar o bit 7 de R antes de rodar o programa. Este tipo de proteção é encontrado em vários jogos. Se no debugger for apontado R maior que 128, há chances de que esta proteção seja usada.
Adaptação para Beta 48
Depois de desprotegido, o jogo pode ser facilmente adaptado para o disquete, pois há bastante espaço de RAM disponível. Basta salvar separadamente a tela e o bloco de códigos entre 23296-55551 no disco. Um carregador em linguagem de máquina deve se encarregar de mover o bloco para o local correto (pois ele tem que ser carregado em um lugar que não se sobreponha ao BASIC) com LDIR, levantar o bit 7 do registrador R e saltar para o início do jogo.
O procedimento de adaptação é explicado pormenorizadamente no documento contido neste arquivo disponível no Google Drive ou 4 Shared. Os arquivos Hobeta do Highway Encounter também estão lá.
Fantástico Flávio! Queria entender de asm Z80 e programação como você... :-D Eu sempre vi sobres esses esquemas de proteção "Speedlock", usado em vários programas nos cassetes originais. Sei tem que vários (speedlock 1,2,3 etc.). Você pretende abordar todos eles, ou ao menos, alguns outros? Abraço!
ResponderExcluirTalvez eu aborde os demais Speedlocks, se encontrar algum jogo interessante que use tais esquemas. O Speedlock 2 é usado pelo Revolution (Vortex) e eu já quebrei essa proteção.
ExcluirUma dúvida, esses esquemas de proteção eram "copyrighted"? Digo, se uma determinada softhouse desejasse usar um esquema ela deveria pagar ao inventor ou eles eram livremente usados?
ResponderExcluirEu nunca pesquisei isto, mas presumo que havia licença sobre tais rotinas de proteção. Havia esquemas usadas por uma única softhouse, como o Bleeploader da Firebird, portanto esta companhia devia ser a detentora dos direitos.
ExcluirPor outro lado, não sei até que ponto a legislação sobre software estava desenvolvida naquela época.