Lección 2
Partimos del tutorial anterior. Este es el archivo make.bat que lo generaba:
Código: Seleccionar todo
zmakebas -r -o loader.bin loader.bas
SjAsmPlus manic.asm
GenTape manic.tap ^
basic 'ManicMiner' 10 loader.bin ^
hdata 'mmm' 5900 screen.bin ^
hdata 'mm1' 8000 manic.bin
La primera optimización que vamos a hacer es transformar el cargador, pasándolo de BASIC en código máquina. ¿Por qué? Pues muy sencillo, un cargador código máquina nos permite cargar bloques sin cabecera, por tanto reducimos el tiempo de carga.
¿Qué es esto de la cabecera? Es un bloque muy corto que sólo contiene información sobre el nombre del programa, la dirección de comienzo, la longitud, etc... cosas que podemos evitar si hacemos una llamada a la rutina de carga desde ensamblador. El único bloque que tendrá cabecera es el primero, obvio porque lo cargamos desde BASIC con LOAD"". Este sería el aspecto del nuevo make.bat:
Código: Seleccionar todo
SjAsmPlus loader.asm
SjAsmPlus manic.asm
GenTape manic.tap ^
basic 'ManicMiner' 10 loader.bin ^
data screen.bin ^
data manic.bin
Como véis, la única diferencia es que el archivo loader.bin lo ensamblamos desde loader.asm (antes lo obteníamos de loader.bas) y los bloques de pantalla y juego pasan de ser hdata (bloques de datos con cabecera) a data (bloques de dato sin cabecera). Han perdido los 2 primeros parámetros (nombre y dirección de carga), quedándose sólo con el nombre del binario a cargar. Por supuesto debemos incluir dicha dirección en el cargador. Desde Basic no hacía falta (se cargaban con LOAD""CODE) porque esta información la obtenían de la cabecera.
Recuerdo el antiguo archivo BASIC, el cual tenemos que pasar a ensamblador:
Código: Seleccionar todo
10 CLEAR 30000
20 PAPER 0: INK 0: CLS : LOAD ""CODE : LOAD ""CODE
30 RANDOMIZE USR 33792
Para no meternos en harina tan pronto, hagamos un resumen de lo que tiene que hacer nuestro cargador, facilito los valores hexadecimales con el signo dolar delante:
- Poner la pila a 30000 ($7530)
- Poner toda la pantalla negra
- Cargar un bloque desde cinta desde la posición 22784 ($5900) con una longitud de 256 ($100) bytes. Esto sería la pantalla de carga.
- Cargar el siguiente bloque (juego) desde posición 32768 ($8000) con longitud 32768 ($8000), es decir, hasta el final de la RAM.
- Saltar a la dirección 33792 ($8400)
Bueno, antes de nada supongo que tenéis una mínima base de ensamblador. Si no es así, no os asustéis, vamos a usar muy pocas instrucciones, tan sólo hay que repetir los pasos con distintos valores.
Lo primero es saber dónde se encuentra la rutina de carga y qué parámetros necesita. Dicha rutina se llama LD_BYTES, y a parte de para cargar bloques también se usa para verificarlos. También hay que saber otra cosa, para diferenciar bloques de datos de bloques de cabecera se usa el byte de FLAG, que básicamente es el primer byte del bloque pero que no forma parte del bloque en sí (no se carga en memoria), es simplemente para comprobar que lo que el tipo de bloque que estamos leyendo es correcto, y en caso contrario mostrar un mensaje de error.
Bueno pues el byte de FLAG para un bloque de cabecera es 0 ($00), mientras que el byte de FLAG para un bloque de datos es 255 ($FF), así de sencillo. Como vamos a llamar a la rutina de carga desde ensamblador, en realidad podemos codificar el FLAG que queramos, pero no vamos a complicarnos, usaremos siempre 255 ($FF) para bloques de datos, que es el tipo de bloque que genera GenTape cuando especificamos "data".
La rutina LD_BYTES se encuentra en la dirección 1366 ($0556), y recibe los siguientes parámetros:
- Flag carry. A 1 si vamos a cargar datos, a 0 si vamos a verificar. Como vamos a cargar siempre ponemos un 1.
- Registro A. Byte FLAG que esperamos para el siguiente bloque. Como es un bloque de tipo datos cargamos 255 ($FF).
- Registro IX. Dirección de comienzo del bloque.
- Registro DE. Longitud del bloque.
Parece complicado, pero en realidad el código ensamblador para generar esto es muy sencillo y siempre el mismo. Por ejemplo si queremos cargar desde $4000 un bloque con longitud $1B00, lo que vendría a ser una pantalla (a partir de ahora por comodidad sólo pondré valores en hexadecimal).
¿Veis que sencillo? La primera instrucción pone el carry a 1, las 3 siguientes cargan registros y la última hace la llamada a la rutina de carga. Lo siguiente es una pequeña mejora, aprovechando que carry está a 1, podemos cargar el registro A a $ff con otra instrucción más corta (1 byte en lugar de 2).
Ya sabemos hacer lo importante del cargador. El resto en realidad es muy sencillo. Lo primero es poner la pila a $7530, para que no haya posibilidad de cuelgue si cargamos algo por encima (recordad que el juego estará ubicado entre $8000 y $FFFF. Esto se hace con una única instrucción:
Lo siguiente es poner la pantalla negra. Esto se hace normalmente con la instrucción LDIR, no es lo más rápido pero sí lo más sencillo. Como sólo vamos a usar atributos en nuestra pantalla de carga y la zona bitmap que nos interesa (la central) ya está inicializada a $00, pues nos basta con poner a cero toda la zona de atributos. Al poner en negro tanto la tinta como el fondo, se borrarán todos los mensajes de cabecera (el Program: ManicMiner).
Por último sólo tenemos que saltar a la dirección que comienza el juego ($8400), usando la instrucción JP.
Todo el cargador junto sería así:
Código: Seleccionar todo
ld sp, $7530
ld hl, $5800
ld de, $5801
ld bc, $2ff
ld (hl), l
ldir
scf
sbc a, a
ld ix, $5900
ld de, $0100
call $0556
scf
sbc a, a
ld ix, $8000
ld de, $8000
call $0556
jp $8400
No es muy complicado, ¿verdad?. Pues bueno, ahora la clave está en cómo pasarlo a binario y sobre todo en cómo incluirlo en el primer bloque BASIC. Hay muchos mecanismos para incrustar y ejecutar código máquina desde BASIC, el que os voy a mostrar es el más corto de todos, se basa en un OVER USR 23755 ($5ccb) truncado, que es esta línea:
Código: Seleccionar todo
db $de, $c0, $37, $0e, $8f, $39, $96 ;OVER USR 7 ($5ccb)
Primero ejecutamos cuatro bytes de ensamblador (si no nos llega rellenamos con NOP), luego iría esta línea db, y finalmente el resto del código. Dado que la primera instrucción está en $5ccb debemos poner el ORG $5ccb antes de nada y las directivas que use nuestro ensamblador para generar el loader.bin, que en nuestro caso es: output loader.bin. En resumen, el archivo loader.asm en su totalidad es:
Código: Seleccionar todo
output loader.bin
org $5ccb
ld sp, $7530
di
db $de, $c0, $37, $0e, $8f, $39, $96 ;OVER USR 7 ($5ccb)
ld hl, $5800
ld de, $5801
ld bc, $2ff
ld (hl), l
ldir
scf
sbc a, a
ld ix, $5900
ld de, $0100
call $0556
scf
sbc a, a
ld ix, $8000
ld de, $8000
call $0556
jp $8400
Por último, dado que la pantalla de este juego es un poco peculiar, he creado una pantalla de carga alternativa a tamaño completo (6912 bytes), para que podáis aplicar el cargador a vuestros juegos. Las modificaciones están en make2.bat y en loader2.asm en el mismo zip de la lección.
Bueno, eso es todo por hoy. Espero que os haya sido útil la lección. En la siguiente no tengo muy claro lo que hacer, no sé si meter cosas turbo o explicar la compresión, ya veremos.
Pincha aquí para bajar el archivo de la lección