Telas do speccy no MSX – parte 3

tbzscmcb-3_abertura

Depois do hiato da semana passada segue continuação da série sobre a construção de um visualizador de telas do ZX Spectrum para MSX-DOS. Só para recordar a primeira parte tratou da organização da memória de vídeo do speccy, das diferenças dela com a do MSX e de como compensar as tonalidade das cores. Na segunda parte foi sobre como reorganizar os dados do arquivo SCR para apresentá-los na VRAM do MSX e, claro, como fazer a carga dele usando diretamente as rotinas da BDOS.

Há também uma parte complementar com um pequeno programa em Python que converte telas do ZX para dumps da VRAM em arquivos binários do MSX.

Nesta parte, conforme foi prometido, é a recuperação e tratamento dos argumentos passados pela linha de comando do MSX-DOS e como o assunto é meio chato tentarei ser breve — eu juro 😀

A linha de comando

O MSX-DOS armazena diversas informações de ambiente nos primeiros 256 bytes da memória, a página zero¹, lá estão algumas variáveis do sistema operacional, os pontos de chamadas para diversas funções e, claro, o o resto da linha de comando (tudo aquilo depois do nome do programa) inserida pelo usuário a partir do prompt no COMMAND.COM.

O número de caracteres que foram digitados é armazenado no endereço 0x0080 enquanto que o texto em si está entre 0x0081 e 0x00FF (só lembrando que os programas começam em 0x0100). Tecnicamente isto significa que a linha de comando tem no máximo 127 caracteres mas na prática ela é composta de “somente” 126 caracteres já que um caractere de espaço é automaticamente inserido entre o nome do programa e o resto da linha.

(¹) Que é o mesmo comportamento do CP/M no qual ele foi baseado porém é importante ressaltar que o MSX-DOS não é um clone CP/M! Algumas funcionalidades, mas não todas, estão implementadas da mesma forma e no mesmo lugar — hoje seria como dizer que “usa a mesma API” — e isto serve para torná-lo compatível mas não necessariamente igual.

Recuperando a linha de comando

Infelizmente não há uma função dentro do MSX-DOS que interprete e retorne de modo automático os argumentos passados na linha de comando, como acontece como acontece com argc e **argv em C.

Assim a solução é trabalhar com as informações que o sistema operacional disponibiliza para fazer algumas experiências.


;
; Visualiza a 'tail' dos comandos do MSX-DOS (e CP/M)
;
ASCII_LF: equ 10 ; avanço de linha ("line feed")
ASCII_CR: equ 13 ; retorno de carro ("carriage return")
BOOT: equ $00 ; rotina Boot()
PUTCHAR: equ $02 ; rotina PutChar()
PUTSTR: equ $09 ; rotina PutString()
zpArgvLen: equ $0080 ; tamanho do buffer de argumentos
zpArgvStr: equ $0081 ; início da string com os argumentos
macro ____bdos,BDOS
ld c,BDOS
call $0005 ; chamada à BDOS em MSX-DOS
endm
macro ____print,str ; macro que simula PRINT "str"
proc
local _jump
local _string
ld de,_string
____bdos PUTSTR
jp _jump
_string:
db str,ASCII_CR,ASCII_LF,"$"
_jump:
endp
endm
org 0x0100
____print "View the command line tail"
ld a,(zpArgvLen) ; lê o tamanho da string de argumentos
cp 0 ; verifica se é igual a zero
jr nz,hasTail ; se >0 vai para 'hasTail'
____print "*** No arguments!" ; avisa que não há argumentos
jp programEnd ; e sai do programa
hasTail:
____print "Command tail:" ; mensagem de que há argumentos
ld hl,zpArgvLen ; 'HL' com a posição do tamanho
ld b,(hl) ; lê novamente o tamanho da string
inc hl ; 'HL' para o começo da string
printLoop:
ld e,(hl) ; lê um caracter da string
push hl ; salva HL na pilha
push bc ; salva BC na pilha
____bdos PUTCHAR
pop bc ; restaura BC da pilha
pop hl ; restaura HL da pilha
inc hl ; incrementa 'HL'
djnz printLoop ; se 'B' < 0 faz o laço
____print "<End>" ; assinala o final da string
programEnd:
____bdos BOOT ; retorna ao MSX-DOS (ou CP/M)

view raw

viewArgs.asm

hosted with ❤ by GitHub

Monte com:

$ pasmo -d -v viewArgs.asm viewargs.com

O que faz? Simples, verifica se foi inserido algo após o nome do programa lendo o valor do endereço 0x0080 e sendo ele maior que zero imprime o conteúdo.

Apesar de feito para rodar no MSX-DOS este programa foi intencionalmente escrito para ser compatível com implementações de CP/M em computadores Z80² por um simples motivo:

tbzscmcb-3_cpm

No CP/M — via emulador rodando dentro no DOSEMU, ufa! — há uma diferença interessante em que a linha é totalmente convertida para caixa alta. Aliás, eu não dissera que MSX-DOS ≠ CP/M? 🙂

(²) Só lembrando que Z80 e 8080 compartilham um conjunto comum de instruções mas não de mnemônicos.

Interpretando argumentos

Então, a partir da estrutura do primeiro programa, é possível criar um interpretador de argumentos passados pela linha de comando mas antes de fazê-lo é necessário considerar algumas premissas importantes:

  1. Os argumentos são tudo aquilo que segue o nome do comando até o limite dos 127 126 caracteres;
  2. O primeiro argumento será sempre um nome/máscara de arquivo;
    • Um nome/máscara de arquivo terá no máximo 12 caracteres³ — até 8 caracteres para o nome, até 3 para extensão e um “.” a separá-los;
  3. Os demais argumentos serão parâmetros indicados pelo caractere de barra (“/”);
    • Os parâmetros usarão letras — /a, /b, …, /z — ou números — /0, /1, …, /9 — e
    • Terão o tamanho máximo de um caractere.
  4. Os parâmetros serão sensíveis à caixa das letras, ou seja, serão case sensitive — desculpe CP/M.

Assim:


;
; Interpreta a 'tail' dos comandos do MSX-DOS (e CP/M)
;
ASCII_LF: equ 10 ; avanço de linha ("line feed")
ASCII_CR: equ 13 ; retorno de carro ("carriage return")
BOOT: equ $00 ; rotina Boot()
PUTCHAR: equ $02 ; rotina PutChar()
PUTSTR: equ $09 ; rotina PutString()
zpArgvLen: equ $0080 ; tamanho do buffer de argumentos
zpArgvStr: equ $0081+1 ; início da string com os argumentos (corrigido)
macro ____bdos,BDOS
ld c,BDOS
call $0005 ; faz a chamada à BDOS
endm
macro ____print,str ; macro que simula PRINT "str"
proc
local _jump
local _string
ld de,_string
____bdos PUTSTR
jp _jump
_string:
db str,ASCII_CR,ASCII_LF,"$"
_jump:
endp
endm
org 0x0100
____print "Test command line tail"
ld a,(zpArgvLen) ; Lê o conteúdo do endereço $80 da Zero page
cp 0 ; verifica se é zero
jr nz,hasTail ; se >0 vai para 'hasTail'
____print "*** No arguments!" ; avisa que não há argumentos
jp endProgram ; e sai do programa
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
macro ____test,param,option ; chama a rotina de lookup
ld c,param
call lookup
jp z,option
endm
hasTail:
ld hl,zpArgvLen ; inicio da string
call getFilename ; recupera o nome do arquivo (até 8 caracteres)
ld (paramIdx),hl ; guarda o início dos parâmetros
ld a,(zpArgvLen) ; tamanho da string
sub c ; retira o que foi utilizado
ld (paramLen),a ; guarda este novo tamanho
____test "H", printHelp1 ; Busca por "/H"
____test "h", printHelp2 ; Busca por "/h"
ld a,(filename) ; foi recuperado um nome de arquivo?
cp "$"
jr z,endProgram ; sai se não for inserido nome de arquivo
ld de,filename
____bdos PUTSTR ; imprime o nome de arquivo
jr endProgram ; sai do programa
printError:
____print "Ilegal parameter" ; caso os parâmetros não sejam entendidos
endProgram:
____bdos BOOT ; retorna ao MSX-DOS (ou CP/M)
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
printHelp1:
ld de,mensa1 ; mensagem 1
jr printHelp
printHelp2:
ld de,mensa2 ; mensagem 2
printHelp:
____bdos PUTSTR ; imprime a mensagem na tela
jr endProgram ; e sai do programa
mensa1:
db "Use:",ASCII_CR,ASCII_LF
db "testargs <filename> [/H] [/h]",ASCII_CR,ASCII_LF
db "Parameters:",ASCII_CR,ASCII_LF
db " /H : This help",ASCII_CR,ASCII_LF
db " /h : Another help",ASCII_CR,ASCII_LF,"$"
mensa2:
db "Use:",ASCII_CR,ASCII_LF
db "testargs <filename> [/H] [/h]",ASCII_CR,ASCII_LF
db "Parameters:",ASCII_CR,ASCII_LF
db " /H : Another help",ASCII_CR,ASCII_LF
db " /h : This help",ASCII_CR,ASCII_LF,"$"
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
macro ____abort,char
cp char
jr z,endLoop
endm
getFilename:
proc
local loop
local endLoop
ld b,12 ; tamanho máximo da string
ld c,1 ; começa com 1, não 0
ld de,filename ; variável com o nome do arquivo
ld hl,zpArgvStr ; 'HL' no começo da string
loop:
ld a,(hl) ; lê um caracter
____abort "/" ; sai da rotina se for "/"
____abort " " ; sai da rotina se for " "
ld (de),a ; copia o caracter para a variável
inc c ; contador de caracteres
inc de ; incrementa o ponteiro de destino
inc hl ; incrementa o ponteiro de origem
djnz loop ; enquanto 'B'>0 faz o laço
endLoop:
ret ; sai da rotina
endp
paramIdx: dw 0
paramLen: db 0
filename: ds 13,"$" ; só servirá para imprimir, pode ser "$"
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
lookup:
proc
local command
local endLoop
local switch
ld a,(paramLen) ; 'A' com o novo tamanho da string
ld b,a ; 'B' com o valor em 'A'
ld ix,(paramIdx) ; 'IX' começa depois do nome do arquivo
switch:
ld a,(ix) ; lê um caracter da string
cp "/" ; procura pelo início de um parâmetro
jr z,command ; é "/" verifica se há um comando
inc ix ; incrementa 'IX'
djnz switch ; enquanto B>0 faz o laço
jr endLoop ; vai para o final da rotina
command:
ld a,(ix+1) ; lê o caracter seguinte
cp c ; compara com 'C'
ret z ; sai se for zero
inc ix ; incrementa 'IX'
djnz switch ; enquanto B>0 faz o laço
endLoop:
neg ; nega 'A' (força "NÃO ZERO")
ret ; sai da rotina
endp

view raw

testArgs.asm

hosted with ❤ by GitHub

Para montar use:

$ pasmo -d -v testArgs.asm testargs.com

Este programa é só um exemplo, ele identifica um nome de arquivo e a presença dos parâmetros “/H” ou “/h” para exibir a mensagem de ajuda. Estes parâmetros são excludentes (é um ou o outro — no caso é o primeiro a ser utilizado) mas esta é uma característica do programa e não desta rotina.

(³) Neste momento estou desconsiderando a compatibilidade com a versão 2.x do MSX-DOS que tem suporte a subdiretórios.

Finalizando esta parte

Deixei disponível uma imagem de disco com estes dois programas já montados para quem quiser se divertir um pouco. E agora com todas as rotinas prontas é possível juntar tudo para, finalmente, criar a ferramenta. Aliás eu acho que deveria ter escrito o visualizador em SDCC e não diretamente em Assembly, mas agora é tarde… 🙂

Até!

Um comentário sobre “Telas do speccy no MSX – parte 3

  1. Pingback: Argumentos da linha de comando com docopt | giovannireisnunes

Os comentários estão desativados.