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.