PEEK & POKE : Viaggio alla ricerca dell’immortalità a 8 bit

INTRODUZIONE

Da sempre la fenice rappresenta il sogno ad occhi aperti dell’immortalità. Nei primi anni 80 il tempo o il numero di vite limitato per i videogiochi arcade in salagiochi era considerato naturale ed anche nel genere roguelike o di narrativa interattiva per mainframe/home computer la morte del personaggio era considerata naturale ed ineluttabile. In seguito con gli home computer si passa da videogiochi in cui è il giocatore che con le sue azioni determina il trascorrere del tempo a giochi di destrezza dove gli eventi si susseguono inesorabili con le classiche tre vite. Questa volta però nella tranquillità delle mura domestiche e con giochi di difficoltà maggiore di quelli in salagiochi ritorna prepotente l’anelito all’immortalità, questa volta digitale, per cui il videogiocatore inizia a fantasticare pensando alla possibilità di avere più vite per progredire nel gioco o addirittura avere l’abilità di non morire mai. D’altronde ora ha tutto nelle sue mani, il computer ed il videogioco. All’inizio dell’era degli home computer i primi videogiochi erano disponibili in forma di lunghi listati in linguaggio Basic riportati nelle riviste di settore ed il giocatore che con ascetica pazienza li copiava nella memoria del computer analizzando il programma aveva la possibilità di modificare le istruzioni in modo da raggiungere il suo sogno proibito, solitamente dopo aver fatto del proprio meglio per progredire nel gioco senza successo.
La situazione cambia con il rilascio di videogiochi in linguaggio macchina: questi sono molto più complessi e luccicosi dei precedenti scritti in basic, non è più necessario scrivere nulla manualmente nella memoria del computer con il rischio di commettere errori, è sufficiente inserire il nastro magnetico nel lettore di cassette e aspettare qualche minuto il caricamento del videogioco che parte automaticamente. Tutto molto bello, ma il giocatore dopo un primo momento di beatitudine è assalito da un pensiero angosciante: non può più modificare il programma per ottenere le vite infinite. Prende il manuale del computer alla ricerca di una soluzione e rileggendo il manuale la sua attenzione si posa su un paio di istruzioni dal nome breve in linguaggio Basic, presenti su tutti i computer a 8 bit dell’epoca, a cui in precedenza probabilmente non aveva dato peso: le istruzioni PEEK e POKE. L’istruzione PEEK è indispensabile per leggere nella memoria del computer, ma l’istruzione POKE si presenta ai suoi occhi come la pietra filosofale in quanto permette di modificare i dati nella memoria del computer a proprio piacimento, è chiaro che la chiave dell’immortalità digitale è tutta in quella istruzione, ma a questo punto la domanda sorge spontanea: dove, cosa e come modificare i dati nella memoria del computer ? Il viaggio è solo all’inizio.
Oggi è naturale trovare informazioni per i videogiochi con l’aiuto di internet, ma nei primi anni 80 non c’è nulla di simile e l’unica fonte di informazioni sono le riviste come Crash, Your Sinclair o ZZap! solo per citarne alcune delle più note. Su queste riviste oltre le recensioni sono pubblicate le POKE dell’eterna giovinezza per alcuni videogiochi e nel 1992 il talentuoso hacker Richard Swann che collabora con Your Sinclair pubblica anche una guida in cui descrive come determinare i parametri per la funzione POKE. Le possibilità sembrano essere solo due: o si aspetta e si spera nella pubblicazione delle POKE del nostro videogioco preferito o bisogna cercare da soli. Questa è la storia ed il vissuto comune dei videogiocatori di quel tempo.
L’attuale tutorial si basa sul lavoro di un ragazzo che invece nel 1984 con la sola conoscenza del linguaggio basic e delle azioni di una manciata di istruzioni dello Z80 escogita un metodo euristico per cercare le vite infinite senza la necessità di disassemblare il codice del gioco.
La finalità del tutorial è di avvicinare i retrogamer all’assembly per mezzo dei videogiochi, il metodo euristico resta utile per definire i concetti e la logica di base, ma useremo strumenti per i computer attuali. Il tutorial è dedicato allo ZX Spectrum, ma il metodo può essere applicato a tutti i computer dell’epoca con gli acccorgimenti e gli strumenti appropriati.

IL METODO EURISTICO

Prima di tutto bisogna avere una chiara visione di insieme del problema e come sempre dal punto di vista operativo è importante formulare le domanda “giuste” e produrre le risposte appropriate.
Partiamo dal fatto che per un microprocessore un programma è formato da gruppi di istruzioni all’interno delle quali è presente un sottoinsieme che in base a determinate condizioni permette l’attivazione dei diversi gruppi. Consideriamo ora un qualsiasi personaggio di un videogioco:

In che COSA consiste la vita nella realtà di questo personaggio ?

Nell’assegnare al personaggio un parametro che nel corso del gioco può variare in seguito a specifici eventi ed è gestito da gruppi di istruzioni.

In che COSA consiste la morte nella realtà di questo personaggio ?

In un insieme di eventi che modificano quel parametro ed attivano altri gruppi di istruzioni che terminano il gioco.

Da un punto di vista operativo adesso abbiamo idea che cosa sia la vita e la morte del personaggio, ma c’è ancora qualche tassello da aggiungere al nostro rompicapo:

Il microprocessore come gestisce questo parametro ?

Il microprocessore gestisce questo parametro (come naturalmente tutti gli altri) tramite istruzioni e per motivi di velocità utilizza una memoria interna le cui unità si chiamano registri. Possiamo raggruppare le istruzioni in sottoinsiemi di istruzioni di caricamento dei valori nei registri, aritmetico/logiche e di salto. Per quanto riguarda i registri si possono raggruppare in sottoinsiemi di registri generali e speciali: i registri generali non hanno uno scopo specifico e sono utilizzati per eseguire le operazioni di calcolo relative al programma, i registri speciali sono di specifico ausilio alle attività interne del microprocessore durante l’esecuzione delle istruzioni. Porremo l’attenzione su due registri speciali coinvolti in tutte le operazioni logico-aritmetiche eseguite dalla A.L.U. (Arithmetic Logic Unit): il registro accumulatore presente in tutti i microprocessori ed il registro dei flag i cui singoli bit indicano lo stato del sistema di calcolo dopo l’esecuzione di una operazione logico-aritmetica. Ci sono altri registri speciali, ma per il nostro metodo questi rudimenti sul funzionamento di un microprocessore sono una buona base di partenza.

DOVE si trova il parametro e le istruzioni che lo gestiscono ?

Per funzionare il microprocessore utilizza due tipi di memorie, una che si può solo leggere detta R.O.M. (Read Only Memory) dove sono presenti le istruzioni per il corretto funzionamento del computer ed un’altra che l’utente può liberamente leggere e scrivere detta R.A.M. (Random Access Memory). il microprocessore assegna un indirizzo univoco ad ogni singolo elemento di queste memorie tramite il quale accede direttamente ai dati presenti. Il parametro e le istruzioni che gestiscono la vita del personaggio si trovano in R.A.M. Ora che abbiamo anche idea su come funziona un microprocessore resta da capire cosa e dove cercare da un punto di vista operativo.

COME cercare ?

Da quanto detto in precedenza il gioco è costituito dall’insieme di istruzioni e dati in R.A.M. gestiti dal programmatore. Chi scrive il programma formula inizialmente una visione generale del gioco ovvero ne descrive le funzionalità principali senza entrare nel dettaglio delle sue parti. Successivamente ogni parte del gioco viene perfezionata aggiungendo maggiori dettagli alla sua progettazione. Per mezzo di questa strategia di elaborazione detta Top-Down il programmatore stabilisce dove mettere il codice, dove sono allocate le risorse: sprite, testi, musica, effetti sonori ed in generale tutto ciò che è necessario per il gioco.
Invece noi ci troviamo nella situazione in cui abbiamo tutti i dettagli del gioco senza avere la conoscenza di dove siano allocate le risorse ed in R.A.M. vediamo solo un insieme di dati che possono essere qualsiasi cosa: istruzioni, sprite, testi, musica, etc. Sappiamo dove il codice ha inizio e conoscendo le istruzioni del microprocessore dai dettagli dobbiamo risalire alla visione generale del programmatore per capire dove si trova il parametro che rappresenta la vita del personaggio ed eventualmente come renderlo immortale. Questo è il percorso inverso di quello seguito dal programmatore e questa strategia di tipo Bottom-Up è detta reverse engineering. E’ complicato scrivere il gioco, ma è molto più complicato ricostruire la logica seguita dal programmatore a partire dai dati contenuti in R.A.M. Le metodologie Top-Down e Bottom-Up di elaborazione dell’informazione sono deterministiche, cioè gli eventi del gioco sono generati e gestiti in modo identificabile e preordinato e sono tali che ad una causa segue solo un dato effetto sia che si programmi il gioco sia che lo si analizzi. Il reverse engineering di un gioco è molto complicato anche per una persona esperta che conosce in dettaglio il microprocessore e questa può impiegare molte ore/giorni solo per cercare le vite infinite. Non ci sono alternative nel paradigma informatico. Per cercare di ridurre i tempi e semplificare la ricerca bisogna provare a cambiare paradigma.

Cosa indica l’espressione cambiare paradigma ?

Significa osservare il sistema da un diverso punto di vista, è un percorso da definire e percorrere con tentativi ed errori sino all’eventuale raggiungimento dell’obiettivo. Nel nostro caso di studio possiamo considerare il gioco come un flusso continuo di dati elaborati dal microprocessore all’interno dei quali cercare le condizioni, diciamo di pretrigger, che scatenano gli eventi di gestione della vita del personaggio. In particolare all’interno di questo insieme di dati cercheremo coppie “speciali” di dati che permettono di identificare le istruzioni di gestione del parametro cui siamo interessati. Questi dati sono semplici coppie di numeri a 8 bit e saremo noi a renderle “speciali” associando ad esse un criterio in quanto le semplici coppie di numeri possono essere di tutto da istruzioni a sprite, testi, musica, effetti sonori, etc. non necessariamente correlate alla gestione della vita del personaggio. Saremo noi a capire un passo alla volta se siamo sulla strada giusta. Questo è un procedimento euristico che può essere utilizzato da persone che hanno poca conoscenza del codice macchina/assembly del microprocessore e che con un buon intuito permette di trovare in pochi minuti/ore le vite infinite per il personaggio del videogioco.
E’ un pò come andare a pescare con la canna: l’abilità del pescatore è quella di riconoscere la buona zona pescosa con l’esca giusta, il mare sono i dati in R.A.M., l’esca sono le coppie di dati, la canna da pesca è il vostro intuito ed il pesce da pescare sono le vite infinite.

APPLICAZIONE DEL METODO EURISTICO

DeepSea LIVES HANDLER BOT
DeepSea SET LIVES HOOK BOT
DeepSea EVENT HANDLER HOOK BOT

Fig.1 – Gestione delle vite del personaggio
Fig.2 – Blocco SET LIVES
Fig.3 – Blocco EVENT HANDLER

Da quanto detto il generico gestore di vite del nostro personaggio può essere schematizzato con un diagramma a blocchi come riportato in Fig.1. Popoleremo i blocchi “SET LIVES” (FIg.2) e “EVENT HANDLER” (Fig.3) con le istruzioni assembly che andremo a pescare ed il criterio matematico che le caratterizza.

Sinora il discorso è stato generico, da questo momento sarà rivolto al microprocessore dello ZX Spectrum, lo Zilog Z80:

ZX Spectrum 48K Memory BOT

Fig.1 – La memoria dello ZX Spectrum 48K
Fig.2 – WIP
Fig.3 – WIP

Alla prossima puntata! 😉

Prima di lasciarvi vi indico un paio di documenti da sbirciare:
Zilog Z80 assembly language programming classic
Z80 by Simon Owen (simon@simonowen.com)

Cercate di relazionare i due documenti con quello che ho scritto affidandovi al vostro intuito …

eNJoy aND STay TuNeD WiTH uS!

Raffaele “MOS” Sanapo

DISCLAIMER
Questo è un tutorial con finalità didattiche e le informazioni sono fornite “così come sono”. I link ai contenuti verso siti web di terzi sono forniti soltanto per vostra comodità e informazione. Non c’è alcun controllo e non si assume alcuna responsabilità dei contenuti o siti web altrui che sono collegati. Si declina ogni responsabilità per qualsiasi perdita, costo, danno o altro diretto o indiretto derivante dall’uso del tutorial. L’uso delle informazioni ottenute o scaricate da questo tutorial è a vostro esclusivo rischio e pericolo. Tutti i nomi di prodotti, loghi e marchi sono di proprietà dei legittimi proprietari. Tutti i nomi di aziende, prodotti e servizi utilizzati in questo tutorial sono solo a scopo di identificativo. L’ uso di questi nomi, loghi e marchi non implica approvazione degli stessi.

Raffaele Sanapo

“We don't stop playing because we grow old; we grow old because we stop playing.” (George Bernard Shaw)

Share
Share

Utilizzando il sito, accetti l'utilizzo dei cookie da parte nostra. maggiori informazioni

Questo sito utilizza i cookie per fornire la migliore esperienza di navigazione possibile. Continuando a utilizzare questo sito senza modificare le impostazioni dei cookie, cliccando su "Accetta" o continuando la navigazione permetti il loro utilizzo.

Chiudi