[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Re-engineered: Wizardry III, Legacy of Llylgamyn



> Tommy,
>
> This level of reverse engineering is nothing but admirable.
>
>    -- Vlad

Hi Vlad,

Thank you for the compliment.

If I ever complete writing about Wizardry III re-engineering, I will
consider creating PDFs and ZIP files to archive this work, but it
doesn't seem right now that very many are interested in this effort.
In fact, I feel a bit discouraged and wonder to myself if I should
spend the time writing about decompilation techniques.  Is there
really anyone that will seriously read about decompilation techniques
in order to learn how to do them and then actually use those
techniques themselves?  Depending on the level of detail that I
provide, it could fill a book, especially as it pertained to my effort
with Wizardry III.


AppleWin and 4 Disk Drives
==========================

In this post, I will describe some fun I had with AppleWin and 4 disk
drives.

In Dec 2011 I realized I would need an Apple II with 4 disk drives in
order to compile the full Wizardry III Pascal program.  Up until that
time I had used AppleWin and 2 disk drives.  If you ever have a reason
to work with UCSD Pascal on an Apple II, I'd strongly suggest having
at least 4 disk drives.

I sent a request to the AppleWin developers at BerliOS to request that
AppleWin support 4 disk drives, but they declined.  They did offer to
send me the AppleWin source code so that I could "hack" at it, and
that is what I did.

I created a version of AppleWin that supports 4 disk drives.  I
created 4 "buttons" where there had been 2 buttons on the right side
of the display.  Each button supports left-click, right-click, mouse-
over popup, and file drag-and-drop.  Each of the 4 buttons has an
"indicator" to display reading/writing/not-spinning.  Since the
buttons were smaller, I altered the disk drive picture displayed on
each, and also set the drive path image on the button to be left
justified.  I disabled the use of slots 4 and 5 for Mockingboards by
using the "-m" option on the AppleWin call line.  I also updated the
Configuration dialog box for "Disk" to have 4 entries instead of 2.
And then I changed the code to recognize the 2 extra disks in slot 5.

I tested the disk drives using Dos3.3.  Each drive could initialize a
disk, save files, delete files, load files, display catalogs, etc.
Ok, so it was now time to compile the whole Wizardry III program -- or
so I thought!

I loaded 4 Pascal disks into the 4 disk drives.  I pressed the
"Reboot" button and the first disk started the boot process, and maybe
the second also started and stopped, but then....

   *** NOTHING ***

The disks stopped spinning and the Pascal system was hung before it
had displayed any messages on the screen.

I let out a few choice words and thought "how the heck am I going to
figure out how or why the Pascal system is hung?  What is so different
between DOS and PASCAL?"

After awhile I hit upon the idea of using the AppleWin debugger to
step through the hung system to gather more information and see if I
could discover what was going on.  As I was stepping through the code,
I realized that it seemed to be doing something with disks (well duh!)
and the code was also checking the keyboard data input ($C000) and
saving any keystrokes.  This jogged something in my memory from long
ago when I had first disassembled the Wizardry III Pascal boot code.
The Pascal operating system on the Wizardry III diskette is of course
a subset of the full blown Pascal system.  I looked at my paper
listings (created in 1991) and with my hand notes I discovered that we
were indeed in the Pascal routine to perform disk I/O, similar to RWTS
for DOS.

Here is the partial listing of code as found in Apple ][ Pascal 1.1 P-
Code Interpreter 6502 Disassembly
(ftp://ftp.apple.asimov.net/pub/apple_II/documentation/programming/
pascal/Apple 2 Pascal 11 PCodeIntDism.pdf)


82 INCLUDE DISKII.IAS
             1 ;.PAGE '???'
             2 ;
             3 ;
             4 ;
D0E8 A002    5 INTAS LDY #$02
D0EA 8CFFDF  6       STY   ADFFF
             7 ;
             8 ;
             9 ;
D0ED A00A   10       LDY   #$0A
D0EF 8CFDDF 11       STY   ADFFD
            12 ;
            13 ; Branch if slot number hasn't changed
            14 ;
D0F2 AEA103 15       LDX   SLOTNO
D0F5 ECAF03 16       CPX   PRVSLT
D0F8 F011   17       BE    INTAU
            18 ;
            19 ;
            20 ;
D0FA AEAF03 21       LDX   PRVSLT
            22 ifndef ORIG
            23       BZ    INTAZ
            24 endif
            25 ;
            26 ;
            27 ;
D0FD 2001D0 28 INTAT JSR   CCONCK ; INTAD
            29 ;
            30 ;
            31 ;
D100 2082D3 32       JSR   INTDV
D103 D0F8   33       BNZ   INTAT
            34 ;
            35 ;
            36 ;
            37 ifndef ORIG
            38 INTAZ
            39 endif
D105 AEA103 40       LDX   SLOTNO
D108 8EAF03 41       STX   PRVSLT
            42 ;
            43 ;
            44 ;
D10B 2082D3 45 INTAU JSR   INTDV
D10E 08     46       PHP
            47 ;
            48 ; Turn drive on
            49 ;
D10F BD89C0 50       LDA   DRVON,X
            51 ;


The BNZ at line 33 was always taking the jump to INTAT at line 28.
CCONCK is checking for keyboard input, and INTDV is testing to see if
the disk drive is still spinning.  When it is done spinning, then the
"Z" status in the processor is set and we break out of the loop.  But
the "Z" status was never getting set.

In looking at this code, I realized that we are only here "if the slot
number (has) changed"!

Here is the INTDV code:

            744 ;
D382 A000   745 INTDV LDY #0
            746 ;
            747 ;
            748 ;
D384 BD8CC0 749 INTDY LDA   DRVRD,X
            750 ;
            751 ; Time delay
            752 ;
D387 2094D3 753       JSR   INTDZ
D38A 48     754       PHA
D38B 68     755       PLA
            756 ;
            757 ; Branch if drive still moving???
            758 ;
D38C DD8CC0 759       CMP   DRVRD,X
D38F D003   760       BNE   INTDZ
            761 ;
            762 ;
            763 ;
D391 88     764       DEY
D392 D0F0   765       BNZ   INTDY
            766 ;
            767 ; Return to caller
            768 ;
D394 60     769 INTDZ RTS


Ok, we are making real progress!  The indicator status lights in
AppleWin for all 4 disk drives showed them as inactive, yet this
Pascal Operating System code thought a drive was still spinning!

So now I turned my attention to the AppleWin code (written in C and C+
+).  Eventually I found myself in DiskReadWrite which returns the next
byte of data from a spinning disk:

static BYTE __stdcall DiskReadWrite (WORD programcounter, WORD, BYTE,
BYTE, ULONG)
{
  Disk_t * fptr = &g_aFloppyDisk[currdrive];

  diskaccessed = 1;

  if (!fptr->trackimagedata && fptr->imagehandle)
    ReadTrack(currdrive);

  if (!fptr->trackimagedata)
    return 0xFF;

  BYTE result = 0;

  if (!floppywritemode || !fptr->bWriteProtected)
  {
    if (floppywritemode)
    {
      if (floppylatch & 0x80)
      {
        *(fptr->trackimage+fptr->byte) = floppylatch;
        fptr->trackimagedirty = 1;
      }
      else
      {
        return 0;
      }
    }
    else
    {
      result = *(fptr->trackimage+fptr->byte);
    }
  }

  if (0)
    { LOG_DISK("nib %4X = %2X\r", fptr->byte, result); }

  if (++fptr->byte >= fptr->nibbles)
    fptr->byte = 0;
  return result;
}

While reading data, DiskReadWrite returns "result" using fptr->byte.
Also notice near the end of the routine the code that unconditionally
increments fptr to the next byte on the disk (++fptr->byte).

I found that a Disk_t object has the following field:

  DWORD  spinning;

And I also found the variable:

  static BOOL  floppymotoron = 0;

On the physical Apple II computer, you can turn the disk drive off and
for awhile the disk continues to spin and the data latch will continue
to change.  Eventually the disk stops spinning.  This is also true for
AppleWin.  The problem with the code in DiskReadWrite is that

 EVEN WHEN THE DISK HAS STOPPED SPINNING, THE fptr POINTER IS UPDATED
TO THE NEXT BYTE.

In essence, for AppleWin, once a disk has started spinning the disk
will continue to spin and the data latch will always keep changing!

This is an existing bug in AppleWin!

Here is a test program I wrote to demonstrate the bug.  On a physical
Apple II computer, the program will eventually stop with the BRK
instruction at $0815.  On AppleWin 1.20.1.0, the program executes in
an infinite loop.

]CALL-151

*800L

0800-   A2 60       LDX   #$60      ; Slot 6
0802-   BD 89 C0    LDA   $C089,X   ; Drive ON
0805-   A9 20       LDA   #$20      ; Input to Wait
0807-   20 A8 FC    JSR   $FCA8     ; Wait for drive
080A-   BD 88 C0    LDA   $C088,X   ; Drive OFF
080D-   20 16 08    JSR   $0816     ; Check data latch
0810-   8D 64 04    STA   $0464     ; "Splat" data to screen
0813-   D0 F8       BNE   $080D     ; Done spinning?
0815-   00          BRK             ; Yes, BRK
0816-   A0 00       LDY   #$00      ; Try to get 256 the same
0818-   BD 8C C0    LDA   $C08C,X   ; Read data latch
081B-   20 28 08    JSR   $0828     ;
081E-   48          PHA             ;
081F-   68          PLA             ;
0820-   DD 8C C0    CMP   $C08C,X   ; Compare with next data latch
0823-   D0 03       BNE   $0828     ; Different?  Yes, still spinning
0825-   88          DEY             ; That pair matched, try again
0826-   D0 F0       BNE   $0818     ; Matched 256 times?  Yes, RTS
0828-   60          RTS             ; Z=0?  Yes, done spinning


--Tommy