Spectrum BASIC cu rutină NMI

În continuare o scurtă prezentare a unei rutine NMI pe care am scris-o pentru folosire cu BASIC-ul standard Spectrum.

În momentul activării semnalului NMI la Z80, procesorul salvează pe stivă adresa curentă de execuție și execută un salt la adresa $0066. În versiunea originală de BASIC Spectrum, la această adresă se află o scurtă rutină, prezentată mai jos, care, pe scurt, verifică o valoare pe 2 octeți stocată la adresa $5CB0, care dacă este $0000 determină un salt la adresa $0000, adică mai simplu spus un reset.

Rutina originală NMI Spectrum



La vremea respectivă (prima parte a anilor '90) am scris o rutină pentru NMI pe care am folosit-o la copierea cîtorva jocuri de pe bandă magnetică pe dischetă în format executabil CP/M. Am încorporat-o în codul original BASIC Spectrum, în spațiul liber din domeniul $386E-$3CFF. Rutina este încorporată începînd cu adresa $386F, iar adresa ei de start este $3880. Pentru a o face efectivă, mai e necesară încă o modificare a codului original BASIC: începînd cu adresa $0066, trei octeți trebuie modificați pentru a compune o instrucțiune de salt la începutul rutinei NMI. Deci începînd cu adresa $0066, trebuie scriși următorii trei octeți: $C3 $80 $38.

Rutina NMI scrisă de mine



În stînga listingul rutinei originale scrisă prin anii '90, salvat ca screenshot din OPUS, în dreapta listingul actualizat cu o completare pentru salvarea stării bistabilului de întreruperi din Z80.



Văzusem deja metoda „tradițională” folosită de alții, dar mi s-a părut de-a dreptul imbecilă. Nici măcar nu se osteneau să salveze starea completă a procesorului, iar CP/M Spectrum Loader-ul se interfața direct cu punctul de restart al aplicației salvat la NMI, fără să mai restaureze corect starea programului.

Eu am abordat problema cu ideea de a face totul corect de la cap la coadă. Pentru a restaura corect un punct de restart al unei aplicații Spectrum „înghețate” cu NMI pe CoBra, starea procesorului va trebui refăcută în configurația BASIC, după terminarea execuției CP/M Spectrum Loader-ului și după terminarea execuției rutinei de încărcare BASIC din BOOT ROM (pentru că din CP/M nu se trece direct în BASIC, ci mai întîi prin BOOT ROM, în configurația de BOOT). Încercarea de a restaura starea procesorului direct din CP/M Spectrum Loader va eșua după ce instrucțiuni intermediare vor trebui executate înainte de a ajunge la punctul de restart al aplicației, schimbînd valorile regiștrilor procesor. Redau mai jos conținutul dezasamblat al CP/M Spectrum Loader-ului parvenit din „mediul ambiant” de atunci, alături de cel folosit de mine.

Ce au folosit alții

Ce am folosit eu

Metoda populară folosea un Spectrum Loader care avea la început niște locații rezervate pentru adresa de încărcare a codului salvat cu NMI, lungimea lui, adresa de (re)start din BASIC și adresa stivei la momentul NMI. Afară de astea, Loader-ul mai încarcă registrul I cu $3F, IY cu $5C3A și fixează modul de întreruperi IM 1, plecînd de la ipoteza stupidă că astea ar fi valori „universal valabile” cu care ar trebui „să meargă”. S-a mers pe principiul comodității și vitezei, trebuind completate doar cele 4 valori pe 16 biți de la început ale codului deja asamblat, înainte de a-l alipi pur și simplu în fața codului aplicației salvate cu NMI. Că bineînțeles treaba trebuia făcută cît mai repede ca să meargă vînzarea de „soft” cît mai rentabil. Afară de asta, cine a scris bucata asta de cod a făcut un OUT la adresa de port a registrului de stare 8272, care nu poate fi scris, ci numai citit (!!). Plus că LD A,$00 de la linia 017A este total inutil din moment ce, două linii mai sus, XOR A deja a făcut același lucru. Sună a caz clasic de scriere de cod sub influența lipsei de neuroni... CP/M Loader-ul folosit de mine trebuie folosit în conjuncție cu o altă rutină, pe care am denumit-o CPU RESTORE. Ea reface complet starea procesorului la NMI și trebuie inserată manual într-un spațiu liber din aplicația „crăcuită”, care spațiu liber trebuie mai întîi găsit. Loader-ul nu încearcă să restaureze nici un registru procesor cu valoarea de la momentul NMI, ci doar păstrează în HL adresa de start din BASIC, care trebuie mai întîi determinată după găsirea unui spațiu liber în codul aplicației unde CPU RESTORE poate fi introdusă, care la rîndul ei restaurează complet starea procesor. Problema e că e o metodă mai muncitorească, deci mai lentă, întrucît codul CPU RESTORE (cu valorile corespunzătoare completate) trebuie introdus manual în spațiul liber respectiv al aplicației „crăcuite”. Dar șansele de restaurare corectă a aplicației Spectrum sînt mult mai mari.


Am scris rutina NMI plecînd de la ideea că vreau să salvez și apoi să restaurez starea cît mai completă a procesorului împreună cu cei 48 KB de memorie utilizator, fără să apelez la ciobănisme de genul salvării regiștrilor procesor în memoria video unde ar corupe ecranul original al jocului respectiv. Concluzia logică este că starea procesorului (regiștrii) trebuie salvată în altă parte decît în cei 48 KB de memorie ocupați de programul „hăcuit”. Cu alte cuvinte, va trebui salvată undeva în memoria BASIC, care se nimerește că la CoBra poate fi făcută R/W cu un comutator inclus în circuitul de configurare și selecție.

La activarea semnalului NMI, rutina va fi apelată, salvînd starea procesorului într-o zonă de 27 de octeți situată imediat după rutina NMI propriu-zisă și intrînd într-un ciclu de interogare a tastaturii care așteaptă apăsarea uneia din 4 taste: F1, F2, F3 sau F4.

Funcțiile celor 4 taste sînt următoarele:

F1 - salvează ecranul în format Spectrum "Bytes", cu un header standard, sub numele "CRACKER".

F2 - salvează un bloc de 27 octeți fără header (cu flag byte $00) conținînd starea procesorului, urmat de alt bloc de 48 KB fără header (cu flag byte $FF) conținînd întreaga memorie utilizator ($4000-$FFFF).

F3 - salvează un bloc de 27 octeți fără header (cu flag byte $00) conținînd starea procesorului, urmat de alt bloc de 42240 octeți fără header (cu flag byte $FF) conținînd zona de memorie ($5B00-$FFFF) de 48 KB minus zona de memorie video cu tot cu atribute de culoare ($4000-$5AFF).

F4 - reia execuția programului întrerupt cu NMI.

Modul de utilizare a acestei rutine este următorul:
ATENȚIE! Pe toată perioada utilizării rutinei NMI, memoria BASIC trebuie să rămînă R/W.


Cu toate precauțiile posibile, atunci cînd un program este „hăcuit” cu metoda NMI, două informații se vor pierde inevitabil, dintre care una este absolut irecuperabilă:
Pentru copierea aplicației astfel „sparte” pe disc, vor trebui copiate blocurile de cod (starea CPU plus codul aplicației) din format audio în format de fișier CP/M (cu KID.COM), după care va trebui asamblat un fișier executabil CP/M din două bucăți separate: la început o bucată de cod de 256 octeți numită „CP/M Spectrum Loader”, după care codul propriu-zis al aplicației (48 KB sau 41.25 KB, funcție de cum a fost salvat codul din rutina NMI).

CP/M Spectrum Loader are rolul de a muta codul aplicației în zona lui normală de lucru în BASIC și de a încărca Spectrum BASIC în DRAM#0 după care se schimbă configurația hardware și se execută un salt la punctul de restartare a aplicației.

Dar lucrurile sînt puțin mai complicate aici, întrucît nu se execută direct un salt la adresa salvată pe stivă de NMI. Înainte de un salt la acea adresă, trebuie restaurată starea procesorului de la momentul activării NMI. Aceasta se face cu o rutină (CPU RESTORE) care va trebui inserată manual undeva într-un spațiu liber care trebuie găsit în blocul de memorie salvat cu tot cu codul aplicației. Rutina arată cam așa:

Rutina CPU RESTORE - sursa în Assembler