Pensando en estrenar una MiniPro TL866 me propuse grabar una function ROM para la Commodore 128 de mi viejo. Alguna vez habíamos charlado sobre la posibilidad de poner el Basic 8 en el zócalo interno, así que probé la versión disponible en internet con el emulador VICE antes de grabar nada. El resultado fue decepcionante.
La versión en cuestión se activa manteniendo pulsada la tecla Control al encender o reiniciar. A partir de ahí muestra el copyright habitual de la C128, va en busca de un sector de arranque, luego muestra un cartelón de Basic 8 y, finalmente, muestra un copyright con el agregado de la referencia a WalruSoft. Todo el proceso lleva casi 9 segundos.
Para peor, se cuelga si es activada en modo de 40 columnas, y no funciona como ROM externa, es decir que no puede colocarse en un cartucho ni en el zócalo disponible en las expansiones de memoria de Commodore. Seguro de que existe una manera de hacerlo mejor, me puse a refrescar mis conocimientos sobre el tema con la invaluable ayuda del 128 Internals (Abacus), Mapping the Commodore 128 (COMPUTE! Publications) y especialmente EPROM’s Programmer Handbook (CMS).
CONSIDERACIONS GENERALES
Lo primero es comprender que una function ROM en la C128 puede activarse en dos momentos diferentes durante el ciclo de arranque: al comienzo de la rutina RESET
o más tarde, durante el inicio del BASIC, por la rutina PHOENIX
. Para ello existen dos vectores al comienzo de la ROM —denominados Coldstart y Warmstart— a los que se accede mediante un JSR
. Esto permite a una ROM tomar el control de la máquina o realizar alguna tarea preparatoria y continuar el arranque devolviendo el control mediante un RTS
. Que una ROM se active dependerá de su ID, que es el siguiente byte presente a continuación de los vectores. Si es cero, el Kernal no la activará nunca, aunque es posible acceder a ella manualmente luego de terminado el arranque.
La cosa se pone interesante cuando el ID es distinto de cero. En ese caso, la rutina PHOENIX
cederá el control a la ROM mediante un JSR
al vector Coldstart. Si ésta le devuelve la gentileza, intentará como último paso cargar un sector de arranque desde la unidad 8 y, en caso de no encontrarlo, dará por finalizada su tarea con un RTS
, lo que nos deja en manos del editor de BASIC. Sobre este punto volveremos para evitar que se intente cargar el sector de arranque cuando optemos por activar Basic 8.
Dijimos que una function ROM podía activarse también tempranamente durante la rutina RESET
. Esto sólo ocurrirá si dicha ROM lleva un ID igual a 1. De aquí surgen dos cuestiones a tener en cuenta. La primera y principal es que si la ROM no toma el control al ser activada durante RESET
, volverá a ser activada por PHOENIX
. Y la segunda es que en ambos casos se utilizará el vector Coldstart, es decir que Warmstart no es utilizado en ningún caso. Quizás la intención original haya sido que RESET
llamase a Coldstart y PHOENIX
a Warmstart, pero es sólo una conjetura.
VOLVER A LA FUENTE
Existieron dos ediciones de Basic 8. Primero fue publicado por Patech Software en 1987 (aunque los copyrights de WalruSoft dicen 1986) y luego en 1989 por Free Spirit Software, sin diferencias a la vista (sólo el mensaje de copyright de la aplicación WIOS dice ahora 1986,89). El código en sí fue levemente alterado con un parche que se carga en $B00 (
"p.basic8.patch"
) y dos saltos desde el código principal $B00 y $B10. Increíblemente, ¡la versión para ROM disponible en internet tiene el código actualizado pero no el parche! Un cuelgue garantizado al momento en que se intente acceder a alguno de esos cambios.
El cargador original ("BOOT ED"
) está escrito en BASIC y carga tres bloques de código: el bloque principal (a partir de $1300), el parche (a partir de $0B00) y las imágenes de los punteros del ratón (a partir de $0E00). En el camino, cambia la definición de algunas teclas de función, invoca el cartelón de Basic 8 mediante la orden @WALRUS,0
e imprime un mensaje de copyright donde agrega la mención a WalruSoft. Un detalle antipático es que, además de poner el editor en modo mayúsculas/minúsculas, intenta desactivar la posibilidad de revertirlo manualmente mediante la impresión de CHR$(8)
. Dicho código produce el efecto deseado, ¡en la Commodore 64! Para la Commodore 128, el correcto habría sido CHR$(11)
.
Con toda esta información, podemos pensar en qué queremos lograr con esta ROM nueva. A mi modo de ver, optar por utilizar Basic 8 implica que no vamos a cargar otra aplicación, por lo que el acceso al sector de arranque debe ser anulado. Tampoco es necesario el cartelón de WalruSoft cada vez que usemos el editor, y a éste es mejor dejarlo en mayúsculas, como estamos acostumbrados a usarlo. El hecho de que Basic 8 cuelgue en modo de 40 columnas es propio de su código, así que para evitarlo vamos a hacer que la ROM sólo pueda activarse estando en modo de 80 columnas, y pulsando la tecla Alt al encender o reiniciar.
Dado que Basic 8 es un agregado al BASIC 7, vamos a dejar que nuestra ROM arranque durante PHOENIX
, cargue el código como si lo hubiésemos cargado desde diskette y devuelva el control. Para eso necesitamos escribir una rutina que copie los tres bloques de código máquina, más el cargador en BASIC, modificado a nuestro gusto, y luego coloque la secuencia "rU"+CHR$(13)
para que al volver al editor de BASIC lo active por nosotros. El último paso restante es el de anular el arranque desde diskette. Resulta que no está previsto en la rutina PHOENIX
pasar por alto esa secuencia, pero si observamos el comportamiento, surge una solución elegante. Dijimos que PHOENIX
cede el control a nuestra ROM mediante con JSR;
nosotros lo devolveremos a su vez con RTS
, PHOENIX
buscará un sector de arranque y —si no lo encuentra— cederá el control al editor de BASIC con otro RTS
. La respuesta está, entonces, en quitar de la pila la dirección de retorno a PHOENIX
, para que nuestro RTS
vaya directo al editor. A fin de evitar confusiones, actualizé también el mensaje «copr. 1986» en el cartel que genera la instrucción @WALRUS
por «(c)1986,89». Los demás detalles pueden leerse en el código que incluyo a continuación.
* = $8000
nop ;vector coldstart
nop
nop
jmp start ;vector warmstart
!byte 2 ;ROM ID (activar durante PHOENIX)
!text "CBM"
start bit $d7 ;estado de la tecla 40/80 columnas
bpl return
lda $d3 ;estado de las teclas de control
and #$08 ;Alt pulsada?
beq return
sei
lda $ff00 ;quitar I/O del medio
ora #$01
sta $ff00
lda #$00 ;mover bloque principal a $1300
sta $fb
sta $fd
lda #>code
sta $fc
lda #$13
sta $fe
ldx #$5e ;5d páginas de código máquina +1 de BASIC
ldy #$00
loop_pge lda ( $fb ), y
sta ( $fd ), y
iny
bne loop_pge
inc $fc
inc $fe
dex
bne loop_pge
ldy #$00 ;mover punteros del ratón a $E00
loop_ptr lda pointers, y
sta $e00, y
iny
bne loop_ptr
ldy #$1f ;mover el parche a $B00
loop_ptc lda patch, y
sta $b00, y
dey
bpl loop_ptc
lda #$70 ;cambiar el comienzo del BASIC ($7001)
sta $2e
lda #$52 ;poner la secuencia "rU" + RETURN en el buffer del teclado
sta $34a
lda #$d5
sta $34b
lda #$d
sta $34c
lda #$3
sta $d0
pla ;quitar la dirección de retorno de PHOENIX de la pila
tax ;y así pasar directo al editor
pla
tay
pla
pla
tya
pha
txa
pha
return rts
* = $8100
code !binary "p.basic8.prg",$5d00,2 ;código principal seguido del cargador BASIC
!byte $00,$27,$70,$0A,$00,$8F,$20,$42
!byte $41,$53,$49,$43,$20,$38,$20,$46
!byte $55,$4E,$43,$54,$49,$4F,$4E,$20
!byte $52,$4F,$4D,$20,$42,$59,$20,$44
!byte $49,$45,$53,$54,$52,$4F,$00,$35
!byte $70,$14,$00,$FE,$25,$3A,$9E,$20
!byte $34,$38,$36,$34,$00,$5E,$70,$1E
!byte $00,$F9,$20,$31,$2C,$22,$40,$54
!byte $45,$58,$54,$3A,$46,$41,$53,$54
!byte $22,$AA,$C7,$28,$31,$33,$29,$3A
!byte $F9,$20,$34,$2C,$22,$22,$3A,$F9
!byte $20,$38,$2C,$22,$22,$00,$97,$70
!byte $28,$00,$99,$20,$22,$93,$11,$22
!byte $3B,$A6,$32,$33,$29,$3B,$22,$9F
!byte $57,$41,$4C,$52,$55,$53,$4F,$46
!byte $54,$20,$9A,$42,$41,$53,$49,$43
!byte $20,$38,$9F,$20,$31,$30,$30,$38
!byte $36,$31,$20,$42,$59,$54,$45,$53
!byte $20,$46,$52,$45,$45,$22,$00,$BE
!byte $70,$32,$00,$99,$20,$A6,$32,$38
!byte $29,$3B,$22,$28,$43,$29,$31,$39
!byte $38,$36,$2C,$38,$39,$20,$57,$41
!byte $4C,$52,$55,$53,$4F,$46,$54,$20
!byte $49,$4E,$43,$2E,$22,$00,$C4,$70
!byte $3C,$00,$A2,$00,$00,$00
!align $ff,0,$ff
pointers !binary "p.ptrdefs.prg",$100,$302 ;punteros para el ratón
patch !binary "p.basic8.patch.prg",$20,2 ;parche edición 1989
!align $ffff,$ffff,$ff
* = $ffff !byte $ff
* = code + $474d
!pet "(c)1986,89" ;actualizar el mensaje del comando @WALRUS
!to "Basic 8 [DST].bin", plain