Mieleeni tuli ajatus harjoitella Manic Minerin C16 versiota emulaattorilla, aivan kuten olin joskus treenannut saman pelin C64 versiotakin. Emulaattorin käytössä on mm. se etu, että emuloitavan koneen tietyn hetkisen tilan voi helposti tallentaa snapshottina. Tämä implikoi myös sitä, että esimerkiksi Manic Minerin tila tallennetaan, ja pelin samaan vaiheeseen voi myöhemmin palata lataamalla snapshotin emulaattoriin, jolloin emulaattori asettaa koneen täsmälleen samaan tilaan kuin snapshotin ottamisen aikaan.
Korostan vielä, että en tietenkään laske näitä emulaattorilla tehtyjä harjoituspelejä kilpailusuorituksiksi. Jotta Manic Miner olisi oikeasti pelattu läpi, pelaamisen täytyy ehdottomasti tapahtua täysin muokkaamattomalla ohjelmalla, ilman mitään tilatallennuksia tai muita helpotuksia.
Löysin netistä Manic Miner C16-version ja tallensin sen. En muista mistä se löytyi, mutta luultavasti www.commodore16.com sisälsi siihen linkin. Kyseinen tiedosto ei todellakaan ole suuri, vaan koko on vain 12288 tavua eli noin 12KB. Jos haluat itsekin pelata sitä, niin muokkaamaton Manic Miner on saatavilla tästä linkistä.
Käytän läppärissäni Fedora 12 Linuxia, joten halusin asentaa emulaattorin, joka toimii siinä. Niinpä menin osoitteeseen www.viceteam.org, joka on VICE-emulaattorin kotisivu. VICE:n uusin versio on 2.2 ja se emuloi ainakin seuraavia 8-bittisiä vanhoja koneita:
emuloitava kone | VICE-ohjelman nimi Unixissa |
---|---|
Commodore PET | xpet |
Commodore CBM-2 | xcbm2 |
Commodore VIC-20 | xvic |
Commodore 64 | x64 |
Commodore PLUS/4 | xplus4 |
Commodore 128 | x128 |
Jos olet lukenut aiempia blogejani, niin saatat muistaa, että Commodore 16 ja PLUS/4 ovat ainakin Manic Minerin osalta täysin yhteensopivia. Niinpä lähtötilanne vaikutti hyvältä.
VICE on virallisten kotisivujen kautta saatavilla Unixin sukuisille käyttöjärjestelmille (esim. Linux ja FreeBSD) vain lähdekoodipakettina. Koodista on tehty tar-arkistotiedosto, joka on vielä pakattu gzip-ohjelmalla. Fedora 12:sta, kuten muissakin Linux-käyttöjärjestelmäjakeluissa, ohjelmistoasennusten hallinta on kuitenkin suositeltavaa tehdä astetta abstraktimmalla tasolla. Ideana on se, että ohjelmistot asennetaan ja poistetaan aina paketinhallintaohjelmiston avulla. Paketinhallinnassa on muun muassa seuraavia etuja suhteessa raakojen lähdekoodipakettien käyttöön:
Fedora, Red Hat Enterprise Linux ja CentOS käyttävät kaikki RPM-paketinhallintaa. RPM-pakettien rakenne
kuvataan erillisissä spec-tiedostoissa ("spec" lienee lyhenne sanasta "specification", joka siis
tarkoittaa määrittelyä tai spesifikaatiota). Spec-tiedostot kertovat RPM-ohjelmistoille esimerkiksi sen miten paketti käännetään ja minne
päin tiedostojärjestelmää paketin tiedostot asennetaan. VICE:n tar.gz-lähdekoodipaketti pitää sisällään spec-tiedostoja
eri järjestelmille, mutta valitettavasti esimerkiksi vice-2.2/vice.spec
on rikkinäinen, eikä
RPM-paketin luonti Fedorassa onnistu sen avulla. Ilmeisesti noita spec-tiedostoja ei oikein kukaan jaksa ylläpitää,
mutta jostain syystä ne silti roikkuvat lähdekoodijakelun mukana. Tässä on korjattu vice.spec,
joka asentaa VICE:n /opt
hakemiston alaisuuteen. Se on kuitenkin edelleen rikki siinä mielessä, että paketin
riippuvuuksia ei ole lueteltu.
Valitettavasti spec-filen korjaamisenkin jälkeen RPM-paketin luominen epäonnistui. C-kääntäjä ei pystynyt kääntämään ohjelmia. Tällä kertaa siksi, että yhdestä lähdekooditiedostosta puuttui kahden X-ikkunointikirjaston header-tiedoston sisällyttäminen. Todella rasittavaa! Eivätkö VICE:n kehittäjät käytä kunnollisia C-kääntäjäoptioita, jotka varoittaisivat virheistä käännösaikana? Ilmeisestikään eivät. Voi helvetti tätä säätämisen tarvetta. Lopulta VICE kuitenkin suostui kääntymään ja paketointikin onnistui.
Näin voidaan kysyä paketin yleistiedot:
Näin saadaan selville mitä tiedostoja paketti sisältää:
Jos taas ihmetellään jonkin tiedoston olemassaoloa, niin aina voidaan tiedustella paketinhallintatietokannalta kuuluuko se johonkin sen tuntemaan pakettiin. Tässä on muutamia esimerkkikyselyitä:
Tarkistetaan nyt, että Commodore PLUS/4 emulaattori on bash
-komentotulkkimme hakupolussa.
Ainakin /opt/bin
on lisätty komentotulkin alustustiedostossa PATH
-ympäristömuuttujaan:
PATH
on myös exportoitu, eli se näkyy myöskin komentotulkin käynnistämille lapsiprosesseille:
Selvästi bash
on lukenut PATH
määrittelyn ja löytää ohjelman suoraan nimellä xplus4
:
Voi olla, että Fedoralle on jossain erikseen RPM-repository, josta VICE:n saisi haettua suoraan toimivana RPM-pakettina, mutta tulipahan nyt tehtyä oma paketti.
Ohjelmien latausta on VICE:ssä helpotettu, ja esimerkiksi Manic Minerin voi käynnistää suoraan näin:
Ja niinhän siinä kävi, että peli on ladattu! Jos haluttaisiin olla oikein old school meiningillä liikenteessä, voisimme käynnistää
PLUS/4 kone-emulaation, antaa LOAD
käskyn, operoida virtuaalista kasettiasemaa, ja
odotella niin kauan kuin ohjelman lataamiseen oikeasti menisi vanhalla raudalla. VICE kuitenkin
on siinä mielessä käyttäjäystävällinen, että se osaa tehdä ohjelman latauksen muistiin huimasti
nopeutettuna ja ilman tarvetta antaa komentoja emuloitavan koneen tasolla.
Siihen se ilo sitten loppuikin. Yritän tallentaa snapshotin graafisen käyttöliittymän valikosta Snapshot commands
ja Save snapshot
, mutta saan vain jatkuvasti saman virheilmoituksen:
ja ainoana vaihtoehtona on kuitata viesti painikkeella Dismiss
. Lopputulos ei muutu vaikka jättäisin Manic Minerin lataamatta ja yrittäisin ottaa snapshotin heti PLUS/4:n käynnistymisen jälkeen. Miksi ihmeessä tallennus ei onnistu? Ensin ajattelin, että kyse voisi olla tiedostojärjestelmän oikeuksista, mutta kyse ei ollut siitäkään. Tarkistin strace
ohjelmalla, että open()
systeemikutsu menee läpi ja tiedoston luonti onnistuu. Mikä siis on VICE Error
ja miksi ihmeessä emulaattori ei kerro tarkempaa syytä snapshotin tallennuksen epäonnistumiseen? Harva asia on turhauttavampaa kuin virheilmoitus, joka ei kerro virheen syytä.
No, VICE on vapaata lähdekoodia ja sitä kehitetään harrastajien voimin, joten eipä tässä auta itku markkinoilla. Sitten vain lähdekoodia tutkimaan siltä varalta, että kyseessä olisi joku helppo bugi.
Menin lähdekoodipuun src
-hakemistoon ja etsin grep -r
komennolla rekursiivisesti myös kaikkien alihakemistojen tiedostoista merkkijonoa 'Cannot write snapshot file'
Tiedoston arch/unix/x11/xaw/uisnapshot.c
riviltä 99 alkaa funktio
static UI_CALLBACK(save_callback) { String name; Boolean save_roms; Boolean save_disks; char *filename; ui_popdown(snapshot_dialog); XtVaGetValues(save_roms_on_button, XtNstate, &save_roms, NULL); XtVaGetValues(save_disk_button, XtNstate, &save_disks, NULL); XtVaGetValues(file_name_field, XtNstring, &name, NULL); filename = lib_stralloc(name); util_add_extension(&filename, "vsf"); if (machine_write_snapshot(filename, save_roms, save_disks, 0) < 0) { ui_error(_("Cannot write snapshot file\n`%s'\n"), filename); } lib_free(filename); }
Selvästi virheilmoitus tulee kun machine_write_snapshot(filename, save_roms, save_disks, 0)
palauttaa arvon, joka on vähemmän kuin nolla. Etsitään siis seuraavaksi machine_write_snapshot()
funktion määrittely. Koska kyse on emuloitavalle koneelle spesifisestä funktiosta, mennään ensin plus4
alihakemistoon, jossa on PLUS/4 koneen toteuttavaa ohjelmakoodia. Sitten etsitään taas grep
komennolla:
Tiedostossa plus4.c
rivillä 555 alkaa koodi:
int machine_write_snapshot(const char *name, int save_roms, int save_disks, int event_mode) { return plus4_snapshot_write(name, save_roms, save_disks, event_mode); }
joten seuraavaksi etsimään plus4_snapshot_write()
funktiota. Se löytyi saman
hakemiston tiedostosta plus4-snapshot.c
riviltä 54:
int plus4_snapshot_write(const char *name, int save_roms, int save_disks, int event_mode) { snapshot_t *s; s = snapshot_create(name, ((BYTE)(SNAP_MAJOR)), ((BYTE)(SNAP_MINOR)), machine_name); if (s == NULL) return -1; sound_snapshot_prepare(); /* Execute drive CPUs to get in sync with the main CPU. */ drivecpu_execute_all(maincpu_clk); if (maincpu_snapshot_write_module(s) < 0 || plus4_snapshot_write_module(s, save_roms) < 0 || drive_snapshot_write_module(s, save_disks, save_roms) < 0 || ted_snapshot_write_module(s) < 0 || event_snapshot_write_module(s, event_mode) < 0 || tape_snapshot_write_module(s, save_disks) < 0 || keyboard_snapshot_write_module(s) || joystick_snapshot_write_module(s)) { snapshot_close(s); ioutil_remove(name); return -1; } snapshot_close(s); return 0; }
Nyt näemme, että on tasan kaksi tapausta, joissa plus4_snapshot_write()
palauttaa arvon,
joka on alle nolla. Ongelma on siinä, että koodissa ei ole millään tapaa eritelty mistä virhekoodi
tulee, vaan se palautetaan aina return -1
lauseella.
Jätetäänpä ensimmäinen return -1
lause rauhaan, ja keskitytään jälkimmäiseen
if
-lauseeseen, joka on disjunktio eli looginen tai-lause. Siinä
tehdään kaikkiaan kahdeksan funktiokutsua, ja jos joku disjunktion komponenteista saa
arvon, joka on eri kuin nolla (tässä tapauksessa tosi), niin ehtolauseen evaluointi loppuu. C-kieli nimittäin takaa
sen, että heti kun loogisen lausekkeen arvo tiedetään, niin jäljellä olevia komponentteja
ei enää evaluoida. Tätä kutsutaan joskus nimellä short-circuiting.
Me haluamme tietää missä kohtaa virhe tapahtuu ja siksi muutamme
plus4_snapshot_write()
funktion olemaan seuraavanlainen:
int plus4_snapshot_write(const char *name, int save_roms, int save_disks, int event_mode) { snapshot_t *s; int r; s = snapshot_create(name, ((BYTE)(SNAP_MAJOR)), ((BYTE)(SNAP_MINOR)), machine_name); if (s == NULL) return -1; sound_snapshot_prepare(); /* Execute drive CPUs to get in sync with the main CPU. */ drivecpu_execute_all(maincpu_clk); if (maincpu_snapshot_write_module(s) < 0) r = -2; else if (plus4_snapshot_write_module(s, save_roms) < 0) r = -3; else if (drive_snapshot_write_module(s, save_disks, save_roms) < 0) r = -4; else if (ted_snapshot_write_module(s) < 0) r = -5; else if (event_snapshot_write_module(s, event_mode) < 0) r = -6; else if (tape_snapshot_write_module(s, save_disks) < 0) r = -7; else if (keyboard_snapshot_write_module(s)) r = -8; else if (joystick_snapshot_write_module(s)) r = -9; if (r < 0) { snapshot_close(s); ioutil_remove(name); return r; } snapshot_close(s); return 0; }
Kuten huomaat, olen määritellyt ylimääräisen kokonaislukumuuttujan koodilla int r
.
Iso if
-lause on purettu erillisiksi if
-
ja else if
lauseiksi. Nyt jokaisessa testilauseessa asetetaan r
:n arvoksi
erilainen negatiivinen arvo riippuen siitä missä kohtaa virhe on tapahtunut.
Loppuun on vielä lisätty yksi if
-lause, jossa tarkistetaan r
:n arvo.
Jos se on alle nollan, tehdään ne kaksi toimenpidettä jotka alkuperäisessäkin koodissa
tehtiin virhetilanteessa (eli snapshot_close(s)
ja ioutil_remove(name)
).
Sitten palautetaan r
:n arvo.
Nyt meidän on vielä palattava käyttöliittymäkoodiin, josta lähdimme liikkeelle. Se muutetaan seuraavanlaiseksi:
static UI_CALLBACK(save_callback) { String name; Boolean save_roms; Boolean save_disks; char *filename; int r; ui_popdown(snapshot_dialog); XtVaGetValues(save_roms_on_button, XtNstate, &save_roms, NULL); XtVaGetValues(save_disk_button, XtNstate, &save_disks, NULL); XtVaGetValues(file_name_field, XtNstring, &name, NULL); filename = lib_stralloc(name); util_add_extension(&filename, "vsf"); if ((r = machine_write_snapshot(filename, save_roms, save_disks, 0)) < 0) { ui_error(_("Cannot write snapshot file\n`%s' (error_code=%d)\n"), filename, r); } lib_free(filename); }
Tässäkin olen lisännyt kokonaislukumuuttujan koodilla int r
. Huomaa, että
muuttujan nimellä r
ei ole mitään yhteyttä muuttujaan r
,
jonka määrittelin funktiossa plus4_snapshot_write()
. Molemmissa tapauksissa
muuttujan nimi olisi voinut olla periaatteessa mitä hyvänsä, kunhan se ei aiheuta yhteentörmäystä
aiempien muuttujanimien kanssa. Funktion sisällä määritellyt muuttujat ovat paikallisessa
nimiavaruudessa, ja siksi int r
määrittelyt ovat tässä riippumattomia toisistaan.
Oleellisin muutos on virheilmoitukseen lisätty (error_code=%d)
, jonka pitäisi
paljastaa meille missä kohdassa virhe on tapahtunut.
On muuten yleinen ohjelmointikäytäntö, että globaaleita muuttujia on hyvä välttää, ja silloin
kun niitä käytetään, niiden nimien tulisi olla sen verran pitkiä ja käyttötarkoitusta kuvaavia,
ettei yhteentörmäystä paikallisten muuttujien kanssa pääse sattumaan.
Miten käy jos C-kielen globaalissa nimiavaruudessa on koodi int banana_counter
ja samanniminen muuttuja määritellään paikallisena myös jonkin funktion sisällä? Silloin C-kielen
standardi määrää, että funktion paikallinen muuttuja varjostaa ("shadows")
globaalin muuttujan, ja funktion sisällä kaikki viittaukset banana_counter
muuttujaan viittaavat muuttujan paikalliseen versioon. Lienee sanomattakin
melko selvää, että tällaiset varjostustapaukset ovat kuitenkin erittäin hämääviä, ja tilanteita,
joissa paikallinen muuttuja varjostaa globaalia, tulee aina välttää. Yleensä
kyseessä on bugi.
Palataan kuitenkin konkreettiseen koodiimme yllä. En ole antanut r
:lle
mitään alkuarvoa, koska se saa arvonsa rivillä if ((r = machine_write_snapshot(filename, save_roms, save_disks, 0)) < 0)
.
Rivillä tehdään kolme asiaa. Ensin kutsutaan funktiota machine_write_snapshot()
kuten alkuperäisessäkin versiossa. Funktion
paluuarvo kuitenkin sijoitetaan muuttujaan r
. Lopuksi testataan oliko r
:n arvo vähemmän kuin nolla (se siis on tässä konventionaalisesti merkki virheen tapahtumisesta). Jos virhe tapahtui, kerrotaan käyttäjälle myös numeerinen virhekoodi.
Tehtyäni muutokset paketoin ja asensin VICE:n uudelleen. Nyt yritys tehdä snapshot päätyy tällaiseen virheilmoitukseen:
Virheilmoitus numeerisen virhekoodinsa kera ei edelleenkään ole mikään suuri apu peruskäyttäjälle, mutta nyt me ainakin pystymme etenemään hieman tutkimuksissamme. Mitä se virhekoodi -3
taas tarkoittikaan? En muista, joten katsotaan uudestaan muutettua koodiamme. Funktiossa plus4_snapshot_write()
lukee:
if (maincpu_snapshot_write_module(s) < 0) r = -2; else if (plus4_snapshot_write_module(s, save_roms) < 0) r = -3; else if (drive_snapshot_write_module(s, save_disks, save_roms) < 0) r = -4; else if (ted_snapshot_write_module(s) < 0) r = -5; else if (event_snapshot_write_module(s, event_mode) < 0) r = -6;
Funktiokutsu plus4_snapshot_write_module(s, save_roms)
on selvästi syyllinen, sillä
se on palauttanut arvon, joka on alle nolla, jonka seurauksena lisäämämme muuttuja r
on saanut arvon -3
. Arvaat ehkä, että seuraavaksi siirrymme tutkimaan funktiota
plus4_snapshot_write_module()
, joka löytyy tiedostosta plus4memsnapshot.c
.
Koodi näyttää tältä:
int plus4_snapshot_write_module(snapshot_t *s, int save_roms) { return -1; }
Ei voi olla totta! Funktio ei tee mitään muuta kuin palauttaa aina -1
.
Samassa tiedostossa on alempana ihan vastaavanlainen funktio myös snapshotin lukemiselle:
int plus4_snapshot_read_module(snapshot_t *s, int save_roms) { return -1; }
Otetaan tähän väliin lyhyt yhteenveto.
Olemme korjanneet vice.spec
tiedoston ja lisänneet muutaman puuttuvan #include
esikääntäjädirektiivin yhteen C-lähdekooditiedostoon, jotta VICE 2.2 edes kääntyisi. Olemme sitten saaneet viimein rakennettua ja asennettua VICE rpm-paketin. Sitten latasimme toiveikkaina Manic Minerin xplus4
emulaattoriin ja yritimme ottaa snapshotin PLUS/4 koneen tilasta tarkoituksenamme harjoitella Manic Minerin pelaamista tallennettujen välietappien avulla. Saimme tulokseksi vain mystisen virheilmoituksen, joka ei kertonut muuta kuin sen, että snapshotin tallennus ei onnistunut. Lähdimme tonkimaan ohjelmakoodia, muokkasimme sitä hieman ja saimme irti vähän informatiivisemman tiedon virheen syystä.
NYT TIEDÄMME, ETTÄ VIRHEEN SYY ON SE, ETTEI KOKO SNAPSHOT-TOIMINTOA OLE LAINKAAN TOTEUTETTU PLUS/4-KONEELLE!!!
Tässä vaiheessa sopii kysyä minkä takia VICE:n graafinen käyttöliittymä PLUS/4:lle kuitenkin sisältää snapshot-valikot, joilla ei ole mitään muuta tarkoitusta kuin hämätä käyttäjiä virheilmoituksella, joka ei kerro kuin epämääräisesti, että jokin virhe tapahtui! Ei, ei, ei.
Tarkoitukseni oli harjoitella peliä eikä säätää, joten tässä vaiheessa iltaa
turhautumisen taso oli jo melkoinen. Jos snapshotit eivät kerran toimi PLUS/4:llä, niin
Commodore 64 emulaattori x64
:ssä varmasti toimivat. Hain siis netistä
Commodore 64 Manic Minerin ja latasin sen.
Silloin kävi ilmi, että kyseessä oli jo krakkerien möyhentämä versio, jossa oli
mahdollista valita käyttääkö mm. loppumattomia elämiä tai poistetaanko spritejen
yhteentörmäykset. Valitsin loppumattomat elämät ja siirtymisen seuraavaan huoneeseen
tiettyä näppäintä painamalla.
Aloitin jauhamisen huoneesta SIXTEENTH CAVERN eli siitä samasta, johon olin parhaimmillani päässyt Commodore 16:lla. Pelasin sen melko nopeasti läpi, mutta juutuin seuraavaan eli THE WAREHOUSE huoneeseen todella pitkäksi aikaa. En laskenut, mutta minulta meni luultavasti jotain 100-150 yrityksen luokkaa ennen kuin onnistuin pelaamaan huoneen läpi. C64 versio on todennäköisesti tässä huoneessa vaikeampi, sillä alla pörrää kaksi vihollista kun C16 screenshotin mukaan niitä on vain yksi. Pelasin läpi myös kahdeksannentoista huoneen, mutta SOLAR POWER GENERATOR huoneessa alkoi tapahtua outoja. Miner Willy kävelee mm. ilmassa kuten alla olevasta screenshotista käy ilmi:
Kuvassa Willy siis ei ole hypännyt palkilta ylöspäin, vaan kävelee ilman päällä!!! En tiedä johtuuko tämä bugi ihan alkuperäisestä ohjelmasta, vai onko pelin krakkeroinut tiimi vahingossa munannut jossain. Alkuteksteissä he ainakin uhosivat tehneensä jonkin "grafiikkabugin korjauksen". Liekö niin, että ovatkin rikkoneet SOLAR POWER GENERATOR huoneen?
No, jätin sitten tuon huoneen sikseen ja siirryin seuraavaan eli THE FINAL BARRIER. Se on nimensä mukaisesti viimeinen eli kahdeskymmenes huone, ja itse asiassa yllättävän helppo kun sitä pääsee näin trainerien avulla harjoittelemaan.
C64 pelisession aikana huomasin myös sen, että monet huoneet ovat erilaisia verrattuna C16:een sekä ulkonäöllisesti että pelillisesti. Joissakin huoneissa pelistrategia, joka toimii C16:ssa, ei toimi ollenkaan C64:ssä. Tarkoitukseni on vielä jossain myöhemmässä kirjoituksessa kuvailla yksityiskohtaisemmin Commodore-versioiden eroavuuksia.
Pelattuani hyvän aikaa C64-versiota mieleeni tuli vaihtoehtoinen suunnitelma. Vaikka VICE ei tuekaan PLUS/4 koneen snapshotteja, voisin siitä huolimatta möyhentää itse Manic Miner binäärin. Minulle riittäisi jo sekin, että saisin siinä käyttööni ikuiset elämät harjoittelua varten. Siispä tuumasta toimeen.
Ensimmäiseksi halusin selvittää mikä CPU C16:ssa ja PLUS/4:ssä on. Wikipedian
avulla sain selville, että C16 CPU on 8501, joka on opcode compliant
eli käskykannaltaan yhteensopiva 6502/6510:n kanssa. Se oli hyvä juttu. Seuraavaksi etsin DEC
,
DEX
ja DEY
käskyjen numeeriset koodit. Nyt puhutaan
siis hyvin koneenläheisen tason ohjelmoinnista.
Koko homman ideana oli se, että etsisin yksitellen raa'alla laskennalla
Manic Minerin binääristä kaikki vähentämisoperaatioita tekevät opkoodit
ja korvaisin ne NOP
(No OPeration) koodeilla.
Tuon ajatuksen takana taas on se, että todennäköisesti Manic Minerin
LIVES
muuttuja on ihan yksinkertainen, 8-bittinen kokonaisluku
jossain päin muistia, ja sitä tietenkin vähennetään aina kun
pelaaja tekee virheen. Kun oikea vähennysoperaatio korvataan NOP
operaatiolla, LIVES
muuttujaa ei pienennetä ja pelaaja
saa käytännössä ikuiset elämät.
Seuraava C-ohjelma ei ole mallikelpoinen siinä mielessä, että olen osittain laistanut virheentarkistuksesta. Minulla on siihen kuitenkin pätevä syy, ja se on se, että tämä ohjelma on ns. run-and-throw-away osastoa: ajetaan kerran ja heitetään pois.
#include <stdio.h> #include <stdlib.h> #define MANIC_SIZE 12288 #define DEX 0xca #define NOP 0xea unsigned char buf[MANIC_SIZE]; int main(void) { FILE *fp; FILE *mm; int n; int i; int total = 0; char namebuf[12]; fp = fopen("manic_miner.prg", "r"); if ((n = fread(buf, MANIC_SIZE, 1, fp)) != 1) { fprintf(stderr, "fread error\n"); exit(EXIT_FAILURE); } fclose(fp); fp = fopen("info.txt", "w"); for (i = 0; i < MANIC_SIZE; i++) { if (buf[i] == DEX) { buf[i] = NOP; ++total; snprintf(namebuf, sizeof namebuf, "mm%d.prg", total); fprintf(fp, "%s offset=%d (%x)\n", namebuf, i, i); mm = fopen(namebuf, "w"); if ((n = fwrite(buf, MANIC_SIZE, 1, mm)) != 1) { fprintf(stderr, "fwrite mm%d.prg error\n", total); exit(EXIT_FAILURE); } fclose(mm); buf[i] = DEX; } } fclose(fp); return 0; }
Ohjelma siis tuottaa erilaisia versioita Manic Minerista,
joissa kussakin yksi DEX
rekisterioperaatio (heksadesimaalina ca)
on korvattu NOP
komennolla (heksadesimaalina ea).
Ajettuani ohjelman kokeilin yksinkertaisesti kaikkia sen tuottamia vaihtoehtoja VICE:llä ja totesin, että mm7.prg oli haluamani ohjelma. Kunnon assembly-hakkerit varmasti halveksivat tätä aivotonta brute force metodia, mutta koska binääri oli pieni ja kiire päästä pelaamaan kova, niin hätä ei lue lakia.
C16:lle ja PLUS/4:lle sopiva Manic Miner versio, jossa elämät eivät vähenny, on saatavissa tästä linkistä. Se eroaa alkuperäisestä vain yhden ainoan tavun osalta.
Pelasin vielä illan päätteeksi tuota versiota, mutta en millään ilveelläkään päässyt SIXTEENTH CAVERN huonetta läpi. Se on minusta paljon vaikeampi kuin Commodore 64:n versio. Toisaalta jotkut muut huoneet ovat C64:ssä hankalampia.
Olen muuten nyt varma, että Commodore 16 foorumilla elvistelty huipputulos Manic Minerilla on saavutettu huijaamalla. Pistemäärän hurja kasvu huoneiden välillä ei selity millään muulla kuin sillä, että pelattu versio on sellainen, jossa elämien määrää voidaan säädellä.
Lopuksi haluan sanoa, että olen erityisen tyytyväinen siitä, että C16 versio on nyt emulaattorilla harjoiteltavissa. SIXTEENTH CAVERN on nimittäin minulle niin vaikea, että tarvitsen ilman muuta monia yrityksiä oppiakseni pelaamaan ruudun läpi. Siinä on itse asiassa vain yksi erittäin vaikea kohta, mutta sekin on tietysti riittävä tuhoamaan hyvätkin pelit.