The Commodore Amiga and DAAD (part 2)

Uto
6 min readMay 12, 2023

--

In the previous post I shared how I managed to modify the Amiga interpreter to allow DAAD to use full Amiga palette (4096 colours) in games, instead of being limited to the Atari ST one (512 colours).

But I didn’t stop there. Once I understood how DAAD interpreter was executing P-code (the DAAD “virtual machine” bytecode), I could run code condact by condact, so I decided to run EXTERN step by step. This is what I found:

Condacts code in the Amiga interpreter

Basically, when DAAD picks a new condact to execute, it prepares some CPU records to point to specific places, and then launches the specific condact subroutine. The Motorola 68000 CPU has several multiple purpose records, which are basically D0-D7 and A0 to A7. The Dx registers are often used to save values, and the Ax registers keep memory addresses.

So DAAD prepares some records, and then jumps to the specific condact code. When that code is called:

  • A0 register contains the address where the location of the object specified in condact’s first parameter is stored
  • A1 register contains the address where the flag specified by condact’s first parameter is stored.
  • A2 points to the current position of execution within that DDB, it’s a kind of PC register of de P-machine. It the Z80 machines, BC register works the same.
  • A6 register contains the address where the DDB is stored in RAM. Please notice that although Amiga databases start at address 0x0000, the DDB file can actually be stored anywhere in the Amiga memory.
  • So A6 + A2 points to the real address of execution in the p-code.

When everything is ready, the subroutine is called, and when condact subroutine is called, A6+A2 points to byte just after the condact opcode (which would be the first parameter if the condact has any parameters, or the next condact opcode if the condact has no parameters, like REDO or OK)

The EXTERN routine

Once DAAD has prepared all that, it calls the specific condact routine, this is what happens when that routine is EXTERN routine:

First that routine does is just increaseing A2 (so it points to second parameter in the EXTERN call, then it calls a subroutine that, sadly, just returns immediatly (there is a RTS in the address called).

So yes, the EXTERN code is basically shortcircuited to do nothing but moving the pointer properly to skip the second parameter.

That was expected though. The documentation found so far says the 16 bit interpreters use a “LINK” feature instead of running code from the DDB like the 8 bit interpreters do. Sadly, that is the only information we have today so there is no known way to use EXTERN condact in 16 bit interpreters. Although the creation of PCDAAD, the new DOS interpreter, allows using EXTERN and it’s documented, Amiga and Atari ST are unable to use that condact due to lack of information about how to use that LINK feature.

So well, I decided to find out how to “reactivate” the 8 bit style EXTERNs in the 16 bit interpreter, and I found how:

The DRC compiler includes the code added by the #extern directive in the DDB file, just as old DC does, and first #extern always end up at address 0x003c within the DDB, if you add it before anything else. So what I needed was replacing that code in interpreter that branches to a pointless empty routine, with something that jumps to the DDB address plus 0x003c, that is to (A6) + 0x3C.

This is the original code of the EXTERN routine:

The old code, with that bsr.w that jumps to an empty subroutine
Increment A2 to point to second parameter, jump to empty subroutine, then jump to next condact.

We are lucky 68000 has so many addressing modes, because there is an instruction that does exectly what I wanted: JSR $3C(a6). So this would be the new code:

Now, instead of jumping to pointless subroutine, we jump to offset 0x003c in the DDB

And yes, I was debugging with the patched interpreter, added a #extern directive with a routine with a couple of NOPs and a RTS, and I saw how the debugger was jumping to the code inside the DDB file, and running over those NOPs :-)

In this case though, we are not that lucky as with the palette patch, and the relative jumps in the original BSR call were different for EDI1 and SDI1 interpreter, so patches differ:

  • To patch SDI you have to find 528A 6100 1710 6000 f95A and replace 6100 1710 with 4EAE 003C
  • To pacth EDI you have to find 528A 6100 1638 6000 f95A and replace 6100 1638 with 4EAE 003C

Maybe the patch can be done better, and properly pick the extern PTR from the DDB header, so instead of a fixed 0x3C, jump is made to wherever the extern code is, but well, for the time being, this is enough.

What about PC and AtariST?

Well, I won’t be checking how it works with DOS interpreter, as new PCDAAD interpreter can already handle EXTERNS in the 8 bit style. Working with the legacy one may be possible, but is not interesting for me.

I have plans to check the Atari interpreter though, and then, if successful, maybe I may include some MALUVA features in the Amiga and ST interpreters, like the extended text pseudocondacts (XMES, XPART).

Patching other interpreters

Warning: way too technical information follows.

Patching other interpreters (EDI2/SDI2 and EDI3/SDI3) is matter of finding the same code: these are the steps to do it with WinUAE:

  • Create a game where its first entry in process 0 starts by AT 0 NOTAT 1 AT 0 NOTAT 1 EXTERN 2 3 REDO
  • Run the game (it enters and endless loop). When running, press Shift+F12 to enter the debugger. Type xx[enter] command to change to GUI. Type command S “amigaram.bin” 0 100000[enter], to get the Amiga RAM saved to a file.
  • Then, open both the DDB file in your game, and amigaram.bin file in your favorite hex editor, and find the header DDB file within the big file, to know where the DDB is located.
  • Again, search the first apparition of 00 00 11 11 00 00 11 11 3D 02 03 in the amigaram.bin file that is after the DDB address. Note the address down. By the way, you were searching for AT 0 NOTAT 1, etc.
  • Go back to the debugger and type “w 1 <address> 20 R F”, where address is the address you noted down (i.e. “w 1 30A78 20 R F”). That will make the execution stop when code reads the DDB in the area of those AT/NOTAT/AT/NOTAT. Make the game start again by typing g[enter].
  • When the debugger stops again, it will be at a CMP $FF opcode. That opcode is the one just after picking the condact opcode, and store it at D0, so if you look at D0 you would see the condact that has just been picked. Type “w 1” command to remove the memory breakpoint you added, right click in the “cpm” line and add a normal breakpoint using the menu (toggle breakpoint).
  • Now, everytime you press “g” you run a condact, do it until you see D= value is 3D (EXTERN), then is time to run step by step. Run steps using “t[enter]” until you find a jp (4). It may take about 20 “t” to get there. Then type “t” one last time and you’ll get to the EXTERN routine code. —
  • Now you’ll probably find the ADDAQ, the BSR and the BRA. Note down the bytes around that BSR, and then find them in the binary. If found only once, it’s time to replace the BSR code 6100 xxxx with 4EAE 003C. If you find that sequence more than once, just take more values around the area to reduce the chances of a random wrong match.

That should get the interpreter properly patched .

If case you need to do this a few times, please notice if the game starts just as the Amiga emulation starts (like DAAD-Ready does), the DDB will be placed almost at the same address, but not exactly the same, so if you noted the address where the AT 0 was, then type “m <address> 3” and try to find where 0000 1111 0000 1111 is this time, then create the “w” memory breakpoint with that address, and skip creating the RAM file.

--

--

Uto

Developing indie interactive fiction and IF engines since 1984