Continuando com visualizador de telas de ZX-Spectrum no MSX e, como já faz algum muito tempo, é bom aproveitar para relembrar que a primeira parte tratou das diferenças entre os modos de vídeo dos dois computadores (mesma resolução mas uma organização completamente diferente), a segunda sobre como ler um arquivo do disco diretamente em assembly usando as rotinas da BDOS e a terceira sobre como recuperar e interpretar os argumentos da linha de comando do MSX-DOS.
Agora é a vez de tratar da parte mais importante de todo o programa, a rotina que faz a conversão da imagem do vídeo do speecy para a VRAM do MSX.
TL/DR; o código!
Para esta parte uma versão mais simples que pode ser montada e executada diretamente como uma imagem de cartucho.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; | |
; Converte uma tela de ZX-Spectrum para ser exibida em um MSX | |
; | |
; Para montar: | |
; pasmo -d -v zxview.asm zxview.rom | |
; | |
; Não esqueça de baixar a imagem, instruções lá no final! | |
; | |
; endereços da MSX-BIOS | |
DISSCR: equ $0041 | |
ENASCR: equ $0044 | |
INIGRP: equ $0072 | |
GRPCGP: equ $F3CB | |
GRPCOL: equ $F3C9 | |
WRTVDP: equ $0047 | |
WRTVRM: equ $004D | |
rom_size: equ 8192 | |
rom_area: equ $8000 | |
ram_area: equ $C000 | |
; variáveis usadas no programa | |
zx_ink: equ ram_area | |
zx_paper: equ ram_area+1 | |
org rom_area | |
db "AB" ; cabeçalho do cartucho | |
dw start_code ; endereço de execução | |
db "ZXVIEW" ; identificação => "ZXVIEW" | |
ds 6,0 ; mantenha em branco | |
start_code: | |
proc | |
call INIGRP ; inicializa a SCREEN 2 | |
call DISSCR ; desliga o vídeo | |
ld b,0 ; 'B' <- (cor transparente) | |
ld c,7 ; registrador 7, cor da borda | |
call WRTVDP ; muda a cor da borda | |
call decode_patterns ; traduz a tabela de padrões | |
call decode_attributes ; traduz a tabela de atributos | |
call ENASCR ; religa o vídeo | |
loop: | |
jr loop | |
endp | |
; | |
; decodifica a tabela de padrões diretamente na VRAM | |
; | |
decode_patterns: | |
proc | |
local __loop_tt | |
local __loop_lll | |
local __loop_rrr | |
local __loop_ccccc | |
; composição do vídeo do endereço de vídeo no speccy | |
; | |
; +——————-+——————-+ | |
; | H | L | | |
; +———+———+———+———+ | |
; | 0 0 0 T | T L L L | R R R C | C C C C | | |
; +———+———+———+———+ | |
; | |
ld de,scr_file ; 'DE' na tabela de padrões do speccy | |
ld hl,(GRPCGP) ; 'HL' no tabela de padrões da VRAM. | |
ld b,3 ; repetições do laço TT (×3) | |
__loop_tt: | |
push bc ; salva 'BC' atual | |
ld b,8 ; repetições do laço LLL (×8) | |
__loop_lll: | |
push bc ; salva 'BC' atual | |
push hl ; salva 'HL' atual | |
ld b,8 ; repetições do laço RRR (×8) | |
__loop_rrr: | |
push bc ; salva 'BC' atual | |
ld b,32 ; repetições do laço CCCCC (×32) | |
__loop_ccccc: | |
push bc ; salva 'BC' atual | |
ld a,(de) ; lê um byte da tela do speccy | |
call WRTVRM ; escreve na VRAM do MSX | |
inc de ; próximo byte da tela do speccy | |
ld bc,8 ; 'BC' <- 8 | |
add hl,bc ; avança a VRAM oito posições | |
pop bc ; recupera 'BC' | |
djnz __loop_ccccc ; repete o laço | |
pop bc ; recupera 'BC' | |
djnz __loop_rrr ; repete o laço | |
pop hl ; recupera 'HL' | |
inc hl ; avança a VRAM uma posição | |
pop bc ; recupera 'BC' | |
djnz __loop_lll ; repete o laço | |
ld bc,2040 ; 'BC' recebe 2040 | |
add hl,bc ; avaça a VRAM em 2040 posições | |
pop bc ; recupera 'BC' | |
djnz __loop_tt ; repete o laço | |
ret ; fim da rotina | |
endp | |
; | |
; decodifica a tabela de atributos diretamente na VRAM | |
; | |
decode_attributes: | |
proc | |
local __loop0 | |
local __loop1 | |
local __apply_bright | |
local __skip_bright | |
; tabela de atributos do speccy: | |
; | |
; +——-+——–+—-+—-+—-+—+—+—+ | |
; | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | |
; +——-+——–+—-+—-+—-+—+—+—+ | |
; | flash | bright | paper | ink | | |
; +——-+——–+————–+———–+ | |
; | |
; (esta versão ainda não trata o 'flash') | |
ld de,scr_file+6144 ; 'DE' na tabela de atributos do speccy | |
ld hl,(GRPCOL) ; 'HL' na tabela de atributos da VRAM. | |
ld bc,768 ; número de repetiçõpes do laço | |
__loop0: | |
push bc ; salva 'BC' atual | |
ld b,8 ; número de repetiçõpes do laço | |
__loop1: | |
push bc ; salva 'BC' atual | |
ld a,(de) ; lê um byte da tela do speccy | |
call extract_attr ; traduz os atributos | |
bit 6,a ; o 6º bit (brilho) está ligado? | |
jr z,__skip_bright ; se desligado, pula a conversão | |
__apply_bright: | |
ld a,1 ; 'A' <- 1 | |
and b ; 'A' <- 'A' and 'B' | |
cp 0 | |
jr z,__skip_bright ; se 'A' é 0, pula a conversão | |
ld c,8 ; 'C' <- 8 | |
ld a,(zx_ink) ; lê o valor de 'zx_ink' | |
or c ; 'A' <- 'A' or 'C' | |
ld (zx_ink),a ; atualiza o valor de 'zx_ink' | |
ld a,(zx_paper) ; lê o valor de 'zx_paper | |
or c ; 'A' <- 'A' or 'C' | |
ld (zx_paper),a ; atualiza o valor de 'zx_paper' | |
__skip_bright: | |
ld a,(zx_ink) ; lê o valor de 'ink' | |
call translate_color ; converte para a cor do MSX | |
rept 4 | |
sla b ; desloca 'B' para esquerda (×4) | |
endm | |
ld c,b ; 'C' <- (cor de frente) | |
ld a,(zx_paper) ; lê o valor de 'paper' | |
call translate_color ; converte para a cor do MSX | |
ld a,b ; 'A' <- (cor de fundo) | |
or c ; 'A' <- 'A' or (cor de frente) | |
call WRTVRM ; escreve na VRAM do MSX | |
inc hl ; avança a VRAM uma posição | |
pop bc ; recupera 'BC' | |
djnz __loop1 ; repete o laço | |
inc de ; próximo byte da tela do speccy | |
pop bc ; recupera 'BC' | |
dec bc ; decrementa o contador | |
ld a,b ; 'A' recebe 'B' | |
or c ; 'A' <- 'A' or 'C' | |
cp 0 ; 'A' é zero? | |
jr nz, __loop0 ; se 'A' != 0, repete o laço | |
ret ; fim da rotina | |
endp | |
; | |
; extrai os valores de INK e PAPER da tabela de atributos | |
; | |
extract_attr: | |
proc | |
push af ; salva 'AF' atual | |
ld i,a ; salva em 'I' o valor de 'A' | |
and $07 ; 'A' <- 'A' AND %00000111 | |
ld (zx_ink),a ; armazena o valor em 'zx_ink' | |
ld a,i ; recupera de 'I' o valor de 'A'' | |
and $38 ; 'A' <- 'A' AND %00111000 | |
rept 3 | |
srl a ; desloca 'B' para esquerda (×3) | |
endm | |
ld (zx_paper),a ; armazena o valor em 'zx_paper' | |
pop af ; recupera 'AF' | |
ret ; fim da rotina | |
endp | |
; | |
; traduz a cor do ZX Spectrum em sua cor de MSX | |
; | |
translate_color: | |
proc | |
local __loop | |
local __zx_colors | |
push hl ; salva 'HL' | |
ld hl,__zx_colors-1 ; aponta para a tabela de cores | |
ld b,a ; 'B' <- (cor a traduzir) | |
inc b ; decrementa 'B' | |
__loop: | |
inc hl ; incrementa o ponteiro | |
djnz __loop ; repete o laço | |
ld b,(hl) ; 'B' <- (cor traduzida) | |
pop hl ; recupera 'HL' | |
ret ; fim da rotina | |
__zx_colors: | |
db $1, $4, $6, $d, $c, $7, $a, $e | |
db $1, $5, $8, $9, $2, $f, $b, $f | |
endp | |
scr_file: | |
; este arquivo pode ser baixado diretamente do World of Spectrum em: | |
; https://www.worldofspectrum.org/pub/sinclair/screens/load/h/scr/HeadOverHeels.scr | |
incbin "HeadOverHeels.scr" | |
; ou substitua-o por qualquer imagem SCR de sua preferência. | |
rom_pad: | |
ds rom_size-(rom_pad-rom_area), 0 |
Só lembrando que a imagem (linha 256) não foi incluída mas que ela pode ser baixada do World of Spectrum — você pode usar qualquer outra, até mesmo a do Chase H.Q. :-D.
Explicando (um pouco) o código
Para a explicar o funcionamento do código vou recorrer ao protótipo em MSX-BASIC apresentado na segunda parte, este sujeito aqui:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
100 DEFINT A-Z:DIM L(15) | |
105 KEY OFF:COLOR 15,4,0:SCREEN 2 | |
110 FOR A=0 TO 15:READ L(A):NEXT A | |
115 OPEN "CHASEHQ.SCR" AS#1 LEN=1 | |
120 FIELD#1,1 AS E$ | |
125 FOR A=0 TO 6143 STEP 2048 | |
130 FOR B=0 TO 7 | |
135 FOR C=0 TO 2047 STEP 256 | |
140 FOR D=0 TO 255 STEP 8 | |
145 GET#1:VPOKE A+B+C+D,ASC(E$) | |
150 NEXT D,C,B,A | |
155 FOR A=8192 TO 14335 STEP 8 | |
160 GET #1:C=ASC(E$):D=(C AND &H38)\8:E=(C AND &H7):C=C AND &H40 | |
165 FOR B=0 TO 7 | |
170 IF C<>0 AND (B AND 1)=1 THEN F=8 ELSE F=0 | |
175 VPOKE A+B,L(F+E)*16+L(F+D) | |
180 NEXT B,A:CLOSE #1 | |
185 GOTO 185 | |
190 DATA 1,4,6,13,12,7,10,14,1,5,8,9,2,15,11,15 |
Motivo? Simples, a versão em assembler é quase que a transcrição direta deste programa.
Rotina “decode_patterns”
É a parte mais importante e corresponde ao trecho entre as linhas 125 e 150 do programa em BASIC. O conjunto de quatro laços TT, LLL, RRR e CCCCC se encarregam de fazer a leitura da imagem do speccy de forma sequencial (ponteiro no registrador DE) e também de incrementar o ponteiro da VRAM (ponteiro no registrador HL) para a posição correta daquele byte no MSX.
Uma curiosidade aqui é o fato do laço TT adicionar a HL 2040 e não 2048, isto ocorre pois ao terminar do laço LLL ela já foi deixado oito posições para frente pois os laços implementados aqui são todos “do «ações» while «condição»”.
Rotinas “decode_attributes”, “extract_attr” e “translate_color”
Correspondem às linhas de 155 a 185 e se encarregam de recuperar o byte de atributo, traduzir as cores para as usadas pelo MSX e, claro, fazer a alternância de cores em linhas pares e ímpares para simular o atributo de bright.
A rotina “decode_attributes” se encarrega de cuidar dos dois laços, um que recupera cada byte das 768 posições da tabela de atributos do speccy e o outro que repete esta informação para cada bloco de 8 bytes da tabela de atributos do MSX.
A “extract_attr” é quase que integralmente a linha 150 — a exceção é o comando GET já que não estou carregado de um arquivo — enquanto que “translate_color” substitui o cálculo do VPOKE usado na linha 175.
Finalizando esta parte
Relembrando que por ser quase uma transcrição da versão em MSX-BASIC e, apesar de infinitamente mais rápida que esta, ela pode receber alguma otimização mas preferi mantê-la desta forma para ajudar na comparação entre os dois códigos, algo que melhoraria em muito a performance seria utilizar um buffer para fazer a conversão em RAM onde o acesso é muito mais rápido para fazer a cópia para a VRAM.
E isto (quase) conclui a construção do programa que permite visualizar telas do speccy em MSX-DOS está quase concluído! E que falta? Simples, juntar todas as peças que foram desenvolvidas separadamente e será este o assunto da próxima parte.¹
Até!
(¹) E prometo não demorar quase três anos para entregá-la desta vez! 😀