Reply 20 of 22, by OttoPS
Hi there! Sorry for the necroposting! But I bring an explanation and a workaround for the AWEUTIL bug on fast CPUs when using GM or MT-32 emulation.
To explain the bug, it is also necessary to explain how the emulation architecture works, using a mixed hardware/software system...
AWEUTIL uses a very dirty low-level trick: when the sound card receives a data byte through the MIDI port (usually 0x330), it generates an NMI, a non-maskable CPU interrupt. This means it can interrupt normal CPU execution even with maskable interrupts disabled. This way, the card requests that the byte be read as soon as possible.
Inside the NMI handler, the process is fairly simple. It checks whether a byte is available through MIDI base + 1 (usually 0x331), then reads the byte through MIDI base (usually 0x330) and generates the corresponding commands for the EMU8000.
In theory, this should work without problems, but there appears to be a timing bug: the NMI may reach the handler before the MIDI feedback data is available for reading. In fact, looking at the AWEUTIL binary, Creative hardcoded five consecutive attempts to check whether data is available. But this mediocre solution depends on CPU and bus timing.
The problem gets worse because, instead of silently discarding the NMI and potentially losing a MIDI byte, it chains to the original NMI handler. On a typical DOS PC, this is a BIOS hardware-error handler, which may stop the machine.
My software workaround is simple. First, increase the number of attempts from 5 to 50. Second, instead of chaining to the original NMI handler when the attempts run out, silently clear the NMI source, restore the execution context and leave the handler with IRET. Luckily, Creative already provided that last part in its own NMI handler.
This patch does not fix CPU speed problems in the games themselves or compatibility problems with protected-mode games. Solutions for those cases already exist.
The offsets and machine code below apply to the AWEUTIL binary I examined. Check the original bytes before applying the patch to another version.
ORIGINAL DATA
; DX = MIDI base + 1, usually 0x331in al, dxtest al, 80hjz 13EFh ; attempt #1: check whether MIDI data is availablein al, dxtest al, 80hjz 13EFh ; attempt #2in al, dxtest al, 80hjz 13EFh ; attempt #3in al, dxtest al, 80hjz 13EFh ; attempt #4in al, dxtest al, 80hjnz 1441h ; attempt #5: if unavailable, chain to original NMI handler
Binary machine code at file offset 0x12D6:
EC A8 80 74 14 EC A8 80 74 0F EC A8 80 74 0A EC A8 80 74 05 EC A8 80 75 52
PATCH DATA
Assembly at file offset 0x12D6 (runtime offset 0x13D6):
push cxmov cx, 0032h ; 50 attempts, decimalwait_midi_data:in al, dxtest al, 80hloopnz wait_midi_datapop cxjz 13EFh ; data available: processjmp 1421h ; timeout: clear source, restore context, IRET
Binary machine code at file offset 0x12D6:
51 B9 32 00 EC A8 80 E0 FB 59 74 0D EB 3D
The patch occupies 14 bytes. The remaining original bytes do not need to be overwritten because neither execution path reaches them.