-
Notifications
You must be signed in to change notification settings - Fork 7
Making ADPATCH patches
To create ADPATCH patches you need to have a basic understanding how x86 machine code works. You don't need to be an expert, you just need to be able to figure out what happens in short fragments.
A good way of practicing is to take the source code of some old game (here's a good collection), figure out how the sound functions work, and then try to find the equivalent x86 code in the published EXE. The rest of this page will assume that you don't have access to the game's source, which is more typical :)
Here's a classic text file about Adlib programming:
http://www.shipbrook.net/jeff/sb.html
You don't need to read the whole thing. There are two important bits. First, how to access the Adlib card:
The sound card is programmed by sending data to its internal registers via its two I/O ports:
- 0388 (hex) - Address/Status port (R/W)
- 0389 (hex) - Data port (W/O)
After writing to the register port, you must wait twelve cycles before sending the data; after writing the data, eighty-four cycles must elapse before any other sound card operation may be performed.
So we expect to find a routine that writes to port 0x388, delays a bit, writes to port 0x398 and delays a bit more.
Second, the procedure to detect Adlib cards:
According to the AdLib manual, the 'official' method of checking for a sound card is as follows:
- Reset both timers by writing 60h to register 4.
- Enable the interrupts by writing 80h to register 4. NOTE: this must be a separate step from number 1.
- Read the status register (port 388h). Store the result.
- Write FFh to register 2 (Timer 1).
- Start timer 1 by writing 21h to register 4.
- Delay for at least 80 microseconds.
- Read the status register (port 388h). Store the result.
- Reset both timers and interrupts (see steps 1 and 2).
- Test the stored results of steps 3 and 7 by ANDing them with E0h. The result of step 3 should be 00h, and the result of step 7 should be C0h. If both are correct, an AdLib-compatible board is installed in the computer.
So: a whole bunch of bunch of Adlib I/O and in the end two values are
ANDed with 0xE0. This last bit is distinctive enough that you can
almost always find this routine code by simply searching for the pattern
and .*,0xe0
.
Let's patch Jill of the Jungle v1.2 shareware, downloaded from https://www.classicdosgames.com/game/Jill_of_the_Jungle.html
It doesn't look like this game stores its sound drivers in separate files, so we start with disassembling the main executable, JILL.EXE:
ndisasm JILL.EXE
ndisasm is the barebones disassembler that comes with NASM. I find it's good enough. If you want something fancier, you could try the freeware version of IDA
Let's search for the Adlib port: "0x388". The first hit is in a long routine:
00020381 53 push bx
00020382 52 push dx
00020383 BA8803 mov dx,0x388
00020386 B004 mov al,0x4
00020388 EE out dx,al
00020389 E8E6FF call 0x372
0002038C BA8903 mov dx,0x389
0002038F B060 mov al,0x60
00020391 EE out dx,al
00020392 E8DDFF call 0x372
00020395 B080 mov al,0x80
00020397 EE out dx,al
00020398 E8D7FF call 0x372
0002039B BA8803 mov dx,0x388
0002039E EC in al,dx
0002039F 8AD8 mov bl,al
000203A1 B002 mov al,0x2
000203A3 EE out dx,al
000203A4 E8CBFF call 0x372
000203A7 BA8903 mov dx,0x389
000203AA B0FF mov al,0xff
000203AC EE out dx,al
000203AD E8C2FF call 0x372
000203B0 BA8803 mov dx,0x388
000203B3 B004 mov al,0x4
000203B5 EE out dx,al
000203B6 E8B9FF call 0x372
000203B9 BA8903 mov dx,0x389
000203BC B021 mov al,0x21
000203BE EE out dx,al
000203BF E8B0FF call 0x372
000203C2 BA8803 mov dx,0x388
000203C5 EC in al,dx
000203C6 8AF8 mov bh,al
000203C8 B004 mov al,0x4
000203CA EE out dx,al
000203CB E8A4FF call 0x372
000203CE BA8903 mov dx,0x389
000203D1 B060 mov al,0x60
000203D3 EE out dx,al
000203D4 E89BFF call 0x372
000203D7 BA8903 mov dx,0x389
000203DA B080 mov al,0x80
000203DC EE out dx,al
000203DD E892FF call 0x372
000203E0 81E3E0E0 and bx,0xe0e0
000203E4 22FB and bh,bl
000203E6 33C0 xor ax,ax
000203E8 80FF00 cmp bh,0x0
000203EB 7501 jnz 0x3ee
000203ED 40 inc ax
000203EE 5A pop dx
000203EF 5B pop bx
000203F0 F8 clc
000203F1 C3 ret
(Ok, this is a long fragment, but there are no loops, so it's still not too bad.)
The values that are assigned to the al
register match what is described
above for the Adlib installation check (04/60/02/FF/etc). So we've found
the Adlib detection routine.
The only thing we need to do here is to patch the test at the end. (The I/O instructions can remain, they're harmless.) The code doesn't quite match the procedure from the textfile, but it seems it returns with AX=1 to indicate the presence of an Adlib card. There's no way to detect an OPL2LPT, so we just force the routine to succeed:
---
name: Epic Megagames / Jill of the Jungle Adlib detection
find: |
and bx, 0xE0E0
and bh, bl
xor ax, ax
cmp bh, 0
jnz L1
inc ax
L1:
replace: |
mov ax, 1
We search for a bit more code than strictly necessary to reduce the
chance of false positives. Note that we can use a label to write the
jnz
instruction.
If you'd try this, you'd find this patch doesn't actually apply. The reason is esoteric: some x86 instructions have multiple encodings, and in this case NASM didn't generate the encoding that was used in Jill. Our only recourse is to specify the bytes literally:
---
name: Epic Megagames / Jill of the Jungle Adlib detection
find: |
and bx, 0xE0E0
db 0x22, 0xFB ; and bh, bl
db 0x33, 0xC0 ; xor ax, ax
cmp bh, 0
jnz L1
inc ax
L1:
replace: |
mov ax, 1
(It would perhaps be easier to just use a hex dump as the search pattern, but I find it convenient to have the symbolic code.)
Note: For more complicated stuff you can write search patterns
directly in Ragel syntax. That's not described here, but look at
ail.yml
for an example.
Next occurrence of "0x388" is in this routine:
000209CB 50 push ax
000209CC 53 push bx
000209CD 51 push cx
000209CE 52 push dx
000209CF BA8803 mov dx,0x388
000209D2 EE out dx,al
000209D3 B96400 mov cx,0x64
000209D6 EC in al,dx
000209D7 E2FD loop 0x9d6
000209D9 42 inc dx
000209DA 86C4 xchg al,ah
000209DC EE out dx,al
000209DD B91E00 mov cx,0x1e
000209E0 E540 in ax,0x40
000209E2 8BD8 mov bx,ax
000209E4 E540 in ax,0x40
000209E6 3BC3 cmp ax,bx
000209E8 74FA jz 0x9e4
000209EA E2F4 loop 0x9e0
000209EC 5A pop dx
000209ED 59 pop cx
000209EE 5B pop bx
000209EF 58 pop ax
000209F0 C3 ret
A write to port 0x388, a delay loop, a write to port 0x389 and another
delay loop: this is the Adlib output routine. The register number is
passed in the al
register, and the value in the ah
register.
We have just enough room to fit the standard replacement code
asm/standard.s
. The replacement code doesn't touch the bx register,
so we can remove the push bx
and pop bx
opcodes (and we need those
two bytes to make our code fit).
Alternatively, we could have used asm/compact.s
, which is a few
bytes shorter but runs slower.
---
name: Epic Megagames / Jill of the Jungle Adlib output
find: |
push bx
push cx
push dx
mov dx, 0x388
out dx, al
mov cx, 0x64
L1: in al, dx
loop L1
inc dx
xchg al, ah
out dx, al
mov cx, 0x1E
L2: in ax, 0x40
db 0x8B, 0xD8 ; mov bx, ax
L3: in ax, 0x40
db 0x3B, 0xC3 ; cmp ax, bx
jz L3
loop L2
pop dx
pop cx
pop bx
replace: |
push cx
push dx
%define ARG1 al
%define ARG2 ah
%include 'standard.s'
pop dx
pop cx
There's no more occurrences of 0x388, so we're probably done. Time to rebuild ADPATCH and test the patched game.
To build ADPATCH, you need these tools:
On Linux all this should be easy to install from your package manager. On Windows it looks like you can get everything using [MSYS2][http://www.msys2.org/], but I haven't tried this (feedback welcome).
To build a 16-bit DOS executable, you'll need the Open Watcom compiler. GCC will do if that's not needed.
Run ./build.sh
for OpenWatcom or ./buildgcc.sh
for GCC.