Modifiche al codice dello Space Invaders
(....introduzione....)
Da quando possiedo dei vecchi flipper Bally, trovo seccante il fatto che non esista una modalità "free play" impostabile tramite i dip-switch.
(....completare....)
Per poter effettuare qualsiasi modifica al codice, è necessario innanzitutto risolvere il problema della routine di controllo dell'integrità delle ROM.
Infatti all'accensione della macchina vengono eseguite varie procedure diagnostiche, ognuna di queste in caso di successo provoca un lampeggio del LED sulla scheda CPU, (i famosi "sette flash") mentre in caso di fallimento manda il programma in un loop infinito che impedisce l'avvio della macchina.
In particolare, la prima procedura si occupa di eseguire delle somme di controllo dei byte delle ROM a blocchi di 512; ciascuna di queste somme deve dare come risultato zero. Questo significa che per ogni blocco di 512 byte, ne esiste almeno uno che può essere impostato liberamente affinchè la somma dia come risultato zero. Chiaramente, senza correggere opportunamente questi byte qualsiasi modifica al programma impedirebbe il funzionamento della macchina.
A questo punto ci sono due possibilità: - trovare questi byte e modificarli - bypassare la routine di controllo Nel primo caso, si presenta un insidioso problema: ho avuto l'impressione che i byte di controllo non siano sempre nella stessa posizione all'interno dei blocchi da 512, quindi l'unico modo per individuarli con certezza è di disassemblare tutto il codice e andare per esclusione!
La seconda opzione è molto più semplice da realizzare, ha come inconveniente che in caso di malfunzionamento di una EPROM (non è affatto improbabile che con il passare del tempo in una EPROM cambino di stato uno o più bit) la macchina verrebbe avviata lo stesso. Ciò poteva rappresentare un problema nell'"ambiente di lavoro" del flipper: un programma di controllo malfunzionante può significare palline o partite annullate al giocatore, crediti elargiti gratuitamente, o peggio veri e propri danni causati dall'attivazione prolungata di un solenoide. Per un uso domestico, invece, questo problema è molto relativo; la strada prescelta è stata quindi la seconda.
Le routine di controllo si trovano nella ROM U6 a partire dall'indirizzo $xxxx. Esaminando il codice di quella che esegue le somme ho scoperto un particolare curioso: è presente un test che confronta il numero a 16 bit contenuto negli indirizzi $516E-$516F (quindi nella ROM U2) con il "numero magico" $5A5A, e se tale confronto ha successo, salta la parte delle somme di controllo! Questo è evidentemente un espediente per disabilitare il controllo delle ROM intervenendo sulla sola ROM U2, probabilmente era usato in fase di sviluppo del programma per non dover ricalcolare i byte di checksum ad ogni modifica.
A questo punto, si può intervenire mettendo $5A nelle locazioni $516E e $516F, o meglio rendendo sempre vero l'esito del confronto con il "numero magico", sostituendo l'istruzione:
$58ea: ldx $516e ($fe $51 $6e)
con:
$58ea: ldx #$5a5a ($ce $5a $5a)
Ora è possibile fare qualsiasi modifica al codice! Per prima cosa implementiamo il free-play. Come punto di partenza, ho deciso di trovare la routine che viene eseguita alla pressione del tasto "start". Ho dovuto quindi individuare la parte di codice che gestisce gli eventi associati alla matrice degli ingressi, questa non è altro che il "loop principale" del programma, posto nella rom U6 a partire dall'indirizzo $xxxx (e qui il duebugger di PinMame si è rivelato prezioso). In particolare, per ogni combinazione di riga/colonna che risulta selezionata (cioè per ogni contatto chiuso, dopo un filtro per eventuali "rimbalzi"), viene eseguito un salto indiretto ad una routine il cui indirizzo di partenza è specificato in una tabella. Non inaspettatamente, la prima cosa che avviene nella routine associata al tasto "start" è il confronto del numero dei crediti memorizzati in $xxxx con zero:
$5364: beq $5360 ($xx $yy)
che, in caso di successo, manda il programma nel punto di uscita della routine. Sostituiamo il salto condizionato lungo due byte (il codice operativo e la lunghezza del salto) con due operazioni nulle:
$5364: nop ($01) $5365: nop ($01)
e l'avvio del gioco proseguirà anche a crediti zero! Ma cosa succede al numero dei crediti, dopo essere stato decrementato di un unità? Bene, eseguendo il programma modificato il numero dei crediti torna immediatamente al valore massimo impostato con i dip-switch! Eppure avendo sottratto 1 da 0 il byte con il numero dei crediti espresso in BCD deve essere passato a 99. Senza neppure disassemblare altro, si può dedurre che deve esserci un controllo del numero di crediti con il massimo selezionato tramite i dip-switch, che reimposta il numero dei crediti con quest'ultimo valore in caso di superamento.
E` possibile che tale controllo sia eseguito ripetutamente per garantire che il numero dei crediti non superi mai il massimo: questa potrebbe essere stata una soluzione a un bug presente nei flipper Bally più vecchi, che consentiva di vincere un numero di crediti superiore al massimo. Forse Federico Croci vorrà raccontarvi come si faceva ;-)
Riassumendo, modificando solo due byte si ottiene una modalità free-play perfettamente funzionante!
Passiamo ora alla seconda modifica, ovvero i punteggi a sei cifre "vere". L'idea era semplice: trovare tutti gli "addendi" al punteggio, e dividerli per dieci. Speravo che tutto ciò che riguarda l'incremento dei punteggi fosse posizionato nella ROM U6, in modo che questa modifica potesse funzionare anche per gli altri flipper. Invece non è proprio così, però in U6 ci sono alcune routine importanti:
- all'indirizzo $xxxx, una routine che somma al punteggio del giocatore corrente (specificato da un indice nella locazione $yyyy) il valore del registro x;
- tre routine che, richiamando quella precedente, servono per sommare delle sequenze di 10, 100 e 1000 punti al punteggio del giocatore corrente. Se ci avete fatto caso, in questi flipper certi punteggi vengono assegnati sommando velocemente delle frazioni del valore da aggiungere; questo è il compito di tali routine, eseguite durante il loop principale; ciascuna aggiunge 10, 100 o 1000 finchè il contenuto di determinate locazioni di memoria, impostate al momento dell'assegnazione del punteggio e decrementate ad ogni iterazione, è maggiore di zero.
per quanto riguarda queste routine, i punti del codice dove si trovano gli addendi da dividere per 10 sono i seguenti:
$5cc1: ldx #$0010 ($ce $00 $10) $5cd4: ldx #$0100 ($ce $01 $00) $5ce7: ldx #$1000 ($ce $10 $00)
che devono diventare:
$5cc1: ldx #$0001 ($ce $00 $01) $5cd4: ldx #$0010 ($ce $00 $10) $5ce7: ldx #$0100 ($ce $01 $00)
purtroppo questo non è sufficiente. Dopo diverse prove, ho verificato che ci sono altre due situazioni dove i punteggi non vengono assegnati tramite tali routine. Si tratta dei punteggi del bersaglio Clone Chamber, e del conteggio dei bonus a fine pallina. Anche qui l'assegnazione avviene a multipli di 1000, e i punti in cui intervenire si trovano nelle ROM U1 e U2, rispettivamente:
$117f: ldx #$1000 ($ce $10 $00) $529b: ldx #$1000 ($ce $10 $00)
che devono diventare:
$117f: ldx #$0100 ($ce $01 $00) $529b: ldx #$0100 ($ce $01 $00)
A questo punto lo Space Invaders è completamente "ridimensionato", e azzerare il punteggio ora non è affatto uno scherzo :-) C'è un curioso effetto collaterale: poichè il punteggio più piccolo assegnato è 5 punti, mentre il numero estratto per il "match" a fine partita è sempre un multiplo di 10 da 0 a 90, la probabilità di vincere una partita passa a 1/20. Se qualcuno volesse proprio ripristinare la probablità originale.. beh, gli lascio trovare le modifiche necessarie per esercizio ;-)