I tried making some nibble copies of Origin Software’s Autoduel to run in an emulator, and of course it didn’t work. So I figured I’d try my hand again at boot tracing. Tricky stuff. There are a couple of hints out there about how to make the Autoduel disk copyable (see Computist #36, which refers back to the Ultima IV soft key in Computist #28, and “Toinet”‘s discussion of Ultima IV), but for the most part they don’t talk about what it’s doing, they just talk about the end result, what you need to change to defeat the copy protection. I will instead see if I can delve into the copy protection itself.
Using Virtual II’s Inspector, I managed to trace a bit down. It is kind of long and involved figuring out what everything does, even when it turns out to be innocuous. Let me just hit the highlights here.
When I try to boot the nibble copy, it just hangs. Why?
When it boots, it starts at $801, which loads in a few more sectors, and then jumps into it at $B700. The first kind of neat trick they did here is that the 26-byte program at $B700 takes everything in memory from $B71A onward and EORs it with $E6. So, you wouldn’t be able to find the code on the disk, because it’s all been (weakly) encrypted. Once the decryption is done, execution falls into the decrypted code at $B71A.
If we set the breakpoint at $B71A and let the decryption execute, we get much more sensible code there.
Now we can actually look at what’s happening. And I did, but I won’t go into all the gory details here. The code at $B71A basically sets a bunch of parameters (and disables Reset), calls $B793, and then goes to $9D82, which is surely a DOS 3.3-like entry point. So, the next bit of trickery to be concerned with is what happens at $B793, where the rest of DOS is loaded.
The instruction at $B793 is just a JMP to $B800, but the first thing that happens at $B800 is that it goes back and modifies $B793 so that it starts with a LDY $B7E5 instead of a JMP $B800. So $B793 will be a useful routine, in fact it is going to be where execution continues shortly.
After it’s “un-patched” $B793, a counter is set to 7, and the disk is checked to see if it is write protected. If it isn’t, execution is diverted to $B830, which heads off to $B76B. This is fatal, if we look at what $B76B does, it moves a chunk of code from $B779 to $E0 and executes it. The chunk of code that it moves there just wipes out RAM and hangs in an infinite loop.
So, that’s the source of the hang I was getting, triggered just by the fact that the disk was not write protected. But write protecting the disk doesn’t solve the problem, it just moves us on to the next stage of the copy protection.
If we pass the write protection test, the next thing it does is call $B83E, store the result, call $B83E, and compare the result with what we stored the first time. If we get the same result for each pair of calls, the counter is decremented, and it tries again. Once it has made seven attempts, we are again sent to the kill code at $B76B. So, now the question is, what is $B83E looking for and returning?
Looking at what’s happening here at the beginning, it’s pretty clear. It’s looking for address marks D5 AA 96 on the disk. Then it loads four more nibbles, but throws them away (they should be the volume number and track number of the address it found).
Continuing on after that point, from $B86E, it then looks for the nibble sequence AA AA, which would indicate that it is looking at sector zero. If it wasn’t looking at sector zero, it goes back and tries to find another sector. If it did find sector zero, then it loads up five more nibbles and returns with the last of them. These should be the checksum and then the address field closer, which on a DOS 3.3 disk is generally DE AA EB. So if this were a DOS 3.3 disk, we’d expect it to return EB.
In actual fact, the nibble stream I captured on side one of the disk has an address field closer of AF A5 AA for track 0 sector 0. Ultimately what this means is that every call to $B83E is coming back with AA. It is always the same, and so eventually the retry counter reaches zero, memory is wiped, and the program hangs.
At this point, I don’t actually know what they did to the original Autoduel disk to result in having the address field closer on track 0, sector 0, come back with an inconsistent third value. But I did know how I could fake it, and maybe this is how it was done on the original disk as well, although it was not captured by SST if so. What I did is found the stream of sync bits at the end of track zero (a whole bunch of FF nibbles), and stuck D5 AA 96 96 96 96 96 AA AA 96 96 96 96 96 in there in the nibble image using a hex editor. Breaking that down, it is the address field header D5 AA 96, four throwaway nibbles (96), the two AAs that the routine is looking for to identify sector zero, and five more throwaway nibbles representing the slots for the checksum and the address field closer. The important thing here is that the last nibble is not AA (I chose 96, but anything but AA would work).
This will now pass the protection check at $B800, and I didn’t have to modify any of the code. When it is looking for sector zero, sometimes it will find the real one and get AA as the last nibble and sometimes it will find my fake one and get 96 as the last nibble. They won’t match, and so execution continues back at $B793 without ticking down the counter and hanging.
The code at $B793, after having been modified by the routine at $B800, goes through and loads the rest of DOS, which it does through repeated calls to $B7B5, which stifles interrupts and passes control to $BA00. This is all basically just RWTS calls. However, trying to boot the disk at this stage still doesn’t work, it just grinds. So, there’s still one more level of protection somewhere.
I had observed above that the address field closers on the front side of the disk were abnormal, they generally start with AF, and then it varies, but they are not the usual DE AA EB. The address field closers on the back of the disk are DE AA EB, though. So, that’s a natural place to look, based on the experience with the Electronic Mailbag I detailed earlier. And it turns out Autoduel does a very similar trick. Deep in the heart of the RWTS routines, we find the part that reads the address field at $BE4D. It’s a little hard to follow, but it’s looking for D5 AA 96, grabs and decodes the next eight nibbles (volume, track, sector, checksum), and then starts looking for the closer at $BE8C.
If it finds DE, the job is done, nothing else is checked. This would happen if we were reading the second side of the disk. The carry flag is cleared to indicate no error, and we’re out. If it doesn’t find DE (and it would have found AF on the front side of the disk), then it does that same thing I saw on Electronic Mailbag, it waits a couple of cycles then sees if it has gotten enough bits in (even before the whole nibble is read) to exceed 00001000. If it has, it reports an error. So again, I think what must have happened on the real disk is that there were some extra zeros written here to delay the arrival of the second nibble. In an emulated nibble image, this is not really an easy option.
Probably at this point, I could have tried to go through the entire nibble image and replace all of the initial two nibbles in the address field trailers with AF 00. That might work. It seems like a drastic change to make, but it has the advantage that the protection code could be left untouched. Because the address field closers are not really consistent in my image of the first side, though, a simple search and replace wouldn’t work. Moreover AF 00 is not a valid nibble sequence, so it relies on the emulator being willing to overlook that. Perhaps a simpler alternative would be to change those AFs to DEs in the first nibble of the address field closer, but it’s still not an easy search and replace to perform as far as I can tell using the hex editor I’m using.
One thing that does work, though it requires patching the code, is changing the CMP #$DE (which gives the second side an automatic pass) to AND #$8E CMP #$8E, since both DE and AF are 8E when ANDed with 8E. This gives both DE and AF an automatic pass. So if you’re sitting at the breakpoint that catches that very first write protection check, you can alter BE91 to 29 8E C9 8E F0 07 EA EA EA EA EA D0, which results in this:
This leaves a little bit of protection in place to catch genuine disk errors (though I guess those are unlikely using an emulator), while allowing either AF or DE to be satisfactory first nibbles in the address field closer.
So, with the nibble image modified to contain the additional fake sector zero header on track zero, and a breakpoint set at the write protection check so I can set the negative flag and patch BE91 as above, resuming from there allows Autoduel to boot. This leaves the disk in pretty much its originally protected state, minus whatever bit delays were allowing it to actually pass the protection and accordingly, minus the check for them.
The last step is to write my new code back out to the nibble image. Again, I have the problem that this is a nibble image I’m trying to modify (because I want to leave the protection as close to intact as I can), and so replacing those 12 bytes is not a simple task.
Following the same procedure I used before with Electronic Mailbag, even after EORing it all with E6 to re-encrypt it, didn’t seem to work, I was still unable to find the nibble stream I needed in the Autoduel nibble image. So I’ll have to leave this project here incomplete, but at least with a beginning of an understanding of where the checks are and what they’re checking for. There may be something else going on (though the nibble image preserved it, since the disk boots). For the moment, the image is only usable by setting a breakpoint and modifying the code in place, then resuming the boot. Perhaps I’ll come back to it. The short version of how to get the disk to boot as-is in Virtual II: Set a breakpoint at $B81C, boot, set the negative flag, replace BE91 with 29 8E C9 8E F0 07 EA EA EA EA EA D0, and resume.