Spectrum BASIC with NMI routine

Next, a short presentation of a NMI routine I wrote in order to use it with the standard Spectrum BASIC.

When the Z80 NMI signal is activated, the CPU pushes on stack the current execution address and then executes a jump to address $0066. In the original version of Spectrum BASIC, this address is the start of a short routine, shown below, which, in brief, checks a 2-byte value stored at $5CB0, which if is $0000 causes a jump to $0000, which simply said is a reset.

The original Spectrum NMI Routine

Back in the day (the early 90's) I wrote a NMI routine which I used for transferring a few games from magnetic tape to floppy disk in CP/M executable format. I included it in the original Spectrum BASIC code, in the free space available between $386E-$3CFF. The routine is embedded starting at $386F, and its entry point is at $3880. In order to make it effective, there is also needed another modification of the original BASIC code: starting at $0066, three bytes must be changed in order to make a jump instruction to the start of my NMI routine. So starting at $0066, the following 3 bytes must be stored: $C3 $80 $38.

The NMI routine written by me

On the left, the listing of the original routine written in the 90's, saved as screenshot in OPUS, on the right, the updated listing with an addition to save the Z80 interrupt flip-flop status.

I had already seen the "traditional" method used by others, but it seemed to me downright imbecil. The did not even bother to save the complete CPU state, and the CP/M Spectrum Loader was directly interfaced with the restart point of the application saved at NMI, without correctly restoring the state of the program.

My approach was to do everything the right way from beginning to the end. In order to correctly restore a restart point of a Spectrum application "frozen" by NMI on CoBra, the CPU state will have to be restored under BASIC hardware configuration, after the execution of the CP/M Spectrum Loader and after the execution of the BASIC loading routine in BOOT ROM (because from CP/M we cannot get directly to BASIC, but first through BOOT ROM, in the BOOT hardware configuration). An attempt to restore the CPU state directly from the CP/M Spectrum Loader will fail after intermediary instructions will have to be executed before getting to the restart point of the application, changing the contents of the CPU registers. I'm showing below the disassembly of the CP/M Spectrum Loader obtained from the "environment" of those days, side by side with the one I used.

What others used

What I used

The "popular" method was using a Spectrum Loader which had at the beginning a few reserved locations for the storage of the start address of the block saved by NMI, its size, the (re)start address in BASIC and the stack address at NMI. Aside from these, the Loader also stores $3F in the I register, $5C3A in IY and sets the interrupt mode to IM 1, stupidly assuming these would be "universal" values which would automatically "work". They went by convenience and speed, thus only the four 16-bit values at the beginning of the assembled code having to be filled out, before simply pasting it in front of the code of the application previously saved by NMI. Because the job had of course to be done as quick as possible so that the "software" sale can be as profitable as possible. Aside from this, whoever wrote this piece of code did an OUT to the 8272 Status Register port address, which register is R/O (!!). Plus the instruction "LD A,$00" on line 017A is completely useless since, two lines above, "XOR A" already did the same thing. Sounds like a typical case of code writing under the influence of lack of neurons... The CP/M Loader I wrote must be used together with another routine, which I called CPU RESTORE. It completely restores the CPU state at the time of NMI and must be manually inserted in some free space in the "cracked" application, which free space must be found first. The Loader does not try to restore any CPU register to its value at the time of NMI, but only keeps the BASIC start address in HL, which must first be determined after finding some free space in the application's code, where CPU RESTORE can be inserted, which in turn completely restores the CPU state. The problem is, this method is more laborious, therefore slower, because the CPU RESTORE code (with the proper values filled out) must be manually inserted in that free space of the "cracked" application. But the chances to correctly restore the Spectrum application are much higher.

I started writing the NMI routine with the idea of saving and then restoring the CPU state as completely as possible, along with the 48 KB of user memory area, without resorting to some moronism like saving the CPU registers in the video memory, where they would corrupt the original screen of the Spectrum application. The logical conclusion is that the CPU state (registers) must be saved somewhere else than the 48 KB of user memory taken up by the application being "cracked". In other words, it will have to be saved somewhere in the BASIC memory area, which fortunately in CoBra can be made R/W using a switch included in the Configurator/Selector Circuit.

When NMI is activated, the routine will be called, saving the CPU state in a 27-byte memory area located right after the NMI routine itself and a keyboard reading loop will be entered, waiting for one of the keys F1, F2, F3 or F4 to be pressed.

The functions of these 4 keys are as follows:

F1 - saves the screen in "Bytes" format, with a standard header standard, under the name of "CRACKER".

F2 - saves a headerless 27-byte data block (flag byte $00) containing the CPU state, followed by another headerless 48 KB data block (flag byte $FF) containing the entire user memory area ($4000-$FFFF).

F3 - saves a headerless 27-byte data block (flag byte $00) containing the CPU state, followed by another headerless 42240 Byte data block (flag byte $FF) containing the 48 KB user memory area ($5B00-$FFFF) minus the video memory area including the color attributes ($4000-$5AFF).

F4 - resumes execution of the program interrupted by NMI.

This NMI routine should be used as described next:
PLEASE NOTE! While using the NMI routine, the BASIC memory area must be kept R/W all the time.

With all the possible precautions, when a program is "hacked" using the NMI procedure, two pieces of information will be inevitably lost, of which one is completely non-recoverable:
In order to copy to disk the application cracked this way, the data blocks will have to be copied (the CPU state plus application code) from audio format to CP/M file format (using KID.COM), after which a CP/M executable file will have to be assembled by putting two pieces together: at the beginning a 256-byte piece of code called "CP/M Spectrum Loader", followed by the application actual code (48 KB or 41.25 KB, depending on how the code was saved from the NMI routine).

The purpose of the CP/M Spectrum Loader is to move the application code to its normal position under BASIC and to load Spectrum BASIC in DRAM#0, after which the hardware configuration is changed and a jump is made to the resuming point of the application.

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:

The CPU RESTORE routine - Assembler source