Old tower

Gioco originale per ZX spectrum


Non mi assumo nessuna responsabilita' per danneggiamenti, perdita di dati o danni personali come risultato diretto o indiretto dell'uso delle informazioni contenute in queste pagine. Questo materiale e' fornito cosi' com'e' senza nessuna garanzia implicita o esplicita.


Home
Hardware
Software

scheen shot

Completamente per caso mi sono imbattuto in questo gioco: e' stato amore a prima vista! Secondo me e' veramente un capolavoro di pixel art, inoltre si tratta di un puzzle e io amo i puzzle, specie di questo tipo: mi riporta indietro al millennio scorso. Da prima mi sono imbattuto nella versione per C64, malgrado questo ero gia' convinto che fosse un bellissimo gioco. Devo ammettere che il C64 ha sempre incarnto per me il male: io vengo dall'MSX, per cui il C64 ha sempre rappresentato la concorrenza, l'avversario. Malgrado questo il gioco mi aveva gia' talmente rapito che ero quasi tentato di scaricare un emulatore per C64. Poi ho trovato la versione per ZX spectrum, ed e' stata una folgorazione totale: per quanto anche lo ZX spectrum fosse uno dei concorrenti era comunque piu' vicino al mio mondo, come l'MSX monta uno Z80, inoltre il frame buffer video e' molto simile a quello dell'MSX; all'epoca molti giochi per ZX spectrum venivano portati su MSX, perche' il porting era piu' semplice che da altri sistemi.

Quindi mi sono lanciato sullo ZX spectrum, questo e' il codice sorgente scaricabile da questo sito. Vediamo che cosa c'e' dentro.

I file

Almeno per il momento mi dedichero' alla versione 48k, la versione 128k dello ZX spectrum mi e' sempre stata sulle palle, io ho conosciuto il computer con i tasti di gomma e voglio rimanere fedele a questo ricordo.
La directory levels contiene una sfilza di file bin piuttosto interessanti: ci sono tutti i livelli ed una serie di file tiles*.bin.

-rwxr-xr-x 1 alex alex  272 13 dic 22:14 tiles2.bin
-rwxr-xr-x 1 alex alex  272 13 dic 22:14 tiles3.bin
-rwxr-xr-x 1 alex alex  272 13 dic 22:14 tilesBase.bin
-rwxr-xr-x 1 alex alex  256 13 dic 22:14 tiles.bin
      

Proviamo a guardarci dentro:

$ hexdump -Cv tiles.bin
00000000  00 40 00 00 00 00 02 00  aa aa 82 82 82 82 aa aa  |.@..............|
00000010  00 00 00 00 20 24 6c 6c  6c 6c 24 24 00 00 00 00  |.... $llll$$....|
00000020  00 40 70 70 40 70 70 00  00 02 0e 0e 02 0e 0e 00  |.@pp@pp.........|
00000030  00 00 00 18 18 18 00 00  00 3c 42 5a 5a 42 3c 00  |..................>..|
00000080  7f 16 5f 11 4f c0 df 3b  1b df c0 4f 11 5f 16 7f  |.._.O..;...O._..|
00000090  60 71 05 cf d5 57 d5 df  06 8e a0 f3 ab ea ab fb  |`q...W..........|
000000a0  00 7e 7c 7a 00 3c 38 34  00 70 f7 77 f7 67 d6 05  |.~|z.<84.p.w.g..|
000000b0  df 91 d5 91 df 90 d0 97  fb 89 ab 89 fb 09 0b e9  |................|
000000c0  df 91 d5 91 df 90 d7 80  fb 89 ab 89 fb 09 eb 01  |................|
000000d0  a5 bd d7 56 78 42 1f 02  a5 bd eb 6a 1e 42 f8 40  |...VxB.....j.B.@|
000000e0  fe ba fe fe fe ba fe fe  00 f7 f7 f7 00 bf bf 00  |................|
000000f0  00 f7 f7 f7 00 bf bf bf  5f 16 5f 11 4f c0 df 9b  |........_._.O...|
      

A prima vista potrebbe non dire molto, ma il nome e' quantomeno sospetto, decido quindi di stamparlo in un modo diverso. Visto che non sono molto abile con gli script, metto insieme un programmino che stampi i byte in modo diverso: voglio una stampa bit per bit del contenuto del file in linee ordinate di 8 bit per linea.

Per farlo uso questo codice.

$ printile < tiles.bin
printile version 0.01, Copyright (C) 2021  Alessandro Accattini

................
..##............
................
................
................
................
............##..
................

##..##..##..##..
##..##..##..##..
##..........##..
##..........##..
##..........##..
##..........##..
##..##..##..##..
##..##..##..##..

................
................
................
................
....##..........
....##....##....
..####..####....
..####..####....

..####..####....
..####..####....
....##....##....
....##....##....
................
................
................
................

................
..##............
..######........
..######........
..##............
..######........
..######........
................

................
............##..
........######..
........######..
............##..
........######..
........######..
................

................
................
................
......####......
......####......
......####......
................
................

................
....########....
..##........##..
..##..####..##..
..##..####..##..
..##........##..
....########....
................

##..####..######
##..........##..
......##........
................
................
................
................
................

................
................
................
................
................
......##........
##..........##..
##..####..######

##..##..##..##..
................
......##........
................
..##..##..##..##
................
............##..
................

..............##
............####
..............##
................
................
..............##
..........##..##
................

##..............
####............
##..............
................
................
##..............
##..##..........
................

################
##............##
##............##
##............##
##............##
##............##
##............##
################

................
........##......
......######....
....##########..
..##############
................
......######....
......######....

................
......######....
......######....
................
..##############
....##########..
......######....
........##......

..##############
......##..####..
..##..##########
......##......##
..##....########
####............
####..##########
....######..####

......####..####
####..##########
####............
..##....########
......##......##
..##..##########
......##..####..
..##############

..####..........
..######......##
..........##..##
####....########
####..##..##..##
..##..##..######
####..##..##..##
####..##########

..........####..
##......######..
##..##..........
########....####
##..##..##..####
######..##..##..
##..##..##..####
##########..####

................
..############..
..##########....
..########..##..
................
....########....
....######......
....####..##....

................
..######........
########..######
..######..######
########..######
..####....######
####..##..####..
..........##..##

####..##########
##....##......##
####..##..##..##
##....##......##
####..##########
##....##........
####..##........
##....##..######

##########..####
##......##....##
##..##..##..####
##......##....##
##########..####
........##....##
........##..####
######..##....##

####..##########
##....##......##
####..##..##..##
##....##......##
####..##########
##....##........
####..##..######
##..............

##########..####
##......##....##
##..##..##..####
##......##....##
##########..####
........##....##
######..##..####
..............##

##..##....##..##
##..########..##
####..##..######
..##..##..####..
..########......
..##........##..
......##########
............##..

##..##....##..##
##..########..##
######..##..####
..####..##..##..
......########..
..##........##..
##########......
..##............

##############..
##..######..##..
##############..
##############..
##############..
##..######..##..
##############..
##############..

................
########..######
########..######
########..######
................
##..############
##..############
................

................
########..######
########..######
########..######
................
##..############
##..############
##..############

..##..##########
......##..####..
..##..##########
......##......##
..##....########
####............
####..##########
##....####..####
      

Ammetto che verso la fine diventi un po' criptico, ma sicuramente all'inzio si riconoscono un sacco di tessere del gioco.

Questa rapprentazione in ASCII art serve sicuramente a sollevare l'entusiamo della truppa, ma possiamo fare di meglio: perche' non convertire le tessere in vere tessere, possiamo ad esempio farne una serie di png.

Scatta quindi la scrittura di un nuovo codice che, dato un file binario di questo tipo in ingresso, produca una serie di png in uscita. Questo il codice.

$ bin2png -b tiles -8 < tiles.bin
bin2png version 0.01, Copyright (C) 2021  Alessandro Accattini
Creating: tiles_00.png... done
Creating: tiles_01.png... done
Creating: tiles_02.png... done
Creating: tiles_03.png... done
Creating: tiles_04.png... done
Creating: tiles_05.png... done
Creating: tiles_06.png... done
Creating: tiles_07.png... done
Creating: tiles_08.png... done
Creating: tiles_09.png... done
Creating: tiles_10.png... done
Creating: tiles_11.png... done
Creating: tiles_12.png... done
Creating: tiles_13.png... done
Creating: tiles_14.png... done
Creating: tiles_15.png... done
Creating: tiles_16.png... done
Creating: tiles_17.png... done
Creating: tiles_18.png... done
Creating: tiles_19.png... done
Creating: tiles_20.png... done
Creating: tiles_21.png... done
Creating: tiles_22.png... done
Creating: tiles_23.png... done
Creating: tiles_24.png... done
Creating: tiles_25.png... done
Creating: tiles_26.png... done
Creating: tiles_27.png... done
Creating: tiles_28.png... done
Creating: tiles_29.png... done
Creating: tiles_30.png... done
Creating: tiles_31.png... done
$ montage tiles_??.png -tile 4x -geometry +16+16 -background Black tiles_table.png
      

Il risultato si puo' vedere qui sotto:

tiles

Ok, l'indagine promette bene! Ora sbirciamo nei livelli. Nella directory levels si trovano dei file bin da level01.bin fono a level25.bin, apparentemente manca il file level00.bin. Per il momento soprassediamo e sbirciamo dentro level01.bin.

Se lo guardiamo con hexdump non e' entusiasmante, a meno di non usare un trucco:

$ hexdump -v -e '"%06.6_ax   " 12/1 "%02x " "\n"' level01.bin
000000   00 00 00 00 00 00 00 00 00 00 00 00
00000c   00 00 00 00 00 00 00 00 00 00 00 00
000018   00 00 00 00 00 00 00 00 00 00 00 00
000024   00 00 00 00 00 00 00 00 00 00 00 00
000030   00 00 00 00 00 00 00 00 00 00 00 00
00003c   00 00 00 00 00 00 00 00 00 00 00 00
000048   00 00 00 00 00 00 00 00 00 00 00 00
000054   00 00 00 00 00 00 00 00 00 00 00 00
000060   00 00 00 00 00 00 00 00 00 00 00 00
00006c   00 00 00 00 00 00 00 00 00 00 00 00
000078   00 00 00 00 00 00 00 00 00 00 00 00
000084   00 00 00 00 00 00 00 00 00 00 00 00
000090   00 00 00 00 00 00 00 00 00 00 00 00
00009c   00 00 00 00 00 00 00 00 00 00 00 00
0000a8   00 00 00 00 00 00 00 00 00 00 00 00
0000b4   00 00 00 00 00 00 00 00 00 00 00 00
0000c0   00 00 00 00 00 00 00 00 00 00 00 00
0000cc   00 00 00 00 00 00 00 00 00 00 00 00
0000d8   00 00 00 00 00 00 00 00 00 00 00 00
0000e4   00 00 00 00 00 00 00 00 00 00 00 00
0000f0   00 00 00 00 00 00 00 00 00 00 00 00
0000fc   00 00 00 00 00 00 00 00 00 00 00 00
000108   00 00 00 00 00 00 00 00 00 00 00 00
000114   00 00 00 00 00 00 00 00 00 00 00 00
000120   00 00 00 00 00 00 00 00 00 00 00 00
00012c   00 00 00 00 00 00 00 00 00 00 00 00
000138   00 00 00 00 00 00 00 00 00 00 00 00
000144   00 00 00 00 00 00 00 00 00 00 00 00
000150   00 00 00 00 00 00 00 00 00 00 00 00
00015c   00 00 00 00 00 00 00 00 00 00 00 00
000168   00 00 00 00 00 00 00 00 00 00 00 00
000174   00 00 00 00 00 00 00 00 00 00 00 00
000180   00 00 00 00 00 00 00 00 00 00 00 00
00018c   00 00 00 00 00 00 00 00 00 0a 0a 0a
000198   00 00 00 00 00 00 0a 0a 0a 0a 0a 0a
0001a4   00 00 00 00 00 00 0a 0a 0a 0a 0a 00
0001b0   00 00 00 00 00 00 00 00 00 00 00 00
0001bc   00 00 0a 0a 0a 00 00 00 00 00 00 00
0001c8   00 0a 0a 0a 0a 0a 00 00 00 00 00 00
0001d4   00 00 00 00 00 00 00 0a 0a 0a 00 00
0001e0   00 00 00 00 00 00 00 00 0a 0a 0a 00
0001ec   00 00 00 0a 0a 00 00 00 00 00 00 00
0001f8   00 0a 0a 0a 0a 0a 09 00 00 00 00 00
000204   00 00 00 00 09 1e 1e 0c 0a 0a 00 00
000210   09 09 09 00 1e 1e 1e 1e 00 09 09 09
00021c   1d 1d 1d 1d 1d 1d 1d 1d 1d 1d 1d 1d
000228   07 06 06 06 07 06 06 07 06 06 06 07
000234   00 00 00 20 00 00 00 00 00 00 00 00
000240   09 0a 0a 00 06 1e 1e 06 00 09 09 09
00024c   1d 1d 1d 1d 06 1e 1e 06 1d 1d 1d 1d
000258   07 06 06 06 07 1e 1e 07 06 06 06 07
000264   06 1d 1d 1d 1d 1e 1e 1d 1d 1d 1d 06
000270   07 06 06 06 07 1e 1e 07 06 06 06 07
00027c   1d 1d 1d 1d 06 1e 1e 06 1d 1d 1d 1d
000288   00 00 08 08 06 1d 1d 06 00 08 08 08
000294   0a 00 00 00 00 08 00 00 00 00 00 00
0002a0   00 00 00 00 0a 0a 0a 0a 00 00 00 00
0002ac   00 00 00 00 00 00 00 00 21 00 00 00
0002b8   0a 0a 0a 00 00 00 00 00 00 0a 0a 0b
0002c4   0a 0a 00 00 00 00 00 0a 0a 0a 0a 0a
0002d0   09 00 1f 00 00 09 09 00 00 0d 09 00
0002dc   1d 1d 1d 1d 1d 1d 1d 1d 1d 1d 1d 1d
0002e8   00 00 00 00 00 00 00 00 00 00 00 00
0002f4   00 0a 0a 0a 0a 0a 0a 00 00 00 00 00
      

All'inizio e' tutto pieno di zeri, apparentemente inutili, ma se guardate bene nella parte finale si riconosce la forma del primo livello. Supponiamo per un momento che ciascun byte in questo file sia l'indice della tessera da prelevare dall'elenco precedente.
Vediamo se funziona.

$ bin2png -b tiles -4 < tiles.bin
$ montage tiles_??.png -tile x1 -geometry +0+0 tiles_line.png
$ levelpng -t tiles_line.png -o level01.png < level01.bin
      

Il risultato e' l'immagine qui sotto.

level01

Al di la del colore questo e' il primo livello.

Il codice

A questo punto dobbiamo veramente aprire il codice sorgente e sbirciarci dentro, niente paura: e' codice Z80.
Qui sotto si vede un estratto in cui vengono inclusi i file dei livelli.

currentLevel:	db START_LEVEL

levels:	dw level00
	dw level02,level01,level03,level04,level06
	dw level08,level09,level07,level10,level05
	dw level11,level24,level25,level22,level12
	dw level15,level21,level14,level13,level23
	dw level02

level00:	incbin "levels\menu.bin.mlz"
level01:	incbin "levels\level01.bin.mlz"
level02:	incbin "levels\level02.bin.mlz"
level03:	incbin "levels\level03.bin.mlz"
level04:	incbin "levels\level04.bin.mlz"
level05:	incbin "levels\level05.bin.mlz"
level06:	incbin "levels\level06.bin.mlz"
level07:	incbin "levels\level07.bin.mlz"
level08:	incbin "levels\level08.bin.mlz"
level09:	incbin "levels\level09.bin.mlz"
level10:	incbin "levels\level10.bin.mlz"
level11:	incbin "levels\level11.bin.mlz"
level12:	incbin "levels\level12.bin.mlz"
level13:	incbin "levels\level13.bin.mlz"
;level14:	incbin "levels\level14.bin.mlz"
;level15:	incbin "levels\level15.bin.mlz"

;level21:	incbin "levels\level21.bin.mlz"
;level22:	incbin "levels\level22.bin.mlz"
;level23:	incbin "levels\level23.bin.mlz"
;level24:	incbin "levels\level24.bin.mlz"
;level25:	incbin "levels\level25.bin.mlz"
      

Per qualche strano motivo la variabile levels riporta i livelli in ordine irregolare, non so, potrebbe essere un trucco per rimescolare l'ordine dei livelli senza rinominare tutti i file. Il file che viene incluso estensione mlz, anche questo e' un mistero; di fatto megalz.exe trasforma i file bin in mlz, potrebbe essere la forma compressa del file binario, lo scopriremo (forse) osservando il codice.

Confermato: i file mlz sono compressi (probabilmente RLE o qualcosa di simile) e devono essere scompattati da una apposita routine. Qui sotto il pezzo di codice che scompatta il livello richiesto.

unpackLevel:
	ld a,(currentLevel)	;in A il livello attuale
	ld l,a : ld h,0		;lo copia in HL
	add hl,hl;x2		;lo moltiplica per 2
	ld de,levels		;punta DE all'array di livelli
	add hl,de		;punta HL all'interno dell'array
	ld a,(hl)		;punta HL al livello compresso
	inc hl
	ld h,(hl)
	ld l,a
	ld de,level		;punta DE a level
	call DEC40		;scompatta il livello
				; questa routine deve essere chiamata con:
				; HL punta al blocco compresso
				; DE punta all'area in cui scompattare
				;quindi il livello viene scompattato in level
	ret			;termina
      

Il pezzo di codice qui sopra preleva l'indice del livello attivo dalla variabile currentLevel e lo scompatta all'interno di level. Qui sotto la routine originale che si occupa della scompattazione.

;Z80 depacker for megalz V4 packed files   (C) fyrex^mhm

; DESCRIPTION:
;
; Depacker is fully relocatable, not self-modifying,
;it's length is 110 bytes starting from DEC40.
;Register usage: AF,AF',BC,DE,HL. Must be CALL'ed, return is done by RET.
;Provide extra stack location for store 2 bytes (1 word). Depacker does not
;disable or enable interrupts, as well as could be interrupted at any time
;(no f*cking wicked stack usage :).

; USAGE:
;
; - put depacker anywhere you want,
; - put starting address of packed block in HL,
; - put location where you want data to be depacked in DE,
;   (much like LDIR command, but without BC)
; - make CALL to depacker (DEC40).
; - enjoy! ;)

; PRECAUTIONS:
;
; Be very careful if packed and depacked blocks coincide somewhere in memory.
;Here are some advices:
;
; 1. put packed block to the highest addresses possible.
;     Best if last byte of packed block has address #FFFF.
;
; 2. Leave some gap between ends of packed and depacked block.
;     For example, last byte of depacked block at #FF00,
;     last byte of packed block at #FFFF.
;
; 3. Place nonpackable data to the end of block.
;
; 4. Always check whether depacking occurs OK and neither corrupts depacked data
;     nor hangs computer.
;

DEC40
        LD      A,#80
        EX      AF,AF'
MSl      LDI
M0l      LD      BC,#2FF
M1l      EX      AF,AF'
M1Xl     ADD     A,A
        JR      NZ,M2l
        LD      A,(HL)
        INC     HL
        RLA
M2l      RL      C
        JR      NC,M1Xl
        EX      AF,AF'
        DJNZ    X2l
        LD      A,2
        SRA     C
        JR      C,N1l
        INC     A
        INC     C
        JR      Z,N2l
        LD      BC,#33F
        JR      M1l

X2l      DJNZ    X3l
        SRL     C
        JR      C,MSl
        INC     B
        JR      M1l
X6l
        ADD     A,C
N2l
        LD      BC,#4FF
        JR      M1l
N1l
        INC     C
        JR      NZ,M4l
        EX      AF,AF'
        INC     B
N5l      RR      C
        RET     C
        RL      B
        ADD     A,A
        JR      NZ,N6l
        LD      A,(HL)
        INC     HL
        RLA
N6l      JR      NC,N5l
        EX      AF,AF'
        ADD     A,B
        LD      B,6
        JR      M1l
X3l
        DJNZ    X4l
        LD      A,1
        JR      M3l
X4l      DJNZ    X5l
        INC     C
        JR      NZ,M4l
        LD      BC,#51F
        JR      M1l
X5l
        DJNZ    X6l
        LD      B,C
M4l      LD      C,(HL)
        INC     HL
M3l      DEC     B
        PUSH    HL
        LD      L,C
        LD      H,B
        ADD     HL,DE
        LD      C,A
        LD      B,0
        LDIR
        POP     HL
        JR      M0l
      

Di fatto level e' un array di 768 byte in cui poter fare un po' quello che si vuole. Alla riga successiva sembra ci sia una sorta di background compresso, ma a questo arriveremo forse piu' avanti.

;tiles level
level:  ;block 0,768;incbin "levels\map.bin"
bgPacked:       incbin "res\bg2.scr.mlz"
      

La routine unpackLevel viene chiamata una sola volta all'interno di initLevel, vediamo se riesco a capire qualcosa di questo pezzo di codice:

;it's just an adresses in mc of every exit
initExits:
	ld hl,exitsList		;punta HL ad exitsList
	ld (exitsPointer),hl	;salva il puntatore in exitsPointer
	ld de,exitsList+1	;punta DE al byte successivo
	ld bc,8*4-1		;in BC il numero di byte da azzerare: 8*4
	ld (hl),0		;azzera il primo byte
	ldir			;azzera tutti gli altri
	ret			;termina

;adds a hero to list
initHeroes:
	;reset heroes count
	xor a			;azzera heroesCount
	ld (heroesCount),a	
	;reset list of heroes
	ld hl,heroList		;punta HL a heroList
	ld (activeHero),hl	;salva il puntatore in activeHero
	ld (heroPointer),hl	;salva il puntatore in heroPointer
	ld de,heroList+1	;punta DE al byte successivo
	ld bc,8*6-1		;in BC il numero di byte da azzerare: 8*6
	ld (hl),0		;azzera il primo byte
	ldir			;azzera tutti gli altri
	ret			;termina

initBullets:
	ld hl,bulletsList	;punta HL a bulletsList
	ld (bulletsPointer),hl	;salva il puntatore in bulletsPointer
	xor a			;azzera A
	ld hl,bulletsList	;idem come sopra !!??!
	ld de,bulletsList+1	;punta DE al byte successivo
	ld bc,4*7-1		;in BC il numero di byte da azzerare: 4*7
	ld (hl),a		;azzera il primo byte
	ldir			;azzera tutti gli altri
	ret			;termina

initCanons:
	ld hl,canonsList	;punta HL a canonList
	ld (canonsPointer),hl	;salva il puntatore in cannonsPointer
	ld hl,baseCanons	;punta HL a baseConons
	ld de,canonsList	;punta DE a canonsList
	ld bc,8*6		;in BC il numero di byte da copiare
	ldir			;copia l'array baseConons in canonsList
	ret			;termina

initEnemies:
	ld hl,enemiesList	;punta HL a enemiesList
	ld (enemiesPointer),hl	;salva il puntatore in enemiesPointer
	ld hl,baseEnemies	;punta HL a baseEnemies
	ld de,enemiesList	;punta DE a enemiesList
	ld bc,8*8		;in BS in numero di byte da copiare
	ldir			;copia l'array baseEnemies in enemiesList
	ret			;termina

initCoins:
	ld hl,coinsList		;punta HL a coinsList
	ld (coinsListPointer),hl;salva il puntatore in coinsListPointer
	xor a			;azzera A
	ld hl,coinsList		;idem come sopra!??!!
	ld de,coinsList+1	;punta DE al byte successivo
	ld bc,32*4-1		;in BC il numero di byte da azzerare: 32*4
	ld (hl),a		;azzera il primo byte
	ldir			;azzera tutti gli altri
	ret			;termina

initMC:
	ld hl,mcList		;punta HL a mcList
	ld de,mcList+1		;punta DE al byte successivo
	ld bc,mc1Size*4-1	;in BC il numero di byte da azzerare: mc1Size*4
	ld (hl),0		;azzera il primo byte
	ldir			;azzera tutti gli altri

	ld hl,mcList2		;idem per l'array mcList2
	ld de,mcList2+1
	ld bc,mc2Size*4-1
	ld (hl),0
	ldir
	ret			;termina

initLevel:	
	ld hl,49152		;in HL il valore magico c000h
	ld (pixelStart+1),hl	;lo salva in pixelStart+1
	set 5,h			;in HL il valore magico e000h
	ld (mcStart+1),hl	;lo salva in mcStart+1
	xor a			;azzera A
	ld (goldCount),a	;azzera goldCount
	call clearPixels
	call unpackLevel	;scompatta il livello attuale
;	ld hl,level01
;	ld de,level
;	ld bc,72*12
;	ldir
	call initExits		;init vari visti sopra
	call initHeroes
	call initCanons
	call initBullets
	call initEnemies
	call initCoins
	call initMC
	;=====draw level
	ld de,0+0*256		;d-y e-x
	ld hl,level		;punta HL al livello scompattato
	ld c,64			;in C il numero di righe
	ld b,12			;in B il numero di colonne
	ld e,0			;azzera E
1:
	ld a,(hl)		;preleva un byte dalla level map
	push bc			;salva i registri sullo stack
	push hl
	push de

	cp CANON_LEFT		;se A=CANON_LEFT=25
	call z,addCanon		;chiama addCannon
	cp CANON_RIGHT		;se A=CANON_RIGHT=24
	call z,addCanon		;idem

	cp ENEMY_RIGHT		;se A=ENEMY_RIGHT=32
	call z,addEnemy		;chiama addEnemy
	cp ENEMY_LEFT		;se A=ENEMY_LEFT=33
	call z,addEnemy		;chiama addEnemy
	
	cp TILE_COIN		;se A=TILE_COIN=7
	call z,addCoin		;chiama AddCoin

	cp TILE_EXIT		;se A=TILE_EXIT=13
	call z,addExit		;chiama addExit

	cp TILE_GOLD		;se A=TILE_GOLD=6
	call z,addGold		;chiama addGold

	cp HERO_START		;se A=HERO_START=31
	call z,initHero		;chiama initHero

	cp HERO_LEFT		;se A=HERO_LEFT=20
	call z,addHero		;chiama addHero
	cp HERO_RIGHT		;se A=HERO_RIGHT=21
	call z,addHero		;idem
	cp HERO_UP		;se A=HERO_UP=19
	call z,addHero		;idem
	cp HERO_DOWN		;se A=HERO_DOWN=18
	call z,addHero		;idem

	call drawTile		;stampa la tessera indicizzata da A nella
				;posizione indicata da DE (E=x, D=y)

	pop de			;recupera i registri
	pop hl
	pop bc
	inc hl			;incrementa HL (byte successivo)
	inc e			;incrementa E (E=x)
	djnz 1b			;decrementa B, se non zero salta
				;B contiene il numero di colonne (12)
	inc d			;riga successiva (D=y, E=x)
	dec c			;scala il contatore delle righe
	jr nz,2b		;se non zero, salta
	ret			; termina
      

La routine initLevel inizia inizializzando degli oscuri buffer: vengono inizializzati a zero tranne alcuni il cui contenuto viene copiato da una costante. Dopo di questo legge la mappa del livello e usa drawTile per piazzare una tessera in un rettangolo di 12 colonne e 64 righe. Man mano che procede in questo riempimento verifica il tipo di tessera che sta posizionando ed aggiorna gli array di cui sopra. Alla fine della routine il livello e' costruito: tutti i pixel sono stati disegnati e gli array sono stati riempiti di teste, pipistrelli, omini, oro, etc...

Quindi ora sappiamo tutto!
Chi si accontenta probabilmente gode, ma a me manca ancora qualcosa: il video non contiene 64 righe, quindi dove cavolo e' andata a scrivere la routine drawTile?

Di fatto la routine drawTile scrive in un frame buffer virtuale, molto probabilmente la parte interessata (64 righe sono tropper per il video) saranno copiate in seguito sul vero video buffer.

Girando per il codice ho risolto un altro mistero: le pietre blu di contorno alla torre: a poche righe da quello che sembra l'entry point del programma c'e' un riferimento a bgPacked, viene scompattato dalla routine DEC40 direttamente nel video buffer (una delle poche scritture dirette a video).

	page 0
	org 24500
start:
	call 49152
	;kempsotn test
	call kempstonTest
	ld a,e
	ld (haveKempston),a;1 - if have it

	di
	ld sp,24499

	xor a
	call swapPage

	ld hl,nullProc
	ld (topProc+1),hl
	ld (botomProc+1),hl
	di: ld hl,interruptNull :	call IMON: ei	

	call startGame
	ld hl,bgPacked
	ld de,16384
	call DEC40
      

Piu' avanti si trova la dichiarazione di gbPacked:

;tiles level
level:	;block 0,768;incbin "levels\map.bin"
bgPacked:	incbin "res\bg2.scr.mlz"
      

Il file bg2.scr contiene uno screen, ovvero la copia del video buffer, completo di attributi. Questo viene compresso, incluso nel codice e scompattato direttamente a video. Ho ricostruito i passaggi creando una nuova utility:

$ screenpng -o bg2.png < 48k/res/bg2.scr
$ convert bg2.png -scale 200% bg2_big.png
      

background

Colmiamo un'altra curiosita': ricordate la prima directory che abbiamo spulciato, conteneva 4 diversi file di tessere. Che differenza ci sara' tra uno e l'altro?

-rwxr-xr-x 1 alex alex  272 13 dic 22:14 tiles2.bin
-rwxr-xr-x 1 alex alex  272 13 dic 22:14 tiles3.bin
-rwxr-xr-x 1 alex alex  272 13 dic 22:14 tilesBase.bin
-rwxr-xr-x 1 alex alex  256 13 dic 22:14 tiles.bin
      
tiles.bin tiles2.bin tiles3.bin tilesBase.bin
Download: OldTower_SourceCode.zip Codice sorgente originale.
oldtower_pack-0.01.tar.gz Uitility varie per l'indagine del codice.
oldtower_pack-0.02.tar.gz Uitility varie per l'indagine del codice.

Questo sito e' stato realizzato interamente con vim.
Grazie a tutta la comunita' open source, alla free software foundation e chiunque scriva software libero.