Tutorial de optimización de cintas y ultracargas

Cursos, reparaciones, fichas, tutoriales, etc.

Moderador: Fundadores

Avatar de Usuario
antoniovillena
Demonio segundo orden
Demonio segundo orden
Mensajes: 1596
Registrado: 02 Abr 2013, 19:06
Been thanked: 1 time

Tutorial de optimización de cintas y ultracargas

Mensaje por antoniovillena »

Lección 1

En esta lección tan sólo voy a generar la cinta original del Manic Miner, para luego ir optimizando a medida que avanzamos en el tutorial.

Para seguirlo hay que instalarse los siguientes programas: Examinemos el fuente que genera la cinta de Manic Miner y expliquemos:

Código: Seleccionar todo

join
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 línea es para generar la pantalla de carga, es un programa ad-hoc hecho para esta carga en cuestión que es bastante peculiar. Sólo usa la parte de los atributos, en concreto el tercio central (256 bytes). A parte todos los atributos tienen el flash (y el brillo) activado, por lo que va conmutando entre los distintos colores. La gracia está en que se codifica una pantalla para la tinta y otra para el fondo, por lo que resulta una animación con el texto MANIC y MINER sin gastar ciclos de CPU. Partiendo de screen1.png y screen2.png se genera screen.bin (los 256 bytes).

La siguiente línea genera el binario que corresponde al fichero BASIC loader.bas, que se trata del cargador. El cargador no tiene mucha miga:

Código: Seleccionar todo

  10 CLEAR 30000
  20 PAPER 0: INK 0: CLS : LOAD ""CODE : LOAD ""CODE
  30 RANDOMIZE USR 33792
Baja RAMTOP a 30000 para ubicar el juego, pone el fondo negro para que los tercios superior e inferior no desentonen con la pantalla de carga, carga dos bloques de datos y salta al juego (dirección 33792).

Prosigamos con el archivo bat. La siguiente línea ensambla el código fuente, generando manic.bin, que ocupa 32768 bytes y parte de la posición 32768 (ocupa toda la RAM alta).

Por último tenemos el generador de cinta (GenTape), que crea los 3 siguientes bloques (6 si contamos las cabeceras):
  1. Bloque BASIC cargador, que hemos explicado antes, con el nombre 'ManicMiner' y que se autoejecuta en la línea 10.
  2. Bloque de datos con la pantalla de carga, llamado 'mmm' que ocupa 256 bytes y ubicado en la dirección hexadecimal $5900 (atributos del tercio central de la pantalla).
  3. Bloque de datos con el juego, llamado 'mm1', que tiene una longitud de $8000 en hexadecimal (nótese que la longitud nunca se especifica, viene determinada por el tamaño del archivo) y comienza también en $8000.
Eso es todo, una vez ejecutamos el make.bat ya tenemos listo nuestro manic.tap, exactamente igual que la cinta original. En la siguiente lección pondremos una pantalla de carga más normalita y trataremos de crear nuestro propio cargador en código máquina.

Pincha aquí para bajar el archivo de la lección
Avatar de Usuario
antoniovillena
Demonio segundo orden
Demonio segundo orden
Mensajes: 1596
Registrado: 02 Abr 2013, 19:06
Been thanked: 1 time

Re: Tutorial de optimización de cintas y ultracargas

Mensaje por antoniovillena »

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:
  1. Poner la pila a 30000 ($7530)
  2. Poner toda la pantalla negra
  3. 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.
  4. Cargar el siguiente bloque (juego) desde posición 32768 ($8000) con longitud 32768 ($8000), es decir, hasta el final de la RAM.
  5. 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).

Código: Seleccionar todo

        scf
        ld      a, $ff
        ld      ix, $4000
        ld      de, $1b00
        call    $0556
¿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).

Código: Seleccionar todo

        scf
        sbc     a, a
        ld      ix, $4000
        ld      de, $1b00
        call    $0556
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:

Código: Seleccionar todo

        ld      sp, $7530

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).

Código: Seleccionar todo

        ld      hl, $5800
        ld      de, $5801
        ld      bc, $2ff
        ld      (hl), l
        ldir
Por último sólo tenemos que saltar a la dirección que comienza el juego ($8400), usando la instrucción JP.

Código: Seleccionar todo

        jp      $8400
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
Avatar de Usuario
Izaro España
Fundador
Fundador
Mensajes: 3340
Registrado: 02 Abr 2013, 10:39
Ubicación: Valencia (mas alla del sol)
Has thanked: 14 times
Been thanked: 20 times

Re: Tutorial de optimización de cintas y ultracargas

Mensaje por Izaro »

si señor muy muy pero que muy util e instructivo gracias compi
01001101 01101001 01110010 01100001 00100000 01110001 01110101 01100101 00100000 01100101 01110010 01100101 01110011 00100000 01100011 01101111 01110100 01101001 01101100 01101100 01100001 00101110
Avatar de Usuario
antoniovillena
Demonio segundo orden
Demonio segundo orden
Mensajes: 1596
Registrado: 02 Abr 2013, 19:06
Been thanked: 1 time

Re: Tutorial de optimización de cintas y ultracargas

Mensaje por antoniovillena »

De nada. Aviso que el tutorial va creciendo en dificultad. Quiero meter compresión, modificaciones del cargador estándar para cambiar los colores de carga y la velocidad y finalmente ultracargadores. Mi idea era mostrar cómo se pueden meter ultracargadores (sobre todo un tipo especial que creó hace tiempo Francisco Villa) en juegos modernos para acelerar el tiempo de carga a aproximadamente 20 segundos (incluída la primera parte en carga estándar).
Avatar de Usuario
Izaro España
Fundador
Fundador
Mensajes: 3340
Registrado: 02 Abr 2013, 10:39
Ubicación: Valencia (mas alla del sol)
Has thanked: 14 times
Been thanked: 20 times

Re: Tutorial de optimización de cintas y ultracargas

Mensaje por Izaro »

ok estare atento al tema gracias de nuevo
01001101 01101001 01110010 01100001 00100000 01110001 01110101 01100101 00100000 01100101 01110010 01100101 01110011 00100000 01100011 01101111 01110100 01101001 01101100 01101100 01100001 00101110
Avatar de Usuario
wilco2009 !Sinclair 1
Hermano de Lucifer
Hermano de Lucifer
Mensajes: 8152
Registrado: 01 Abr 2013, 23:47
Ubicación: Valencia
Has thanked: 47 times
Been thanked: 101 times

Re: Tutorial de optimización de cintas y ultracargas

Mensaje por wilco2009 »

Pero que muy interesante.

Tengo que engancharme de nuevo al software y practicar estas cosas.
"Aprender a volar es todo un arte. Aunque sólo hay que cogerle el truco. Consiste en tirarse al suelo y fallar".

Douglas Adams. Guía del autoestopista galáctico.
Avatar de Usuario
flopping
Fundador
Fundador
Mensajes: 9971
Registrado: 29 Mar 2013, 15:26
Ubicación: Valencia
Been thanked: 122 times
Contactar:

Re: Tutorial de optimización de cintas y ultracargas

Mensaje por flopping »

Muy instructivo y como siempre muy útil, seguiremos este curso-tutoríal, que promete y mucho, jejejej..

Y ya puestos, te pregunto una cosa, que igual no viene al caso, pero que me ha llamado la atención, veo que con las pruebas y demás del ZXUNO, estáis creando unos juegos en ROM, bueno, mas que creando modificando juegos ya hechos y pasandolos a ROM, no se si comprimiendo partes o descartando cosas, pero el caso es que metéis algunos juegos que en principio no son de 16k, en esos 16k de rom, ¿podrías crear algún tutorías al respecto o explicar como va el tema de esas conversiones?, salu2.
No me hago responsable de mis post pues estan escritos bajo la influencia del alcohol y drogas psicotropicas, por la esquizofrenia paranoide.
(C) 1982-2024, 42 años de ZX Spectrum.
http://www.va-de-retro.com/ un foro "diferente".

Mi juego, que puedes descargar desde aqui
Avatar de Usuario
antoniovillena
Demonio segundo orden
Demonio segundo orden
Mensajes: 1596
Registrado: 02 Abr 2013, 19:06
Been thanked: 1 time

Re: Tutorial de optimización de cintas y ultracargas

Mensaje por antoniovillena »

Lección 2 y media

Antes de meterme en la lección 3 os voy a mostrar esta miniguía para analizar los TAPs/TZXs. La mejor herramienta para esto, desde mi punto de vista es Tapir. Es muy sencilla y se pueden hacer muchas cosas. Tiene cosas interesantes como un visor de código BASIC, de código máquina, de pantallas.

Lo más útil para mí es que se pueden extraer bloques binarios. Aunque bueno, la idea es editar archivos TZX copiando y pegando bloques entre el panel Left y Right. No guarda en formato TAP pero sí los reconoce y los lee. Suficiente para nuestro análisis.

Otra herramienta muy útil es un editor hexadecimal. Nos servirá para comprender el formato TAP. También se puede emplear para hacer pokes en los propios TAPs, aunque luego habría que corregir el checksum con Tapir. Yo uso HxD como editor, no es nada del otro mundo pero el open source y hace lo que necesito.

Os muestro 2 pantallazos y os lo explico. Lo suyo es que vayáis experimentando con estas utilidades hasta conseguir cierto manejo.

En este primer pantallazo muestro en Tapir el TAP de la lección 1, con el código BASIC abierto en ventana. Además he cargado detrás el mismo archivo con el editor hexadecimal, y he marcado los 3 primeros bloques (el primero en rojo, el segundo en azul y el tercero en verde). Cada bloque se compone de 4 segmentos:
  1. Longitud del bloque, 2 bytes. Si es cabecera siempre es 19 ($13 $00).
  2. Byte FLAG, 1 byte. $00 para cabecera, $FF para datos.
  3. Datos. Es el segmento grande del bloque.
  4. Checksum, 1 bytes. Es la función XOR de todos los datos del bloque, incluyendo el byte FLAG. Sirve para detectar errores de carga en cinta.
Imagen

En este segundo pantallazo cargo los dos TAPs de la segunda lección, uno en cada panel. Dejo abierta la ventana que corresponde a la pantalla de presentación del segundo TAP.

Imagen
Avatar de Usuario
antoniovillena
Demonio segundo orden
Demonio segundo orden
Mensajes: 1596
Registrado: 02 Abr 2013, 19:06
Been thanked: 1 time

Re: Tutorial de optimización de cintas y ultracargas

Mensaje por antoniovillena »

flopping escribió:Muy instructivo y como siempre muy útil, seguiremos este curso-tutoríal, que promete y mucho, jejejej..

Y ya puestos, te pregunto una cosa, que igual no viene al caso, pero que me ha llamado la atención, veo que con las pruebas y demás del ZXUNO, estáis creando unos juegos en ROM, bueno, mas que creando modificando juegos ya hechos y pasandolos a ROM, no se si comprimiendo partes o descartando cosas, pero el caso es que metéis algunos juegos que en principio no son de 16k, en esos 16k de rom, ¿podrías crear algún tutorías al respecto o explicar como va el tema de esas conversiones?, salu2.
Pasar juegos a ROM es más complicado, puesto que hay que tener un nivel alto de ensamblador. Además no hay ninguna forma sencilla de ir descartando cosas. Antes de recortar nada tienes que saber lo que estás recortando, o sea que tienes que entender como está estructurado el juego aunque sea por encima. Lo más cómodo es disponer del código fuente del juego, pero la mayoría de las veces esto es imposible.

Una vez hayas separado binario, y pantalla de presentación y compruebes que el juego te cabe en un cartucho el siguiente paso es comprimirlo. El stream comprimido quedaría en los 16K el cartucho junto con el descompresor. Al ejecutarse el cartucho se descomprime en RAM, en las mismas direcciones en que se cargaría desde la cinta. Básicamente uso 2 compresores, el zx7b que es rápido y sencillo, que es el que uso la mayoría de las veces. Y el exomizer, que es más complejo de utilizar pero hace su trabajo en los casos que estamos más ajustados de espacio.

Lo más complicado es hacer creer al juego que tiene una ROM auténtica. Si por ejemplo el juego usa el mapa de caracteres de la ROM (Manic Miner, Jet Set Willy), tienes que almacenar el mismo en las direcciones $3d00-$3fff (evidentemente no se puede comprimir). Tienes que ver las rutinas ROM que usa el juego y partir el stream comprimido justo en los trozos que necesitas para dichas rutinas. También puedes poner JPs (de tres bytes) en lugar de las rutinas en sí, y ensamblar las rutinas ROM originales aglutinadas al final del fichero.
Avatar de Usuario
antoniovillena
Demonio segundo orden
Demonio segundo orden
Mensajes: 1596
Registrado: 02 Abr 2013, 19:06
Been thanked: 1 time

Re: Tutorial de optimización de cintas y ultracargas

Mensaje por antoniovillena »

Si quieres aprender a crear cartuchos IF2, lo mejor es que veas el código fuente de cómo lo he hecho. Tengo ejemplos de varios juegos dispersos por el hilo del ZX-Uno, te recomiendo que sigas este hilo y descargues de aquí, que están más juntos:

http://www.worldofspectrum.org/forums/s ... hp?t=47243
Responder

Volver a “Cursos y Tutoriales”