Hallo,
keine Ahnung ob das schon mal gemacht wurde. ich wollte mich in das CBM prg Studio einarbeiten und suchte eine Aufgabe.
ich hatte mich immer schon gefragt, wie schwer es ist, ein Spiel aus einem 8k Modul anstelle des Kernals zu starten.
Idee:
Spiel suchen, was nur 8k Größe hat. Einfache Pack/Entroutine suchen/schreiben, um aus den 8k weniger zu machen.
Dann einen Kernal-Loader schreiben, der das gepackte Image nach $8000 verschiebt und dann startet.
Sollte ja nicht so schwer sein.
Also los:
ich entschied mich für Galaxian von Atari, weil ich Galaga Clones schon immer geliebt hae und es im gepackten Zustand 5kb groß war. Gute Chancen also!
Als erstes musste ich aus dem CRT ein Binärfile machen. Danke Vice kein Thema, da Vice das Tool cartconv mitbringt:
cartconv -t bin -i galaxian.crt -o galaxian.bin
Dann kommt natürlich die Frage der Fragen: Was aus dem Kernal wird durch Galaxian aufgerufen. Die Frage ist wichtig,
da der Kernal ja nicht mehr existiert.
Um dies herauszubekommen habe ich das Binärfile durch den DA65 (der Disassambler des CC65) gejagt.
Damit nicht zu großes Quatsch am Anfang entsteht, habe ich die ersten 18 Bytes gelöscht, da dort der Modulstart Header mit
CBM80 und den Startvektoren zu finden ist.
Das disassemblierte Programm (galaxian_dis.zip) sah dann so aus (nur der Anfang, unten das ganze File):
- ; da65 V2.15 - Git cbb2833
- ; Created: 2017-10-29 13:17:29
- ; Input file: galaxian_cut.bin
- ; Page: 1
- .setcpu "6502"
- L191D := $191D
- L3931 := $3931
- L4220 := $4220
- L4420 := $4420
- L6E9A := $6E9A
- L70A0 := $70A0
- LA2AA := $A2AA
- LA822 := $A822
- LEA7E := $EA7E
- LFF81 := $FF81
- cld
- jsr LFF81
- lda #$80
- sta $0319
- lda #$11
- .....
Mich interessierten die Sprungadressen $EA7E und $FF81. Eine Suche im Quellcode ergab, dass $FF81 nur einmal und $EA7E zweimal aufgerufen wurde.
Bei $EA7E handelt es sich um das Ende der Tastaturabfrage aus dem Kernal. Die muss ich ersetzen, da sie häufig aufgerufen wird.
Bei $FF81 handelt es sich um den Einsprung in die Reset Initialisierungsroutine, die nur einmal am Anfang aufgerufen wird.
Zum Glück ist der Code bei EA7E nur 9 Byte lang. Diese 9 Byte habe ich mir aus dem Ende des Images geklaut. Dort steht nur ein Copyright des Entwicklers.
Sorry dafür
CodeEnde.JPG
Neu:
CodeEndeNeu.JPG
Wenn man sich dann die Sprung-Orgi ab $FF81 ansieht, kam mir der Zweifel, ob ich das alles noch in den 8k unterbringen kann (und vor allem muss).
Ich entschied mich daher zu erst es zu ignorieren.
Stattdessen patchte ich im Binärfile die beiden Stellen mit dem EA7E Einsprung auf $FFF0. Das geht mit nem Hexeditor sehr einfach.
Nun also zum Packen und Entpacken.
In einem anderen Projekt hatte ich mal mit der RLE packmethode gearbeitet. Die ist eher für Grafik und nicht für Code geeignet,
aber ich wollte mir nicht zu viel Arbeit machen.
Unter RLE kann man sich Beispiel Code für CC65 inklusive C Code zum Packen und Entpacken herunterladen. Mit CC65 ud GCC schnell übersetzt
hatte ich eine rlepack.exe und testete sie auf dem Galaxian Image. Es ergaben 7675 Bytes. Also noch über 400 bytes frei für die Entpack und ggf. Initialisierungroutine.
Die Datei hängt im Anhang.galaxian_pacht2_rle.zip
Der dann entstandene Code sah ungefähr so aus. galaxiankernal_asm.zip
Der Code selber ist nicht groß (bitte nochmal auf die Erklärung zu initvic warten.
- ; Kernal Version
- *=$E000
- destadr = $8000
- backcol = $D020
- src = $6C
- dest = $6E
- lastbyte = $70 ; last byte read
- counter = $71
- destlen = $72
- jsr initvic
- lda #<rlefile
- ldx #>rlefile
- sta src ; save src arg
- stx src + 1
- lda #<destadr
- ldx #>destadr
- sta dest
- stx dest + 1
- jsr rle_unpack; execute
- lda #00
- sta backcol
- jmp $8012
- rts
Aus dem oben erwähnten RLE Toolkit habe ich die folgenden Anteile entnommen:
- ; unpack a run length encoded stream
- rle_unpack
- ldy #0
- sty destlen ; reset byte counter
- sty destlen + 1
- jsr rle_read ; read the first byte
- sta lastbyte ; save as last byte
- jsr rle_store ; store
- @unpack
- inc counter
- lda counter
- sta backcol
- jsr rle_read ; read next byte
- cmp lastbyte ; same as last one?
- beq @rle ; yes, unpack
- sta lastbyte ; save as last byte
- jsr rle_store ; store
- jmp @unpack ; next
- @rle
- jsr rle_read ; read byte count
- tax
- beq @end ; 0 = end of stream
- lda lastbyte
- @read
- jsr rle_store ; store X bytes
- dex
- bne @read
- beq @unpack ; next
- @end
- rts
- ; read a byte and increment source pointer
- rle_read
- lda (src),y
- inc src
- bne @loop
- inc src + 1
- @loop rts
- ; write a byte and increment destination pointer
- rle_store
- sta (dest),y
- inc dest
- bne @loopA
- inc dest + 1
- @loopA inc destlen
- bne @loopB
- inc destlen + 1
- @loopB rts
So weit so gut. Aber es fehlte ja noch die Routine an $FF81.
Ich schaute mir den Kernal Quellcode dazu an und entschied mich,
erstmal nur die VIC Initialisierungsroutine aus $EAA0 zu nehmen.
- initvic lda #$03 ;set the screen as the output device
- sta $9A ;save the output device number
- lda #$00 ;set the keyboard as the input device
- sta $99 ;save the input device number
- ldx #$2F ;set the count/index
- @lop lda victable,X ;get a vic ii chip initialisation value
- sta $CFFF,X ;save it to the vic ii chip
- dex ;decrement the count/index
- bne @lop ;loop if more to do
- rts
- victable
- BYTE $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
- BYTE $00, $9B, $37, $00, $00, $00, $08, $00, $14, $0F, $00, $00, $00, $00, $00, $00
- BYTE $0E, $06, $01, $02, $03, $04, $00, $01, $02, $03, $04, $05, $06, $07
Fehlt noch das Einbinden des RLE gepackten Files und die Vektoren am Ende.
Damit den neuen Kernal compilierrt und in VICE ausprobiert.
HEUREKA es funktionierte!!!!!!!
Es gab nur ein Problem. Das Spiel war links und rechts abgeschnitten. Was man dank schwarzem Hintergrund und Rahmen nicht sofort sehen konnte war,
dass das Bild vertikal um 3 Pixel verschoben war. Als ich den Wert 9B aus der Initialisierungsroutine auf $98 geändert hatte. Sah alles gut aus.
Das so nun mit dem CBM prg Studio erzeugte File konnte (nach dem löschen der ersten (2 bytes) problemlos
als Kernal im Vice eingebunden werden und startete Galaxian.
Das war im Grunde einfacher als ich dachte. Zum Glück gab es nur wenige Sprünge in die Kernal Routinen.
Ich denke mit einem besseren Pack Algorithmus könnte man noch mehr Platz sparen (ZIP schafft es ja den Code auf 5Kbytes zu schrumpfen). Da bliebe
noch viel Platz für Kernal-Routinen.
Mal sehen ob mich noch ein anderes Spiel reizt
Der fertige "Kernal" ist dieser hier galaxian_in_the_kernal.zip
EDIT: Da war noch Qautsch drin ! Musste daher den text noch mal anpassen.