Skip to content

Making ADPATCH patches

Peter De Wachter edited this page Mar 9, 2020 · 3 revisions

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 :)

Some Adlib Theory

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:

  1. Reset both timers by writing 60h to register 4.
  2. Enable the interrupts by writing 80h to register 4. NOTE: this must be a separate step from number 1.
  3. Read the status register (port 388h). Store the result.
  4. Write FFh to register 2 (Timer 1).
  5. Start timer 1 by writing 21h to register 4.
  6. Delay for at least 80 microseconds.
  7. Read the status register (port 388h). Store the result.
  8. Reset both timers and interrupts (see steps 1 and 2).
  9. 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.

Example: Jill of the Jungle

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.

Rebuilding ADPATCH

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.

Clone this wiki locally