Y ahora, algunas comparativas (MA es "Máquina Alucinante"):
La rutina a la que se salta cuando ocurre una NMI (primeras instrucciones)
ROM del Phoenix:
Código: Seleccionar todo
loc_0101: ; CODE XREF: ROM:0066j
ld (411Eh), sp
ld sp, 411Eh
push af
push bc
push de
push hl
push ix
push iy
exx
ex af, af'
push af
push bc
push de
push hl
ld hl, 24Eh
ld de, 4000h
ld bc, 20h ; ' '
ldir
ld de, 4100h
ld bc, 4
ldir
ld de, 4200h
ld bc, 0Eh
ldir
ld (4001h), sp
ld a, r
ld (4008h), a
ld a, i
ld (4004h), a
jp pe, loc_0146 ; Salto según IFF1/IFF2
ld d, 0F3h ; 'ó' ; Opcode de DI
jr loc_0148
; ---------------------------------------------------------------------------
loc_0146: ; CODE XREF: ROM:013Fj
ld d, 0FBh ; '-' ; Opcode de EI
ROM de la MA:
Código: Seleccionar todo
loc_3870: ; CODE XREF: ROM:0066j
ld (411Eh), sp
ld sp, 411Eh
push af
push bc
push de
push hl
push ix
push iy
exx
ex af, af'
push af
push bc
push de
push hl
ld hl, 3ACEh
ld de, 4000h
ld bc, 20h ; ' '
ldir
ld de, 4100h
ld bc, 4
ldir
ld (4001h), sp
ld a, r
ld (4008h), a
ld a, i
ld (4004h), a
jp pe, loc_38AD
ld d, 0F3h ; 'ó'
jr loc_38AF
; ---------------------------------------------------------------------------
loc_38AD: ; CODE XREF: ROM:38A6j
ld d, 0FBh ; '-'
A partir de aquí hay variaciones: me refiero, por ejemplo, a la forma en la que detecta cada ROM el modo de interrupción actual del procesador.
La ROM del Phoenix III usa un método directo: cambia el vector I para que apunte a una rutina de su propia ROM y chequea si se ejecuta esa rutina, o bien la rutina en la posición 0038. La rutina de "IM 2" de la ROM la sitúa en 003C:
Código: Seleccionar todo
xor a
ld i, a ; Ponemos el vector I a 0, con lo que la direcci¢n de
; la supuesta rutina de interrupción IM 2 estará en la
; palabra de 16 bits apuntada por 00FF, que es 003C.
ei
halt ; Habilitamos interrupciones y esperamos a que se produzca una.
di
ld (4101h), de ; Según estuviéramos en IM 1 o IM 2, se ejecutará la rutina
; en 0038 o en 003C, y el valor de E será distinto en cada caso.
; Recordemos que D contenía el código de operación DI o EI según
; estuvieran deshabilitadas las interrupciones enmascarables al
; interrumpir el programa, o no.
; ---------------------------------------------------------------------------
org 0038h ; Rutina de interrupci¢n para el caso IM 1
ld e, 56h ; Junto con EDh, esto es el opcode de IM 1
reti
; ---------------------------------------------------------------------------
org 003Ch ; Rutina de interrupci¢n para el caso IM 2
ld e, 5Eh ; Junto con EDh, esto es el opcode de IM 2
reti
; ---------------------------------------------------------------------------
Sin embargo, la ROM de la MA no tiene tanto espacio como para implementar una rutina de interrupción, así que usa un método más heurístico: mira el valor de I, y si es mayor que 3Fh (el valor por defecto en el Spectrum), entonces asume que la máquina está en IM 2, en otro caso, asume que I no se ha tocado, y por tanto está en IM 1. En cualquiera de los dos casos, modifica el valor de E con los mismos valores que el método "Phoenix".
Código: Seleccionar todo
ld a, i
ld (4004h), a
jp pe, loc_38AD
ld d, 0F3h ; 'ó'
jr loc_38AF
; ---------------------------------------------------------------------------
loc_38AD: ; CODE XREF: ROM:38A6j
ld d, 0FBh ; '-'
loc_38AF: ; CODE XREF: ROM:38ABj
cp 3Fh ; '?'
jr nz, loc_38BB
ld e, 56h ; 'V'
loc_38B5: ; CODE XREF: ROM:38BDj
ld (4101h), de
jr loc_38BF
; ---------------------------------------------------------------------------
loc_38BB: ; CODE XREF: ROM:38B1j
ld e, 5Eh ; '^'
jr loc_38B5
; ---------------------------------------------------------------------------
loc_38BF: ; CODE XREF: ROM:38B9j
Por último, la grabación en sí de los bloques, en la MA se puede ver, por el código, el típico patrón de carga de IX, DE, A para el flag y llamada a la rutina SAVE. Según esto, se ve que se graban dos bloques normales (con cabecera) que se corresponden con un cargador BASIC y una rutina en código máquina, seguidos de tres bloques sin cabecera. El primero es fijo y va desde el inicio de la pantalla (4000h), hasta la posición 63FFh. El segundo bloque es variable, y se ha calculado antes, con una rutina que busca un trozo de memoria que no tenga bytes a 0 dentro del rango 6400h a F9FFh. Por último, se graba un pequeño bloque que ocupa desde FA00h hasta el final de la memoria, en FFFFh
Código: Seleccionar todo
ld ix, 3AF2h
ld de, 11h
xor a
call sub_3B14
pop de
push de
ld a, e
add a, e
add a, e
sla a
ld e, a
ld ix, 3BA1h
add ix, de
ld de, 14h
ld a, 0FFh
call sub_3B14
ld ix, 3B03h
ld de, 11h
xor a
call sub_3B14
ld ix, 3BFDh
ld de, 103h
ld a, 0FFh
call sub_3B14
pop bc
add iy, bc
ld ix, 4000h
ld de, 2400h
ld a, 0FFh
call sub_3B14
ld ix, (4200h)
ld de, (4104h)
ld a, 0FFh
call sub_3B14
ld iy, 3B91h
ld ix, 0FA00h
ld de, 600h
ld a, 0FFh
call sub_3B14
jp loc_38F2
En la ROM del Phoenix vemos una rutina prácticamente idéntica a la de MA, si bien tiene un ligero cambio: en el segundo bloque sin cabecera, el comienzo siempre es la dirección 6400h. Da la impresión (ahora lo veremos) de que la rutina de la ROM de la MA busca el primer byte no nulo desde la posición F9FFh hacia atrás, y después desde la posición 6400h hacia adelante, y sólo graba el trozo de memoria comprendido entre ambos bytes, en cambio, la ROM del Phoenix sólo busca un extremo: desde la posición F9FFh hacia atrás, y asume que desde la posición 6400h en adelante, hay datos válidos.
Código: Seleccionar todo
loc_01BB: ; CODE XREF: sub_02B3-10Cj
; sub_02B3-106j ...
ld b, 0
push bc
ld iy, 335h
ld ix, 280h
ld de, 11h
xor a
call sub_02B3
pop de
push de
ld a, e
add a, e
add a, e
add a, a
ld e, a
ld ix, 341h
add ix, de
ld de, 14h
ld a, 0FFh
call sub_02B3
loc_01E2: ; CODE XREF: sub_02B3-68j
ld ix, 291h
ld de, 11h
xor a
call sub_02B3
ld ix, 385h
ld de, 162h
ld a, 0FFh
call sub_02B3
pop bc
add iy, bc
ld ix, 4000h
ld de, 2400h
ld a, 0FFh
call sub_02B3
ld ix, 6400h
ld de, (4104h)
ld a, 0FFh
call sub_02B3
ld iy, 335h
ld ix, 0FA00h
ld de, 600h
ld a, 0FFh
call sub_02B3
loc_0225: ; CODE XREF: sub_02B3+72j
ld de, (4101h)
jp loc_0148
En efecto, la ROM de la MA se esmera un poco más que la ROM del Phoenix en buscar el trozo de memoria válido que hay que guardar. Aquí lo busca:
Código: Seleccionar todo
loc_38BF: ; CODE XREF: ROM:38B9j
ld de, 60E0h
ld hl, 6400h
loc_38C5: ; CODE XREF: ROM:38CEj
ld a, (hl)
cp 0
jr nz, loc_38D0
inc hl
dec de
ld a, d
or e
jr nz, loc_38C5
loc_38D0: ; CODE XREF: ROM:38C8j
ld (4200h), hl
ld de, 0F9FFh
ex de, hl
sbc hl, de
ld de, 0F9FFh
ex de, hl
loc_38DD: ; CODE XREF: ROM:38E6j
ld a, (hl)
cp 0
jr nz, loc_38E8
dec hl
dec de
ld a, d
or e
jr nz, loc_38DD
loc_38E8: ; CODE XREF: ROM:38E0j
ld de, (4200h)
sbc hl, de
inc hl
ld (4104h), hl
loc_38F2: ; CODE XREF: ROM:39BAj
di
ld sp, 4220h
En cambio, la ROM del Phoenix, se limita a buscar el primer byte no nulo desde el final al principio:
Código: Seleccionar todo
loc_0185: ; CODE XREF: sub_02B3-125j
xor a
cp (hl)
jr nz, loc_0190
dec hl
ld a, h
xor 65h ; 'e'
or l
jr nz, loc_0185 ; Busca la dirección m s alta en memoria que no contenga un 0,
; desde F9FFh hasta como mínimo, 6500h.
loc_0190: ; CODE XREF: sub_02B3-12Cj
ld de, 6400h
xor a
sbc hl, de
inc hl
ld (4104h), hl
ld sp, 4220h
Esto último es significativo, ya que hay muchísimo más espacio en la ROM del Phoenix que en la de la MA, para implementar una búsqueda un poco más concienzuda de lo que se ha hecho. Sin embargo, la ROM de la MA lo hace, y la del Phoenix no. ¿Por qué? ¿Estamos ante un verdadero maestro del hacking que le hizo ingeniería inversa al Phoenix (os recuerdo que tiene dos circuitos integrados con la serigrafía borrada, y aunque el contenido de la EPROM se entiende perfectamente aún sin saber qué hacen esos integrados, el uso del puerto DFh debería escamar un poco a quien viera el contenido de esa EPROM), desensambló la rutina de la EPROM, la mejoró un poco, y la incrustró en la ROM de un Spectrum? ¿O es el mismo autor del Phoenix, quien uso su propia rutina en su propia modificación del Spectrum? ¿Fue la "máquina alucinante" un ensayo de ingeniería para lo que iba a ser el futuro Abaco Phoenix?
Os recuerdo que según me consta en la documentación del interface que tengo, éste es el modelo Phoenix III, fabricado en Febrero de 1987 (adquirido por Alfonso Borro un mes más tarde. Él, de hecho, conoció al hijo del creador del Phoenix) y esta modificación se supone que se ha hecho.... ¡antes que el Phoenix III, en 1986!, el mismo año en que salió la primera versión del Abaco Phoenix.