Hooks


  Um hook é um local da memória onde alguns programas contidos na BIOS ou no interpretador Basic fazem referência, de forma a permitir que um outro programa seja disparado antes de começar uma determinada ação. Esse mecanismo é similar aos triggers de um sistema gerenciador de banco de dados (SGBD).
  Assim como nos SGBDs, algumas sub-rotinas possuem um hook específico para ela, que permite ao programador desviar o curso de execução daquelas sub-rotinas para que uma rotina em linguagem de máquina sua (ou da ROM) faça algo antes delas.
  O trecho de memória da área de trabalho do sistema de &HFD9A até &HFFC9 contém 112 hooks, cada uma deles preenchido no arranque do sistema com 5 bytes contendo a instrução do Z-80 RET (&HC9). Assim, no estado inicial, quando a sub-rotinas desviar para o endereço do hook, ela encontra a instrução RET e retorna ao ponto de chamada sem realizar qualquer ação extra.
  As figuras abaixo ilustram como funcionam os hooks do MSX.

Hook no estado normal Hook desviando para um programa

  No estado normal, uma determinada sub-rotina, que utiliza o mecanismo de hook, desvia sua execução para o endereço inicial de seu hook correspondente (figura da esquerda). Ao encontrar a instrução RET, ela retorna para a sub-rotina chamadora sem realizar qualquer outra ação. Quando modificamos o hook de forma a desviar a execução para um novo endereço contendo um programa nosso (figura da direita), ele irá executá-lo antes de prosseguir com a sub-rotina principal que chamou o hook.

  A ilustração a seguir mostra como estão estruturados os hooks na memória de trabalho.
+--------------++--------------++--------------++--------------+
|    Hook 1    ||    Hook 2    ||      ...     ||   Hook 112   |
+--+--+--+--+--++--+--+--+--++--+--+--+--+--+--++--+--+--+--+--+
|C9|C9|C9|C9|C9||C9|C9|C9|C9|C9||..|..|..|..|..||C9|C9|C9|C9|C9|
+--+--+--+--+--++--+--+--+--+--++--+--+--+--+--++--+--+--+--+--+

  Como cada hook tem apenas 5 bytes, temos somente espaço para colocar uma instrução de desvio para o nosso programa (gasta 3 bytes). Não podemos esquecer de deixar um comando RET para que a execução volte para a sub-rotina chamadora do hook.


  A lista completa de hooks [1]

  A seguir, a lista completa das funções do MSX que possuem desvio para os hooks. A primeira coluna contém o endereço do hook. A segunda e terceira contém a função chamadora e seu endereço na ROM. Por fim, a descrição da função chamadora.

Hook    Rotina    Descrição
---------------------------------
FD9AH HKEYI 0C4AH Controle de interrupção
FD9FH HTIMI 0C53H Controle de interrupção
FDA4H HCHPU 08C0H Rotina padrão CHPUT
FDA9H HDSPC 09E6H Cursor da tela
FDAEH HERAC 0A33H Cursor de apagar
FDB3H HDSPF 0B2BH Rotina padrão DSPFNK
FDB8H HERAF 0B15H Rotina padrão ERAFNK
FDBDH HTOTE 0842H Rotina padrão TOTEXT
FDC2H HCHGE 10CEH Rotina padrão CHGET
FDC7H HINIP 071EH Copy character set to VDP
FDCCH HKEYC 1025H Keyboard decoder
FDD1H HKYEA 0F10H Keyboard decoder
FDD6H HNMI  1398H NMI Rotina padrão
FDDBH HPINL 23BFH Rotina padrão PINLIN
FDE0H HQINL 23CCH Rotina padrão QINLIN
FDE5H HINLI 23D5H Rotina padrão INLIN
FDEAH HONGO 7810H "ON DEVICE GOSUB"
FDEFH HDSKO 7C16H "DSKO$"
FDF4H HSETS 7C1BH "SET"
FDF9H HNAME 7C20H "NAME"
FDFEH HKILL 7C25H "KILL"
FE03H HIPL  7C2AH "IPL"
FE08H HCOPY 7C2FH "COPY"
FE0DH HCMD  7C34H "CMD"
FE12H HDSKF 7C39H "DSKF"
FE17H HDSKI 7C3EH "DSKI$"
FE1CH HATTR 7C43H "ATTR$"
FE21H HLSET 7C48H "LSET"
FE26H HRSET 7C4DH "RSET"
FE2BH HFIEL 7C52H "FIELD"
FE30H HMKI$ 7C57H "MKI$"
FE35H HMKS$ 7C5CH "MKS$"
FE3AH HMKD$ 7C61H "MKD$"
FE3FH HCVI  7C66H "CVI"
FE44H HCVS  7C6BH "CVS"
FE49H HCVD  7C70H "CVD"
FE4EH HGETP 6A93H Locate FCB
FE53H HSETF 6AB3H Locate FCB
FE58H HNOFO 6AF6H "OPEN"
FE5DH HNULO 6B0FH "OPEN"
FE62H HNTFL 6B3BH Close I/O buffer 0
FE67H HMERG 6B63H "MERGE/LOAD"
FE6CH HSAVE 6BA6H "SAVE"
FE71H HBINS 6BCEH "SAVE"
FE76H HBINL 6BD4H "MERGE/LOAD"
FE7BH HFILE 6C2FH "FILES"
FE80H HDGET 6C3BH "GET/PUT"
FE85H HFILO 6C51H Saída sequencial
FE8AH HINDS 6C79H Saída sequencial
FE8FH HRSLF 6CD8H "INPUT$"
FE94H HSAVD 6D03H "LOC"
            6D14H "LOF"
            6D25H "EOF"
            6D39H "FPOS"
FE99H HLOC 6D0FH "LOC"
FE9EH HLOF 6D20H "LOF"
FEA3H HEOF 6D33H "EOF"
FEA8H HFPOS 6D43H "FPOS"
Hook    Rotina    Descrição
---------------------------------
FEADH HBAKU 6E36H "LINE INPUT#"
FEB2H HPARD 6F15H Parse device name
FEB7H HNODE 6F33H Parse device name
FEBCH HPOSD 6F37H Parse device name
FEC1H HDEVN       Esse hook não é usado
FEC6H HGEND 6F8FH I/O function dispatcher
FECBH HRUNC 629AH Run-clear
FED0H HCLEA 62A1H Run-clear
FED5H HLOPD 62AFH Run-clear
FEDAH HSTKE 62F0H Resseta a pilha
FEDFH HISFL 145FH Rotina padrão ISFLIO
FEE4H HOUTD 1B46H Rotina padrão OUTDO
FEE9H HCRDO 7328H CR,LF to OUTDO
FEEEH HDSKC 7374H Mainloop line input
FEF3H HDOGR 593CH Line draw
FEF8H HPRGE 4039H Fim do programa
FEFDH HERRP 40DCH Controle do erro
FF02H HERRF 40FDH Controle do erro
FF07H HREAD 4128H Mainloop "OK"
FF0CH HMAIN 4134H Mainloop
FF11H HDIRD 41A8H Mainloop direct statement
FF16H HFINI 4237H Mainloop finished
FF1BH HFINE 4247H Mainloop finished
FF20H HCRUN 42B9H Tokenize
FF25H HCRUS 4353H Tokenize
FF2AH HISRE 437CH Tokenize
FF2FH HNTFN 43A4H Tokenize
FF34H HNOTR 44EBH Tokenize
FF39H HSNGF 45D1H "FOR"
FF3EH HNEWS 4601H Runloop new statement
FF43H HGONE 4646H Runloop execute
FF48H HCHRG 4666H Rotina padrão CHRGTR
FF4DH HRETU 4821H "RETURN"
FF52H HPRTF 4A5EH "PRINT"
FF57H HCOMP 4A54H "PRINT"
FF5CH HFINP 4AFFH "PRINT"
FF61H HTRMN 4B4DH "READ/INPUT" error
FF66H HFRME 4C6DH Expression Evaluator
FF6BH HNTPL 4CA6H Expression Evaluator
FF70H HEVAL 4DD9H Factor Evaluator
FF75H HOKNO 4F2CH Factor Evaluator
FF7AH HFING 4F3EH Factor Evaluator
FF7FH HISMI 51C3H Runloop execute
FF84H HWIDT 51CCH "WIDTH"
FF89H HLIST 522EH "LIST"
FF8EH HBUFL 532DH Detokenize
FF93H HFRQI 543FH Converte para inteiro
FF98H HSCNE 5514H Ponteiro para a linha
FF9DH HFRET 67EEH Descritor livre
FFA2H HPTRG 5EA9H Busca variável
FFA7H HPHYD 148AH Rotina padrão PHYDIO
FFACH HFORM 148EH Rotina padrão FORMAT
FFB1H HERRO 406FH Controle de erro
FFB6H HLPTO 085DH Rotina padrão LPTOUT
FFBBH HLPTS 0884H Rotina padrão LPTSTT
FFC0H HSCRE 79CCH "SCREEN"
FFC5H HPLAY 73E5H Sentença "PLAY"



  Utilizando os hooks

  A seguir, será mostrado como utilizar o mecanismo de hooks.

  O comando Basic LIST possui um hook localizado em &HFF89, tendo os bytes de &HFF89 a &HFF8D reservados para ele.
  Podemos modificar esse hook de forma a desviar a execução dessa sub-rotina para a sub-rotina de limpeza de tela da BIOS (&H00C3). Assim, antes de listar um programa em Basic, o MSX irá limpar a tela.
  O programa em Assembly é listado a seguir.
CD C3 00	CALL &HC3
C9		RET

  A partir desse programa, podemos criar um programa em Basic e alterar o Hook.
10 POKE &HFF89,&HCD
20 POKE &HFF8A,&HC3
30 POKE &HFF8B,0

  A memória correspondente a esse hook é alterada para:
+--------------+
|    "LIST"    |
+--+--+--+--+--+
|CD|C3|00|C9|C9|
+--+--+--+--+--+
  Uma vez que a instrução RET já está no hook, não precisamos inserí-la ao final do programa em Basic.

  Experimente agora dar o comando LIST. A tela será limpa antes do MSX listar o programa.

  Para interromper uma ação do hook, basta alterar o primeiro byte dele de volta para &HC9. Assim ao darmos:
POKE &HFF89,&HC9
  O comando LIST pára de apagar a tela. Isto porque agora a sub-rotina retorna normalmente ao atingir o hook.

  Em vez de utilizar uma instrução CALL, podemos também uma instrução de desvio longo (JP - &HC3). É o que vamos ver no exemplo a seguir.

  No livro 50 dicas para MSX [2], um dos autores sugere utilizar o hook do teclado para emitir um BEEP (sub-rotina &H00C0 da ROM). Para isso, utilizamos o hook correspondente ao comando CHGET (&HFDC2), que espera uma tecla ser pressionada. Nesse caso, sempre que uma tecla for pressionada, o hook correspondente ao CHGET é chamado e a rotina do BEEP é executada. Vamos ao programa em Basic.
10 POKE &HFDC2,&HC3 : ' JP &HC3
20 POKE &HFDC2,&HC0
30 POKE &HFDC2,&H00
  O comando "POKE &HFDC2,&HC9" pára o BEEP.

  Nos dois exemplos apresentados, o hook somente é acionado quando a respectiva função é utilizada. Assim, no primeiro caso, a tela é apagada somente quando utilizamos o comando LIST, e no segundo, somente quando pressionamos uma tecla, o BEEP é executado pelo hook da função CHGET.

  No próximo exemplo, iremos utilizar um hook de uma função que é acionada constantemente pelo sistema, ou seja, uma interrupção. Dessa forma, podemos criar uma animação em um sprite, fazendo com que suas coordenadas sejam atualizadas por um programa que é chamado através do hook dessa interrupção.

  O programa a seguir for adaptado de [3] e faz com que uma bolinha passeie pela tela na screen 1, enquanto estamos livres para mover o cursor. No programa original, um sprite realiza movimentos em diagonal na screen 2 a espera do usuário teclar algo e deasabilitar o hook. Entretanto, achamos mais conveniente demonstrar o funcionamento do hook em segundo plano na screen 1, enquanto podemos realizar outras tarefas.

  Listagem em Assembly do movimento básico em Y:
			10	ORG &HE880
E880	21 00 1B	20	LD HL,&H1B00	; Endereço VRAM de Y
E883	CD 4A 00	30	CALL &H4A	; Lê VRAM
E886	47		40	LD B,A		; Salva em B
E887	3A 00 EA	50	LD A,(&HEA00)	; Carrega variável de incremento
E88A	80		60	ADD A,B		; Realiza soma
E88B	4F		70	LD C,A		; Salva em C
E88C	FE 00		80	CP 0		; Verifica se igual a 0
E88E	28 04		90	JR Z,INV	; Se sim, inverte variável de incremento
E890	FE FF		100	CP &HB8		; Verifica se igual a B8
E892	20 08		110	JR NZ,NXT	; Senão continua
E894	3A 00 EA	120 INV:LD A,(&HEA00)	; Carrega valor da variável atual
E897	ED 44		130	NEG		; Inverte sinal
E899	32 00 EA	140	LD (&HEA00),A	; Salva na memória
E89C	79		150 NXT:LD A,C		; Retoma valor salvo em C
E89D	CD 4D 00	160	CALL &H4D	; Atualiza na VRAM
E8A0	C9		170	RET		; Retona

  Listagem em Basic do programa final.
10 CLEAR 200,&HE880
20 SCREEN 1,0
30 FOR A=&HE880 TO &HE8C0
40 READ A$
50 POKE A,VAL("&H"+A$)
60 NEXT A
70 ' Movimento em Y
80 DATA 21,00,1B,CD,4A,00,47,3A,00,EA,80
90 DATA 4F,FE,00,28,04,FE,B8,20,08,3A,00
100 DATA EA,ED,44,32,00,EA,79,CD,4D,00
110 ' Movimento em X
120 DATA 21,01,1B,CD,4A,00,47,3A,01,EA,80
130 DATA 4F,FE,00,28,04,FE,F8,20,08,3A,01
140 DATA EA,ED,44,32,01,EA,79,CD,4D,00,C9
150 POKE &HEA00,2:POKE &HEA01,2
160 POKE &HFD9F,&HCD : '
170 POKE &HFDA0,&H80 : ' Altera o hook para chamar o programa
180 POKE &HFDA1,&HE8 : '
190 SPRITE$(0)=CHR$(&H3C)+CHR$(&H7E)+CHR$(&HFF)+CHR$(&HFF)+CHR$(&HFF)+CHR$(&HFF)+CHR$(&H7E)+CHR$(&H3C)
200 PUT SPRITE 0,(128,100)
210 END
220 POKE &HFD9F,&HC9
  Quando quiser parar, digite:
RUN 220

  Importante: alguns comandos como o LIST podem interromper a bolinha. Entretanto, execute a linha 220 para garantir que o hook foi desativado.



  Referências:

  1- O Livro Vermelho do MSX, Avalon Software. Editora McGraw Hill.
  2- 50 dicas para MSX, Pierluigi Piazzi et al. Editora Aleph, 1989.
  3- Revista Load MSX #3, artigo sobre hooks. Argentina, 1986.


MarMSX/cursos/assembly - MarMSX 2020