Megalextoria
Retro computing and gaming, sci-fi books, tv and movies and other geeky stuff.

Home » Digital Archaeology » Computer Arcana » Apple » Apple II » Wizardry IV bootstrap bug in SYSTEM.INTERP
Show: Today's Messages :: Show Polls :: Message Navigator
E-mail to friend 
Switch to threaded view of this topic Create a new topic Submit Reply
Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270050] Sun, 05 October 2014 13:09 Go to next message
TommyGoog is currently offline  TommyGoog
Messages: 112
Registered: January 2013
Karma: 0
Senior Member
I'm starting to re-engineer Wizardry IV.

I've found a software defect (bug) in the Pascal runtime code in SYSTEM.INTERP on the boot disk. I'm using the Wizardry IV disk image (Wiz4_d1.dsk) from ASIMOV, which uses the Apple Pascal 1.2 system.

In the early stages of the boot, most of SYSTEM.INTERP is loaded into RAM from $D000 to $FFFF. The last 4 sectors are loaded into $BC00 to $BFFF. The code at $BC00 is executed. In this code the active segment table is updated for the file SYSTEM.PASCAL. Following this there is a call to a subroutine to examine and determine the type of devices in the Apple's backplane card slots by examining the memory (e.g., $C600 to $C6FF for slot 6). Here is the code:

BE01- 85 52 STA $52 Identify Peripherals
BE03- 20 1C BE JSR $BE1C Check boot slot (e.g., $C600)
BE06- A0 C7 LDY #$C7 Check other slots
BE08- C4 F3 CPY $F3 Boot slot? (e.g., #$C6)?
BE0A- F0 03 BEQ $BE0F Yes, already checked
BE0C- 20 1C BE JSR $BE1C ***** Y changes *****
BE0F- 88 DEY Oops!
BE10- C0 C1 CPY #$C1
BE12- B0 F4 BCS $BE08
BE14- AD 80 C0 LDA $C080
BE17- 60 RTS

The first call to BE1C at BE03 is fine. The next call, at BE0C destroys Y. It becomes 0 while booting from slot 6, drive 1. The DEY changes it to $FF and things go south from there.

The code gets a little bit lucky that it escapes after checking "slot" memory at $FF00 when Y eventually becomes 1 and then breaks out of this loop.

I think the only adverse affect is that the other slots are not identified but I have not verified this.

The code resides on Track $04, Sector $09 if you use a program like CopyII+ to examine it.

Identical code as above is also on Wiz4_d6.dsk. It's interesting to note a minor coding difference between these 2 versions of INTERP at $BD4F where a "clear screen" call exists in the Wiz4_d1.dsk code.

I'm wondering if there is any other information about this bug or if it exists in other versions of Wizardry IV (or other programs). Is there a list anywhere of programs written using Apple Pascal 1.2?

Tommy
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270269 is a reply to message #270050] Tue, 07 October 2014 14:41 Go to previous messageGo to next message
Antoine Vignau is currently offline  Antoine Vignau
Messages: 1860
Registered: October 2012
Karma: 0
Senior Member
Yes, Tommy's back!!!
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270298 is a reply to message #270050] Tue, 07 October 2014 16:10 Go to previous messageGo to next message
Michael AppleWin Debu is currently offline  Michael AppleWin Debu
Messages: 1262
Registered: March 2013
Karma: 0
Senior Member
On Sunday, October 5, 2014 10:09:53 AM UTC-7, TommyGoog wrote:
> I'm starting to re-engineer Wizardry IV.

Very, very, nice reverse engineering!

Please keep us updated of your progress ! :-)
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270394 is a reply to message #270298] Wed, 08 October 2014 18:37 Go to previous messageGo to next message
TommyGoog is currently offline  TommyGoog
Messages: 112
Registered: January 2013
Karma: 0
Senior Member
On Tuesday, October 7, 2014 3:10:31 PM UTC-5, Michael "AppleWin Debugger Dev" wrote:
> On Sunday, October 5, 2014 10:09:53 AM UTC-7, TommyGoog wrote:
>
>> I'm starting to re-engineer Wizardry IV.
>
>
>
> Very, very, nice reverse engineering!
>
>
>
> Please keep us updated of your progress ! :-)


They told me at the insane asylum they needed someone to re-engineer Wizardry IV. They said I was just the person they were looking for. I said you'd have to be crazy to try to re-engineer that. They just nodded their heads.

This journey will be a slow one. My DeCompiler program to display the p-code from the executable files does not seem to work properly for Pascal 1.2 files;
hence, I'm starting to look at the runtime Pascal system on the boot disk to identify differences between Pascal 1.1 and Pascal 1.2.

A short(?) lesson today will be "Manually Navigating the Pascal Directory and Code Files".

I use CopyII+ to examine diskettes. You can use your favorite sector editor.

The Pascal directory occupies blocks 2-5 on Track 0. (https://code.google.com/p/profuse/wiki/PascalFileSystem)

Conversion of blocks
--------------------
The best way to convert a Pascal "Block" to Track and Sector is to look at the value in binary format. The high order 5 bits represent the Track, and the low 3 bits represent the Sector (divided by 2). The sector also needs to be converted from the Pascal sector interleaving to DOS interleaving. I use the following table:

Pascal Dos
0 0
1 E
2 D
3 C
4 B
5 A
6 9
7 8
8 7
9 6
A 5
B 4
C 3
D 2
E 1
F F

Conversion from one to the other is simple. If it is 0 or F, it is the same, otherwise subtract the value you have from 15.

So, Block 2 in binary is 00000010. Split it into 00000 010.
Track is 0.
Sector is 010. Multiply by 2 = 0100 = 4. 4 in Pascal = $B in DOS.

Reading T $0 S $B shows the beginning of the directory. Each entry is 26 bytes long. The first entry is for the Volume and shows "WIZBOOT".

I am interested in the file "SYSTEM.PASCAL". We find that it starts at offset $9C.

46 00 E2 00 02 00 0D 53 59 53 54 45 4D 2E 50 41 53 43 41 4C 40 B2 00 02 BB AE

First Block: $46
Last Block: $E2
File Kind: 02 (Code File -- executable)
Name: SYSTEM.PASCAL
Last Byte: $0200

Block $46 = 01000 110
Track 8;
Sector 110 times 2 = 1100 = $C = DOS sector 3.

T $8 S $3 is the beginning of the executable file, and therefore is the Segment Dictionary. All disk pointers in this table are relative to the disk address ($46) of the Segment Dictionary.

There are 16 entries at the beginning of the Segment Dictionary that are each 4 bytes long. They represent the relative disk address and the length of the segment. Following the first 16 x 4 bytes are 16 entries of 8 bytes that are the names of the segments ("WIZARDRY", "COMBAT", "CUTIL", etc.).

There is more information in the rest of this sector, but the next sector (T 8 S 2) contains more information we are interested in. At the start of this sector are 16 entries. One entry for each segment. The values are the actual segment number assigned when executing, followed by the Version (low 3 bits) and Machine type (upper 4 bits). The version numbers here are all "02", and the Machine type are all "04" = "P-code, least significant byte first".

For the first Segment we have:
01 00 66 1C
WIZARDRY
01 42

The Segment "WIZARDRY" therefore starts at relative block address $01 and has length $1C66.

Yes, Alice, the White Knight is talking backwards and it is $1C66, not $661C.

$46 + $01 = first Pascal block for segment "WIZARDRY".

$47 = 01000 111
Track 8;
Sector 111 times 2 = 1110 = $E = DOS 1

T $8 S $1 is start of WIZARDRY segment, but we need to find the END of the segment to find the "Procedure Dictionary".

Are we having fun yet?

The start of the segment is at block $47, and we know the length is $1C66. Since 1 block is 2 sectors, then the upper 8 bits of the length ($1C in this case) represents 2 times the number of blocks in the file. $1C = 00011100 divided by 2 = 00001110 = $E.

$47 + $E = $55 = 01010 101
Track $A;
Sector 101 times 2 = 1010 = $A = DOS 5.

Track $A Sector $5 and Track $A Sector $4 are therefore the last sectors of the segment WIZARDRY. Since the upper 8 bits of the length divided evenly by 2, the last sector for the segment is sector $5, and the last byte is at offset $65.

T$A S$5 A$64 is the beginning of the Procedure Table. Note, it starts at the last byte of the segment and extends upward.

The last byte is the number of procedures ($58 = 88) in this segment. The byte that precedes this is the segment number (1). This "1" corresponds with the segment dictionary value.

Why are all my students looking at me with blank faces? And then a kid in the back row raises his hand and asks, "Do we need to know any of this junk for the final exam?" Class dismissed.

Recapping where we are, we started with the Wizardry IV diskette, found the Pascal Directory, then found the file SYSTEM.PASCAL. SYSTEM.PASCAL has a Segment Dictionary, and we are now looking at the first segment's Procedure Dictionary. Next step is to find the first procedure in this segment.

Moving upward in the file from the last word of the file are 1 word (2 byte) pointers to each procedure in this segment. Since there are 88 procedures, there are 88 pointers here. The first pointer is for procedure 1. The value is "B0 00" or $00B0.

$00B0. What is that? How do we get to the code?

These pointers are self-referencing.
Take the value and subtract it from the address where the pointer is stored..

T $A S $5 A$62 minus $00B0 = T $A S $6 A$B2.
Using DOS sector numbering, realize that sector 6 is actually the sector that precedes sector 5 in the Pascal numbering scheme.

T $A S $6 A$B2 is the Table of Attributes (JTAB) for Proc 1 in Segment WIZARDRY.
T $A S $6 A$B3 is the Lexical level (00).
T $A S $6 A$B2 is the Procedure Number (01).
T $A S $6 A$B0 is the relative pointer to the Enter IC (First instruction of the procedure), ($94 $01 = $0194).

Another relative pointer?! Yes!

They're coming to take me away! Ha ha, ho ho, hee, hee!

T $A S $6 A$B0 minus $0194 = T $0A S $07 A$1C.

There's the Pascal p-code!!!
D7 D7 0D AB 12 04 D7 A6 08 49 53 2E 41 50 50 4C 45 00 00 CE...and a bunch more.

This much can be verified by using my DeCompiler that I described (and posted) in the earlier thread.

FILE: 3 SEG: 0 PROC: 1

JTAB FOR SUBROUTINE 1:
DATA SIZE: 4656
PARAM SIZE: 4
EXIT AT: 6BA2
ENTER AT: 6A1C
PROC NUMBER: 1
LEXICAL LEVEL: 0
6A1C D7 NOP. NOP
6A1D D7 NOP. NOP
6A1E 0D SLDC. PUSH #000D
6A1F AB 12 SRO. BASE.12 := (TOS)
6A21 04 SLDC. PUSH #0004
6A22 D7 NOP. NOP
6A23 A6 08 49 53 2E 41 50 50 4C 45
IS.APPLE
LSA. PUSH #(PC+1) POINTER TO THE STRING
6A2D 00 SLDC. PUSH #0000
6A2E 00 SLDC. PUSH #0000
6A2F CE 3B CLP. CALL CHILD PROCEDURE: 3B
6A31 00 SLDC. PUSH #0000
6A32 C5 GRTI. PUSH ((TOS-1) > (TOS))
6A33 AB 88 11 SRO. BASE.0811 := (TOS)
6A36 04 SLDC. PUSH #0004
6A37 A6 08 4C 49 4E 45 53 2E 32 34
LINES.24

<snip>

Ok, why did I show you all this stuff? Because it doesn't seem to work for the other segments in the file and right now I don't know why. It's possible that there was a file structure change from Pascal 1.1 to Pascal 1.2 that I am unaware of. The values in the Segment table seem consistent for WIZARDRY, COMBAT, CUTIL, etc., but it doesn't seem like there is Pascal (or 6502 code) where the pointers say there should be. It's almost like the executable has not been properly Linked. There is of course the possibility that all these other segments are just "noise" or left over garbage, or that I've run into some "copy protection" scheme.

It takes a long time for me to write all this stuff down. Am I wasting my time in trying to describe all these details in my posts? Is it too much detail? Have you actually tried to look at the Wizardry IV diskette while following along in this post?

I started playing Wizardry IV for the first time about 2 months ago. I made it through about 4 levels, but I don't find it enjoyable at all. It's an interesting game, and an interesting twist to the first 3 versions, and I'm sure the code is a LOT different from the first three, but playwise it is what I would call boring.

So far I've had to learn about the Apple IIe. I was more of an Apple II and Apple II+ guy in the 80's. I've also had to learn about Pascal 1.2 and relearn everything I've forgotten since the last re-engineering.

As always, I encourage questions and comments.

Tommy
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270413 is a reply to message #270394] Wed, 08 October 2014 21:09 Go to previous messageGo to next message
gbody4 is currently offline  gbody4
Messages: 47
Registered: October 2012
Karma: 0
Member
Hi Tommy,
It been interesting looking at the work your been doing on Wizardry. I remember playing wiz 4 and it requiring a lot of disk changes. I poked about inside the internals and I think there was an internal table used for locating data on the disks. I created a two disk version for use with two drives to remove the disk swapping. Not sure if I can find the details on this, but I will have a look.

GeoffB
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270530 is a reply to message #270413] Fri, 10 October 2014 02:38 Go to previous messageGo to next message
TommyGoog is currently offline  TommyGoog
Messages: 112
Registered: January 2013
Karma: 0
Senior Member
On Wednesday, October 8, 2014 8:09:42 PM UTC-5, geoff body wrote:
> Hi Tommy,
>
> It been interesting looking at the work your been doing on Wizardry. I remember playing wiz 4 and it requiring a lot of disk changes. I poked about inside the internals and I think there was an internal table used for locating data on the disks. I created a two disk version for use with two drives to remove the disk swapping. Not sure if I can find the details on this, but I will have a look.
>
>
>
> GeoffB

Hi Geoff,

The version I am decompiling and played with 2 months ago does properly work with 2 disk drives. This is true if they are both connected to slot #6 and both have disks in them while booting. When I played through 4 levels, I never needed to swap disks.

Fixing that first bug (Y getting destroyed) unveils yet another bug in the software. They are trying to find disk devices and associate them with device numbers using the Pascal numbering scheme of:

4: Slot 6, Drive 1
5: Slot 6, Drive 2

9: Slot 4, Drive 1
10: Slot 4, Drive 2

11: Slot 5, Drive 1
12: Slot 5, Drive 2

After fixing the "Y destroyed" problem, and having 2 disk drives connected to slot 6 and 2 disk drives connected to slot 5, and booting from slot 6 drive 1, the code associates the drives for slot 5 with device numbers 9 and 10. I'm not sure, but I guess this would cause problems later if the software tried to use all 4 devices.

If you have them, I'd be interested in seeing the unaltered disk images for Wizardry IV that you first started with so as to compare them with the current versions that I'm working with from Asimov.

Tommy
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270553 is a reply to message #270530] Fri, 10 October 2014 13:33 Go to previous messageGo to next message
Tempest is currently offline  Tempest
Messages: 529
Registered: November 2012
Karma: 0
Senior Member
On Friday, October 10, 2014 2:38:14 AM UTC-4, TommyGoog wrote:
> If you have them, I'd be interested in seeing the unaltered disk images for Wizardry IV that you first started with so as to compare them with the current versions that I'm working with from Asimov.

I have all the original disks for Wizardry IV. If no one else has them I can loan them out.
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270591 is a reply to message #270553] Fri, 10 October 2014 23:45 Go to previous messageGo to next message
qkumba is currently offline  qkumba
Messages: 1584
Registered: March 2013
Karma: 0
Senior Member
Tommy, can you explain in more detail why you think that the other segments don't work?
I extacted the "kanjirea" segment. Going backwards from the end, we have #$1c, #$0c, #$0038. Going -#$38 we have #$01, #$01, #$004a. Going -#$4a, we have (going forwards now) #$da, #$9f, #$c3, #$a1...
which looks like p-code. Am I wrong?
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270595 is a reply to message #270591] Sat, 11 October 2014 04:14 Go to previous messageGo to next message
TommyGoog is currently offline  TommyGoog
Messages: 112
Registered: January 2013
Karma: 0
Senior Member
On Friday, October 10, 2014 10:45:32 PM UTC-5, qkumba wrote:
> Tommy, can you explain in more detail why you think that the other segments don't work?
>
> I extacted the "kanjirea" segment. Going backwards from the end, we have #$1c, #$0c, #$0038. Going -#$38 we have #$01, #$01, #$004a. Going -#$4a, we have (going forwards now) #$da, #$9f, #$c3, #$a1...
>
> which looks like p-code. Am I wrong?

Hi qkumba,

I think you are wrong.

The sequence you give, "#$DA, #$9F, #$C3, $#A1" is most likely pcode.

That sequence shows up in 5 locations on the boot diskette. I definitely know it is in SYSTEM.PASCAL Segment 0, in procedures $43 and $4E. It shows up on the diskette in the following "DOS" sectors:

T $9 S $5
T $9 S $B
T $A S $2
T $B S $2
T $B S $4

The reason I've listed those locations will become clear further down.

Now to answer your first question.

The first sign of trouble was when I ran my DeCompiler on the Wizardry IV boot diskette. It crapped out badly. There could be an error with the program, but then I started looking at the diskette "by hand", and I got basically the same bad results.

The DeCompiler does work very nicely on SYSTEM.PASCAL Segment 0 and all of its 88 procedures. When it got to the next segment, it seemed to indicate it was a 6502 piece of code, but the code on diskette doesn't look like 6502. Could still be ok. But then on the next Segment, my DeCompiler got really confused really early in just looking at what it thought was a JTAB. Total garbage displayed.

I hadn't looked at KANJIREA until your posting, but here is my "by hand" method:

SYSTEM.PASCAL as found in the Pascal directory starts at Pascal block #$46.

Pascal block #$46 is 01000 110; Track $10; Sector 1100 = $C = DOS sector $3.

T $10 S $03 is the start of SYSTEM.PASCAL.

That is therefore the location for the Segment Dictionary for SYSTEM.PASCAL..

Segment Dictionary (first sector):

00: 01 00 66 1C 10 00 E0 03 A@&\P@`C
08: 12 00 3A 1B 20 00 FC 13 R@:[ @|S
10: 2A 00 E6 0C 31 00 DE 1A *@fL1@^Z
18: 3F 00 4E 10 48 00 98 15 ?@NPH@.U
20: 53 00 E2 0B 59 00 34 2C S@bKY@4,
28: 70 00 CA 24 8B 00 E8 21 0@J$.@h!
30: 83 00 A0 07 87 00 2E 07 .@ G.@.G
38: 00 00 00 00 00 00 00 00 @@@@@@@@
40: 57 49 5A 41 52 44 52 59 WIZARDRY
48: 43 4F 4D 42 41 54 20 20 COMBAT
50: 43 55 54 49 4C 20 20 20 CUTIL
58: 43 41 53 54 41 53 50 45 CASTASPE
60: 53 57 49 4E 47 41 53 57 SWINGASW
68: 43 49 4E 49 54 20 20 20 CINIT
70: 4B 41 4E 4A 49 52 45 41 KANJIREA
78: 55 54 49 4C 49 54 49 45 UTILITIE
80: 53 48 4F 50 53 20 20 20 SHOPS
88: 53 50 45 43 49 41 4C 53 SPECIALS
90: 43 41 4D 50 20 20 20 20 CAMP
98: 52 55 4E 4E 45 52 20 20 RUNNER
A0: 44 4F 43 4F 50 59 20 20 DOCOPY
A8: 44 4F 43 41 43 48 45 20 DOCACHE
B0: 20 20 20 20 20 20 20 20
B8: 20 20 20 20 20 20 20 20

The first 4 bytes at "18:", namely "3F 00 4E 10" correspond with KANJIREA.

Therefore KANJIREA segment starts at Pascal block $3F relative to the start of this Segment Dictionary.

$46 + $3F = Pascal Block $85.

$85 = 10000 101; T $10; S 1010 = $A = DOS 5.

T $10 S $05 is start of KANJIREA.

Ok, look back at the list above where I point out where "DA 9F C3 A1" are found. They are not in KANJIREA.

Now we need to find the END of KANJIREA.

The length (in bytes) of KANJIREA is found in those 4 bytes at "18:" above, namely "4E 10" which is $104E bytes.

We need to convert $104E into blocks.
The top 8 bits of this value represents the number of whole "sectors". So one half this value is the number of whole Pascal blocks. We then account for the $4E bytes to find the last byte of the file at offset $4D in the partial block.

$85 + $08 will give us the last disk block address for KANJIREA. We also know that the last byte in the block is in the first sector since $10 divided evenly by 2.

KANJIREA last block = $8D = 10001 101; T $11, S 1010=$A= DOS 5.

T $11 S $05

Here is T $11 S $05:

00- 00 00 00 00 00 00 3E 7B @@@@@@>;
08- 40 60 70 78 3C 1E 0F 06 @ 08<^OF
10- 07 03 01 40 40 40 40 58 GCA@@@@X
18- 7C 7E 07 FB C1 D0 C4 D4 <>G{APDT
20- 3F 7F 60 5F 03 8B A3 AB ?? _C.#+
28- 00 00 01 03 07 06 06 06 @@ACGFFF
30- 60 47 0F 1E 58 1C 7F 7B GO^X\?;
38- 01 03 07 07 03 30 78 73 ACGGC083
40- 5E 5F 3F 6F 7F 76 3C 07 ^_?/?6<G
48- D1 87 27 28 20 22 Q.'( "

This doesn't look like a valid procedure dictionary. The last byte says there are $22 procedures, and the number before it $20, indicates this is Segment $20 = 32. The words that precede these are supposed to be relative pointers to procedure JTABs, but they are much too large in value. I'm pretty sure this is where my DeCompiler gets greatly confused. Note, it does display that it thinks there are 34 procedures in this segment, so that at least corresponds to my finding $22 in this spot.

If you, or anyone else, can point out where I am making a mistake I'd appreciate it.

Tommy
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270618 is a reply to message #270595] Sat, 11 October 2014 17:42 Go to previous messageGo to next message
qkumba is currently offline  qkumba
Messages: 1584
Registered: March 2013
Karma: 0
Senior Member
Hi Tommy,

> I think you are wrong.
> The sequence you give, "#$DA, #$9F, #$C3, $#A1" is most likely pcode.
> That sequence shows up in 5 locations on the boot diskette. I definitely know it is in SYSTEM.PASCAL Segment 0, in procedures $43 and $4E. It shows up on the diskette in the following "DOS" sectors:
> T $9 S $5
> T $9 S $B
> T $A S $2
> T $B S $2
> T $B S $4
> The reason I've listed those locations will become clear further down.

and it appears twice in KANJIREA.

> I hadn't looked at KANJIREA until your posting, but here is my "by hand" method:
> SYSTEM.PASCAL as found in the Pascal directory starts at Pascal block #$46.
> Pascal block #$46 is 01000 110; Track $10; Sector 1100 = $C = DOS sector $3.
> T $10 S $03 is the start of SYSTEM.PASCAL.
> That is therefore the location for the Segment Dictionary for SYSTEM.PASCAL.
> Segment Dictionary (first sector):
....
> The first 4 bytes at "18:", namely "3F 00 4E 10" correspond with KANJIREA.
> Therefore KANJIREA segment starts at Pascal block $3F relative to the start of this Segment Dictionary.
> $46 + $3F = Pascal Block $85.

I agree with all of this.

> $85 = 10000 101; T $10; S 1010 = $A = DOS 5.
> T $10 S $05 is start of KANJIREA.

I do not agree with this, from observation of the code. I traced the loader code, and then dumped the memory. I am certain that the file contents are correct.
The first sector that is read is actually DOS 6.
The full list looks like this (TT=track, PS=Pascal Sector, DS=DOS Sector):

TT PS DS
0A 09 06
0A 0B 04
0A 0D 02
0A 0F 0f
0B 00 00
0B 02 0d
0B 04 0b
0B 06 09
0B 08 07
0B 0A 05
0B 0C 03
0B 0E 01
0B 01 0e
0B 03 0c
0B 05 0a
0B 07 08
0B 09 06

The DOS Sector is based on your translation table. However, the first physical sector that is read (taken from a dsk image) is T $10 S 03. It starts "04 C6 06 00 C7". It is the only occurrence on the disk.

....
> This doesn't look like a valid procedure dictionary.

It's not the same data from the same location. All following analysis will be wrong for that reason.

> If you, or anyone else, can point out where I am making a mistake I'd appreciate it.

The algorithm appears to be this:
track = block number / 8;
tmp = (block number & 7) * 2;
carry = 0;
if (tmp >= 8) carry = 1; tmp = tmp - 8;
sector = (tmp * 2) + carry;
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270619 is a reply to message #270618] Sat, 11 October 2014 18:35 Go to previous messageGo to next message
qkumba is currently offline  qkumba
Messages: 1584
Registered: March 2013
Karma: 0
Senior Member
The block number for KANJIREA is somehow converted from #$85 to #$56. I haven't investigated yet how that happens, but starting there, we have:

#$56 / 8 = $5. *2 = track $10.
tmp = (#$56 & 7) * 2 = #$0c.
#$c is >= 8, so #$0c - 4 = 4, and a carry.
sector = ($4 * 2) + 1 = $9;
That's a Pascal sector number.
Using 0 D B 9 ... ordering, that's sector $3.
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270624 is a reply to message #270619] Sat, 11 October 2014 19:19 Go to previous messageGo to next message
Antoine Vignau is currently offline  Antoine Vignau
Messages: 1860
Registered: October 2012
Karma: 0
Senior Member
The Pascal interleaving is the same as the ProDOS one which is different from the DOS 3.3 one.

I would vote for Peter on this one!

Antoine
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270630 is a reply to message #270618] Sat, 11 October 2014 22:40 Go to previous messageGo to next message
TommyGoog is currently offline  TommyGoog
Messages: 112
Registered: January 2013
Karma: 0
Senior Member
On Saturday, October 11, 2014 4:42:46 PM UTC-5, qkumba wrote:
> Hi Tommy,

> The full list looks like this (TT=track, PS=Pascal Sector, DS=DOS Sector):
>
>
>
> TT PS DS
>
> 0A 09 06
>
> 0A 0B 04
>
> 0A 0D 02
>
> 0A 0F 0f
>
> 0B 00 00
>
> 0B 02 0d
>
> 0B 04 0b
>
> 0B 06 09
>
> 0B 08 07
>
> 0B 0A 05
>
> 0B 0C 03
>
> 0B 0E 01
>
> 0B 01 0e
>
> 0B 03 0c
>
> 0B 05 0a
>
> 0B 07 08
>
> 0B 09 06
>
>
>
> The DOS Sector is based on your translation table. However, the first physical sector that is read (taken from a dsk image) is T $10 S 03. It starts "04 C6 06 00 C7". It is the only occurrence on the disk.

> The algorithm appears to be this:
>
> track = block number / 8;
>
> tmp = (block number & 7) * 2;
>
> carry = 0;
>
> if (tmp >= 8) carry = 1; tmp = tmp - 8;
>
> sector = (tmp * 2) + carry;

Hi qkumba,

One of us (or both of us) are terribly confused.

"The full list looks like this (TT=track, PS=Pascal Sector, DS=DOS Sector):"
"...first physical sector that is read (taken from a dsk image) is T $10 S 03".

Your list does not include any TT=$10. I'm not sure what you are trying to say here.

Here is an important question to ask:

How do you know that you are tracing the loading of the segment KANJIREA and not the segment COMBAT?

Hopefully we are both using the same disk image (or an identical one) from ASIMOV as a starting point, otherwise all this might be moot.

The Segment Dictionary for SYSTEM.PASCAL has this entry for COMBAT:

10 00 E0 03

The start of SYSTEM.PASCAL is $46, and the relative start for COMBAT is $10.. $46 + $10 = $56.

Is this where your $56 is coming from?

I will describe the "in memory" segment table after PASCAL.SYSTEM is loaded in a minute, but first we need to examine the second sector of the PASCAL.SYSTEM Segment Dictionary:

01 42 07 42 08 42 09 42
0A 42 0B 42 0C 42 0D 42
0E 42 0F 42 10 42 11 42
12 42 13 42 00 00 00 00

These are word entries and each word corresponds to the compiler assigned segment number. The first segment, WIZARDRY, is assigned segment 1; the next segment, COMBAT, is assigned segment 7; the next one is assigned segment 8.

The segment KANJIREA is assigned segment $0C.

Part of the segment load table in memory starts at $C19 and after PASCAL.SYSTEM Dictionary Segment is loaded it looks like this:

$C19: 0E 00 00 00 00 00 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 00 00 00 00 ....

$C3A: 47 56 58 66 70 77 85....
$C4A: 00 00 00 00 00 00 00....
$C5A: 66 E0 3A FC E6 DE 4E....
$C6A: 1C 03 1B 13 0C 1A 10....

The table at $C19 is a translation table from the compiler / linker assigned number to the entries in the $C3A, $C4A, $C5A, and $C6A tables. The way to read these tables is in vertical columns (e.g., 47, 00, 66, 1C, then 56, 00, E0, 03). Recall that runtime segment numbers 0, and 2-6 are reserved, so a program occupies segments 1, 7, 8, 9, etc..

Segment 1 translates to: Entry 00 in tables $C3A, etc.
Segment 7 translates to: Entry 01
Segment 8 translates to: Entry 02
Segment 9 translates to: Entry 03

Segment 0C translates to: Entry 06

Those values in the table at $C19 are indexes into the $C3A, $C4A, $C5A, and $C6A tables.

For runtime Segment 1, the value at $C19,1 is $00, so that is used as an index into $C3A, $C4A, etc.

$C3A and $C4A are the Pascal block address for the segment, and $C5A, $C6A are the length (in bytes). So we see that for runtime segment 1, has Block $0047, and length $1C66.

Now let's look at the next segment in the Segment Dictionary, which is COMBAT. It was assigned segment 7 by the compiler. $C19,7 has the value 01. The entries in $C3A etc., say this segment is at block $56 and has length $3E0.

Once again, it looks to me like COMBAT and not KANJIREA is the segment that occupies Pascal block $56 on the disk.

Also, your conversion algorithm for Pascal Blocks to Tracks / Sectors is the same as I posted earlier.

For example, "track=block number / 8" is the same as looking at the block number in binary and looking at just the top 5 bits (00000 000).
$56 = 01010110 = 01010 110. The 01010 is $A which is the same as INT($56 / 8).

And Antoine, never disagree with a mad man. You will never win. Solving software problems by voting is not an option. :)

At this time I will continue my disassembling of the Pascal runtime system for Wizardry IV and then start working on Segment 0 (WIZARDRY). This discrepancy will be solved at some point.

Tommy
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270633 is a reply to message #270630] Sun, 12 October 2014 00:32 Go to previous messageGo to next message
qkumba is currently offline  qkumba
Messages: 1584
Registered: March 2013
Karma: 0
Senior Member
Hi Tommy,

> One of us (or both of us) are terribly confused.
> "The full list looks like this (TT=track, PS=Pascal Sector, DS=DOS Sector):"
> "...first physical sector that is read (taken from a dsk image) is T $10 S 03".
> Your list does not include any TT=$10. I'm not sure what you are trying to say here.

$10==#$0A, which is in my list.

> Here is an important question to ask:
> How do you know that you are tracing the loading of the segment KANJIREA and not the segment COMBAT?

Because I see the value #$85 being accessed from the segment table, the length is #$104E bytes, and the data that I dumped contain a reference to KANJI.KRN.

> Hopefully we are both using the same disk image (or an identical one) from ASIMOV as a starting point, otherwise all this might be moot.

Yes, it's the same one.

> The start of SYSTEM.PASCAL is $46, and the relative start for COMBAT is $10. $46 + $10 = $56.
> Is this where your $56 is coming from?

No, the $#56 comes from a subtraction, not an addition.

> Part of the segment load table in memory starts at $C19 and after PASCAL.SYSTEM Dictionary Segment is loaded it looks like this:
> $C19: 0E 00 00 00 00 00 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 00 00 00 00 ....
> $C3A: 47 56 58 66 70 77 85....
> $C4A: 00 00 00 00 00 00 00....
> $C5A: 66 E0 3A FC E6 DE 4E....
> $C6A: 1C 03 1B 13 0C 1A 10....

Yes, I have seen this table.

....
> Once again, it looks to me like COMBAT and not KANJIREA is the segment that occupies Pascal block $56 on the disk.

I will check where the #$56 comes from, but I see #$85 being read from the table, and then converted to #$56. It's not COMBAT that I am seeing, because I am tracing from the boot, so it's part of the intro code that is being loaded.

> Also, your conversion algorithm for Pascal Blocks to Tracks / Sectors is the same as I posted earlier.
> For example, "track=block number / 8" is the same as looking at the block number in binary and looking at just the top 5 bits (00000 000).
>
> $56 = 01010110 = 01010 110. The 01010 is $A which is the same as INT($56 / 8).

Good. My algorithm is converted directly from the assembler code which I was reading shortly before I posted the previous message. :-)

> And Antoine, never disagree with a mad man. You will never win. Solving software problems by voting is not an option. :)

:-)

> At this time I will continue my disassembling of the Pascal runtime system for Wizardry IV and then start working on Segment 0 (WIZARDRY). This discrepancy will be solved at some point.

Something for you to try while you are waiting - what happens if you extract the mysterious segment using the track/sector list that I showed? You should be able to decompile it, and determine its name...
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270634 is a reply to message #270633] Sun, 12 October 2014 01:15 Go to previous messageGo to next message
qkumba is currently offline  qkumba
Messages: 1584
Registered: March 2013
Karma: 0
Senior Member
The #$56 comes from a table that is read from T00 S03. It begins 02 00 01 00 0C 00 00 00 00 00 56 00 85 00 56 00.
You can see the #$85 and the #$56.
The loader reads the #$01, and then the #$0C #$00. It searches the table for the matching block number (#$85) and then fetches the following word (#$56) for its location on disk. Not a subtraction after all.

DOCACHE has block number #$CD. This one becomes #$87, which is track #$10, Pascal sector #$0D, so physical sector #$01, begins #$C6 #$08 #$DE #$AA #$50 #$00.
Where do you think that DOCACHE lives on the disk, in comparison?
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270635 is a reply to message #270633] Sun, 12 October 2014 01:19 Go to previous messageGo to next message
qkumba is currently offline  qkumba
Messages: 1584
Registered: March 2013
Karma: 0
Senior Member
The #$56 comes from a table loaded to #$1000, which comes from T00 S03.
The loader finds the #$85, then reads the following word which is #$56, to find its location on disk.

Let's look at DOCACHE. It has block #$CD, which is converted to #$87, which becomes Track #$10, Pascal Sector #$0D, which is physical sector #1. It begins C6 08 DE AA 50 00 0A 28 05 1F. That is what I see being loaded into memory.
Where do you find it using your algorithm?
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270654 is a reply to message #270634] Sun, 12 October 2014 12:09 Go to previous messageGo to next message
gbody4 is currently offline  gbody4
Messages: 47
Registered: October 2012
Karma: 0
Member
Tommy,
what I was trying to say is the game is configured to work with a single drive.
To get this to work and reduce disk swapping the disks were arranged to hold all the required information and program code for the area being played.
As qkumba has shown the sector T00 S03 relates to the reorganisaztion, the second half is T00 S02. If I remember correctly when I reduced the number of disks from four back to two I used this data to build a new set of two disks which didn't need changing.
Byte offset 00 is checked for 02 and then using a base address starting at byte offset 02, 01 represents the disk number, +2 offset gives the number of segments on disk, flag byte, (two byte logical block number, two byte physical block number , segment block length) repeated for each segment on disk. Then the next disk starts with the 02 and continues for all four disks.

02 - check byte
00
01 - Disk number
00
0C - number of disk segments
00 - Check flag
0000 - logical block number
0000 - Physical block number
5600 (0056) - Segment length
8500 (0085) - logical block number
5600 (0056) - physical block number
1A00 (001A) - Segment length
... repeated for number of segments on disk
..... Next disk data starts with disk number.

This also holds true for Wiz 5 but it is bigger than two disks.

GeoffB
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270776 is a reply to message #270654] Wed, 15 October 2014 13:49 Go to previous messageGo to next message
TommyGoog is currently offline  TommyGoog
Messages: 112
Registered: January 2013
Karma: 0
Senior Member
On Sunday, October 12, 2014 11:09:20 AM UTC-5, geoff body wrote:
> Tommy,
>

>
> GeoffB

Hi qkumba and Geoff,

Thank you for looking into this segment dictionary disk address problem.

Most of what I previously posted is true for Pascal 1.1 used for Wizardry I, II, and III.

Maybe a bit surprisingly, it is also true for Pascal 1.2 for Wizardry IV and segment 1 ("WIZARDRY").

As you guys found, for the other segments (probably all of them?) the value from the Segment Dictionary is translated using the table that is loaded at $1000 from T00 S03 and T00 S02.

It is very clear to me that "WIZARDRY" is the first segment loaded, and then "KANJIREA" is the next segment. I can clearly see this in the properly decompiled code for WIZARDRY. The WIZARDRY segment is loaded by the bootstrap code using "CXP 1,1". Once it starts execution, and before the main WIZARDRY processing loop, the next segment load is "CXP C,1".

CD 0C 01 CXP. CALL EXTERNAL PROCEDURE: 01 IN SEGMENT: 0C

I can see the divergence in code execution between WIZARDRY and KANJIREA at $3D07:

3D07- 24 F2 BIT $F2
3D09- 10 F9 BPL $3D04

For WIZARDRY, $F2 is 0 and we take the branch to an immediate "JMP $E7FE".. During the $E7FE path, $1000 is not examined. It uses the block number ($47) found in the segment load table.

For KANJIREA, $F2 is #$80 so we drop into the next piece of code. That code eventually examines $1000 and translates the block number in the segment table into the real block number.

Its not clear to me yet what triggers the setting of $F2 to #$80, or what triggers the loading of the table to $1000 (the code that does it is in the routine at $EDAC.

It's also not clear to me exactly why this feature exists, but maybe that's because I'm just not thinking about this clearly enough and don't even have all the pieces to the puzzle yet.

If I wanted to just manually alter the Segment Dictionary on the disk to have the real block number, my decompiler would most likely produce properly decompiled listings, and obviously the code would not work.

The rest of this message will be a brief synopsis of the Pascal bootstrap code for Wizardry IV.

Stage 1: T 00 S 00, loaded to $800. Execution begins at $801.

Stage 2: Read #$B more sectors: T 00 S01 to T 00 S 0B (Pascal numbering) into $900..$13FF.

Stage 3: Find SYSTEM.INTERP in the directory and read the first 24 blocks (48 sectors) to $1400..$43FF

SYSTEM.INTERP is moved from $1400..$43FF to $D000..$FFFF (RAM memory, Bank 2). Execution begins at $D000.

Stage 4: The next 2 blocks (the last 2) of SYSTEM.INTERP are read from disk into $BC00..$BFFF. Execution begins at $BC00.

Note, when BC00 code completes, most of the space at $BC00..$BFFF is available as Pascal stack space.

BC00 initializes the stack pointer and lots of memory. It displays the text "WIZARDRY FOR THE APPLE II" on the text screen.
SYSTEM.PASCAL segment dictionary is used to initialize the segment load tables at $C19, $C3A, $C4A, $C5A, $C6A, $C7A, $C8A, $C9A.

Disk devices on the Apple are checked and the tables at $FC63 and $FC71 are updated (although improperly as there is a bug).
The "heart" of the Pascal p-code interpretter is transferred from $BC74..BCA3 to zero page locations at $90..$BF.
The heart starts pumping with a JMP $9D, and the pcode interpretter is now alive!

The first pcode instruction executed is found at $BFFB. This is what $BFFB looks like:

$BFFB: 00
$BFFC: 00
$BFFD: CD 01 01

Here is the "heart" of the interpreter:

0090- A2 00 LDX #$00
0092- A9 00 LDA #$00
0094- 48 PHA
0095- 8A TXA
0096- 48 PHA
0097- E6 9E INC $9E
0099- D0 02 BNE $009D
009B- E6 9F INC $9F
009D- AE FB BF LDX $BFFB IPC ($9E..$9F). Self-modified!
00A0- 10 F0 BPL $0092
00A2- BD 00 FC LDA $FC00,X
00A5- 85 AD STA $AD
00A7- BD 00 FD LDA $FD00,X
00AA- 85 AE STA $AE
00AC- 4C 00 FE JMP $FE00 Self-modified code!
00AF- 4C 00 00 JMP $0000
00B2- F0 BF BASE
00B4- 00 10 NP
00B6- 91 BC SEG
00B8- 91 BC JTAB
00BA- 91 BB JTAB - #$100
00BC- F0 BF MP
00BE- BF EE KP

Once you know a pcode value, you can find the routine that executes it by looking at $FC00,x and $FD00,x. For example, CXP is defined as #$CD, and FCCD is $9F, while FDCD is $D5, so the routine begins at $D59F.

I think I have the pcode registers properly labeled, but not 100% sure right now.

I compared a few routines for Pascal 1.2 with those for Pascal 1.1 and found they were drastically changed - if nothing else, they were optomized for speed and/or space use.

Well, time to explore more of this "maze" :)

Tommy
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270789 is a reply to message #270776] Wed, 15 October 2014 18:18 Go to previous messageGo to next message
qkumba is currently offline  qkumba
Messages: 1584
Registered: March 2013
Karma: 0
Senior Member
This is the sequence from WIZARDRY that sets $F2:80.

01 ab 09 ;this one sets $9164 to 1
a9 12
a5 09
00
1f
f0
00
9e 06 ;this one sets $F2 to #$80 when $9164==1
00

I haven't tried to decompile it, and as a result I haven't gone back far enough to see how or why it reached this point.
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270886 is a reply to message #270789] Sat, 18 October 2014 05:55 Go to previous messageGo to next message
TommyGoog is currently offline  TommyGoog
Messages: 112
Registered: January 2013
Karma: 0
Senior Member
On Wednesday, October 15, 2014 5:18:29 PM UTC-5, qkumba wrote:
> This is the sequence from WIZARDRY that sets $F2:80.
>

I've done a little more research on the Segment Table problem. I wish I had more to offer, but thought I would share this now.

Wizardry IV segment 1 procedure 1 (the mainline code) starts by verifying the following files are on the boot disk:

IS.APPLE
LINES.24
HAS.CACHE
HAS.STROPS
SYSTEM.RELOC
HAS.KRNSRCH
MAZE.INFO

If it found SYSTEM.RELOC, then 2 special calls to UNITWRITE() are made, something like this:

UNIT := 13; (* 13!? *)
DEST[ 0] := 0;
LENGTH := 31;
UNITWRITE( UNIT, DEST, LENGTH, FINDFILE( 'SYSTEM.RELOC'));

UNIT := 13; (* 13!? *)
DEST[ 0] := 1;
LENGTH := 31;
UNITWRITE( UNIT, DEST, LENGTH, DEST[ 0]);

The FINDFILE function returns the first Pascal block of SYSTEM.RELOC.

My documentation says UNITWRITE accepts UNIT values from 1 to 12. Wizardry IV has extended the definition of UNITWRITE as a "hook" into the pcode interpretter to do some devilish stuff.

When processing the first UNITWRITE, the first 3 blocks (hard-coded) of SYSTEM.RELOC, are read into memory at $1000 (also hard-coded). The file actually seems to occupy 4 blocks on the disk, and there is really less than 1 block (2 sectors) of useful information in it. FINDFILE and the directory indicate the file begins at Pascal block 6 which is...drum roll...T 00 S 0C (Pascal), T 00 S 03 (DOS).

The area at $1000 is the heap space pointed to by NP, and after processing the file, NP is updated to indicate this space is being used.

One thing not mentioned earlier is that after loading SYSTEM.RELOC to $1000, some of the values are altered. Each segment is represented by 3 words (2 bytes per word, with low byte first). In memory, the first word is added to the third word and stored into the third word.

The values of LENGTH and DEST[0] cause different sections of code to execute while processing these "UNITWRITEs".

The second UNITWRITE is unusual too. It sets $F2 to #$80. It also reads Pascal block 1 to $200..$3FF. I'm not sure why.

My notes on all this stuff to this point are quite a jumbled mess, but I thought I'd share this little information now in case anyone else is still looking at this.

A note to Robert Woodhead fans. Matt Barton ("Matt Chat" on Youtube), has a recent 3 part interview with Robert Woodhead.

Tommy
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270893 is a reply to message #270886] Sat, 18 October 2014 10:23 Go to previous messageGo to next message
gbody4 is currently offline  gbody4
Messages: 47
Registered: October 2012
Karma: 0
Member
Tommy,
Pascal 1.2 added units 13-20, using the following link have a look at the DISKMAP disk image.

http://apple2.callapple.org/software/tribby/apascal.html

GeoffB
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270933 is a reply to message #270893] Sun, 19 October 2014 00:27 Go to previous messageGo to next message
TommyGoog is currently offline  TommyGoog
Messages: 112
Registered: January 2013
Karma: 0
Senior Member
On Saturday, October 18, 2014 9:23:08 AM UTC-5, geoff body wrote:
> Tommy,
>
> Pascal 1.2 added units 13-20, using the following link have a look at the DISKMAP disk image.
>
>
>
> http://apple2.callapple.org/software/tribby/apascal.html
>
>
>
> GeoffB

Hi Geoff,

I stand corrected. Apple Pascal 1.2 did indeed define unit numbers 13-20 (and some others).

I had previously looked in the Apple Pascal 1.2 Update Manual and did not find any mention of changes to unit numbers, but today I found the following manual that has a lot more detail:

ftp://ftp.apple.asimov.net/pub/apple_II/documentation/progra mming/pascal/Apple%20II%20Pascal%201.2%20Device%20and%20Inte rrupt%20Support%20Tools%20Manual.pdf

Hopefully that link displays ok. It is the document:

Apple II Pascal 1.2 Device and Interrupt Support Tools Manual.pdf

Nevertheless, Wizardry IV is doing some nonstandard and bizarre things with unit 13 and UNITWRITEs, and what I assume are Robert Woodhead changes to the run-time interpreter.

Tommy
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270940 is a reply to message #270933] Sun, 19 October 2014 06:36 Go to previous messageGo to next message
Antoine Vignau is currently offline  Antoine Vignau
Messages: 1860
Registered: October 2012
Karma: 0
Senior Member
The $200..$3ff space is a perfect place for a protection check routine

av
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #270995 is a reply to message #270940] Sun, 19 October 2014 23:57 Go to previous messageGo to next message
Anonymous
Karma:
Originally posted by: awanderin

Antoine Vignau <antoine.vignau@laposte.net> writes:

> The $200..$3ff space is a perfect place for a protection check routine

Not to mention it overwrites the reset vector and interrupt vectors.

--
Jerry awanderin at gmail dot com
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #271027 is a reply to message #270995] Mon, 20 October 2014 11:27 Go to previous messageGo to next message
Anonymous
Karma:
Originally posted by: Don Bruder

In article <m3ppdne6lu.fsf@gmail.com>, awanderin <awanderin@gmail.com>
wrote:

> Antoine Vignau <antoine.vignau@laposte.net> writes:
>
>> The $200..$3ff space is a perfect place for a protection check routine
>
> Not to mention it overwrites the reset vector and interrupt vectors.

Which, in a protection check scheme, was considered to be a desirable
side-effect. Assuming it wasn't specifically coded with whacking those
vectors being an intended result - something that was true for several
CP schemes back in the day. I can remember seeing at least 3 (and I'd
bet there were many more that I never personally laid eyes on) that put
critical code at $0200 so that any attempt to use the "toys" in the
monitor to look at what was going on would cause the code to be garbaged
by the typed characters overwriting it. Likewise, they'd do things like
first trashing some or all of the various page 3 vectors, then if, and
only if, the protection check came back with an "OK" result, "un-trash"
them, either by cramming correct values into place, or moving a block of
RAM from someplace else over top of the needed locations.

Quite effective, considering how simple the trick was. When combined
with code obfuscation and/or encryption, and shifting code around in RAM
after it was loaded from disk, about the only way to actually get into a
program that used such techniques for a look at what was going on was by
boot-tracing, or use of hardware gizmos like the "Wildcard". Only a few
(comparatively speaking) had the know-how and motivation to slog through
the tedium of a boot-trace, and the Wildcard was a rather specialized
bit of gear that had very few other practical uses, and (at least back
in the day) sported a price tag high enough that only someone intending
to pirate software on a "for profit" basis would be willing to spring
for it. Which meant that most "casual copiers" were quite effectively
locked out.

--
Security provided by Mssrs Smith and/or Wesson. Brought to you by the letter Q
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #271105 is a reply to message #270933] Tue, 21 October 2014 05:23 Go to previous messageGo to next message
TommyGoog is currently offline  TommyGoog
Messages: 112
Registered: January 2013
Karma: 0
Senior Member
On Saturday, October 18, 2014 11:27:37 PM UTC-5, TommyGoog wrote:

> Nevertheless, Wizardry IV is doing some nonstandard and bizarre things with unit 13 and UNITWRITEs, and what I assume are Robert Woodhead changes to the run-time interpreter.
>

> Tommy

I have more information about the conversion from Segment Dictionary block addresses to the actual physical block addresses on disk.

Wizardry IV compiled code spans 5 diskettes. This is accomplished by modifying the Pascal p-code interpretter code (SYSTEM.INTERP), changing the values in the Segment Dictionary for SYSTEM.PASCAL (the main executable for Wizardry IV), and using the file SYSTEM.RELOC as a mapping file from the modified Segment Dictionary to each of the 5 diskettes that comprise the main Wizardry IV program.

Here is information from the Segment Dictionary for SYSTEM.PASCAL in a slightly better to read format (I hope):

Seg# SegName FirstBlock Size(in bytes)
1 WIZARDRY 01 0 66 1C
7 COMBAT 10 0 E0 03
8 CUTIL 12 0 3A 1B
9 CASTASPE 20 0 FC 13
A SWINGASW 2A 0 E6 0C
B CINIT 31 0 DE 1A
C KANJIREA 3F 0 4E 10
D UTILITIE 48 0 98 15
E SHOPS 53 0 E2 0B
F SPECIALS 59 0 34 2C
10 CAMP 70 0 CA 24
11 RUNNER 8B 0 E8 21
12 DOCOPY 83 0 A0 07
13 DOCACHE 87 0 2E 07

FirstBlock entries are relative to the first block of the Segment Dictionary (which is the same as the first block for SYSTEM.PASCAL). The first block for SYSTEM.PASCAL is $46. Therefore the WIZARDRY Segment FirstBlock is resolved as block $47, COMBAT is $56, KANJIREA is $85, etc.

SYSTEM.RELOC is arranged by Disks (1, 2, 3, 4, and 5) that correspond to the 5 disks comprising Wizardry IV. All values in SYSTEM.RELOC are Pascal word oriented (2 bytes, with low byte first).

Word 0 in the file is a signature (or version number?) that has the value 2..

After the signature value there are 5 Disk Sections. Each section starts with a 2 word header:

Word 0 Disk number (1, 2, 3, 4, or 5)
Word 1 Number of Segment Sections for this disk.

The rest of the disk section contain Segment Section entries (each 3 words).
Word 0 LowBlock
Word 1 Physical Block
Word 2 Segment Section Length (in Blocks)

A Segment Section is not always in a one-to-one relationship with a Segment.. Multiple Segments can (and do) map into one Segment Section. The Segment Section Length is the sum of the lengths of the individual Segments that map to this Segment Section.

These Segment Sections are arranged in increasing LowBlock order from smallest to largest.

Here is part of SYSTEM.RELOC:

Signature
02 00
...Disk Section for Disk 1:
...01 00 0C 00
.....Segment Section 0:
.....00 00 00 00 56 00
.....Segment Section 1:
.....B5 00 56 00 1A 00
.....
.....(There are a total of 12 (#$0C) Segment Sections for Disk 1.)
.....
...Disk Section for Disk 2:
...Disk Section for Disk 3:
...Disk Section for Disk 4:
...Disk Section for Disk 5:

Let's look at Segment Section 1 with KANJIREA, UTILITIE, and SHOPS.

The resolved FirstBlocks for these are:

KANJIREA = $46 + $3F = $85.
UTILITIE = $46 + $48 = $8E.
SHOPS = $46 + $53 = $99.

After loading SYSTEM.RELOC, the code adds LowBlock (Word 0) to Segment Section Length (Word 2) forming HighBlock(+1). Therefore the in-memory copy of SYSTEM.RELOC (at $1000) defines a "range" of blocks. For Segment Section 1:

LowBLock PhysicalBlock HighBlock(+1)
85 00 56 00 9F 00

If a resolved FirstBlock is in the range [$85..$9E], then this Segment Section defines the physical location on disk for the segment.

If the resolved FirstBlock for a Segment is equal to LowBlock, then its physical block is the next word (PhysicalBlock). For Segment Section 1 we see that KANJIREA has a resolved FirstBlock of $85, so its physical location on disk 1 is Block $56.

Now lets look at UTILITIE. The resolved FirstBlock is $8E which falls in the range [$85..$9E]. For Segments that do not start at the beginning of the Segment Section, we find the difference between the resolved FirstBlock and LowBlock and then add that difference to PhysicalBlock to determine its true physical block on the disk. In this case:

$8E - $85 = $09.
$56 + $09 = $5F.

Therefore $5F on disk 1 is the first block for Segment UTILITIE.

The file and the code are basically just combining consecutive segments into 1 entry in the Segment Section instead of having one segment per entry.

Some of this is actually starting to make sense!?

Tommy
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #283450 is a reply to message #271105] Thu, 05 February 2015 03:21 Go to previous messageGo to next message
TommyGoog is currently offline  TommyGoog
Messages: 112
Registered: January 2013
Karma: 0
Senior Member
On Tuesday, October 21, 2014 at 4:23:01 AM UTC-5, TommyGoog wrote:
> On Saturday, October 18, 2014 11:27:37 PM UTC-5, TommyGoog wrote:
> Tommy

After a break, the journey continues....

ASCII.KRN (Wizardy IV messages file) and ASCII.HUFF (control file for Huffman ASCII character encoding) have revealed themselves to me.

I've been working periodically on Wizardy IV over the past few months, and I'm making very slow progress, but today I can present to you the human readable contents of ASCII.KRN. ASCII.KRN contains most of the messages displayed during the game.

Here is a link to a spreadsheet I created with the messages:

https://docs.google.com/spreadsheets/d/1wyI3dCHVUeowkjVgLPjR rAb8CVHYgKxMwT1Kv5sZ0CU/edit?usp=sharing

But first I want to answer a question that I raised a few months ago concerning the reading of disks to address $200..$3FF. The code is just using that space as a buffer and is reading each diskette's block 1 there in order to obtain its "disk number". Each of the five disks is branded with a number 1 to 5 respectively to identify them to the Wizardry code. The last byte in the block is the disk #. The separate disk 6 (demo disk) has 6 stored there.

The table that I referred to in an earlier post to convert Pascal segment disk addresses to real diskette + Track/Sector addresses is used for more than just the segments. The entire 5 diskettes are driven by a modified Pascal runtime system that employs a "virtual disk" system. There is one file directory on disk #1. The information in the directory contains "virtual disk addresses" for all 5 disks. Those disk addresses are converted to real diskette + Track/Sector addresses using the information in SYSTEM.RELOC (loaded to $1000).

The file ASCII.KRN is a good example of a file that is spread out among the 5 diskettes.

Virtual Pascal Disk Block Real Pascal Disk Block
159..165 ------------------- Disk 1 159..165
166..166 ------------------- Disk 2 153..153
167..205 ------------------- Disk 1 166..204
206..218 ------------------- Disk 2 191..203
219..222 ------------------- Disk 1 205..208
223..223 ------------------- Disk 3 189..189
224..237 ------------------- Disk 4 206..219
238..247 ------------------- Disk 5 191..200
248..249 ------------------- Disk 3 195..196
250..269 ------------------- Disk 5 202..221
270..272 ------------------- Disk 1 210..212
273..273 ------------------- Disk 2 215..215
274..276 ------------------- Disk 1 213..215

I used a table like this to reconstruct the file ASCII.KRN on 1 single diskette before examining it more closely.

When looking for a block, it is possible that it exists on more than 1 diskette. The contents should be the same on both, but I haven't verified this for all duplicate blocks.

I also built a similar table to reconstruct SYSTEM.PASCAL (the Wizardry IV Pascal code) and then use my de-compiler on the reconstructed file.

As an example, if the code needs to read ASCII.KRN block 225, it finds it is in the Virtual range of blocks from 224 to 237. That range of blocks corresponds to the blocks on diskette #4 from 206 to 219. So the actual I/O occurs to diskette 4 at block 207.

In my earlier posts I was a bit baffled by the use of UNITREAD and UNITWRITE with device 13. From what I have seen to date, I believe the Wizardry IV code is using the "device driver" mechanism to have machine language code available for execution. It does this instead of having separate assembler code linked into the actual Wizardry segments. See the following document for more information about device drivers:

Apple II Pascal 1.2 Device and Interrupt Support Tools Manual.pdf

The device driver code is loaded with the Pascal Interpretter code in the address range $D000 - $FFFF. Note, don't confuse $D000 - $FFFF with $0D00 - $0FFF.

For example, the following code snippet reads the first 2 blocks of the file ASCII.HUFF into BUFFER, and then BUFFER is passed on a UNITWRITE using device 13 and "function" 32. The code to process device 13 and function 32 was inserted into the Pascal runtime interpretter code by the Wizardry programmers. The code takes the first 3 * 256 bytes of BUFFER and stores them at $0D00 to $0FFF.

S6D1 := 4;
FILENM := 'ASCII.HUFF';
BLOCK := FINDFILE( S6D1, FILENM);
UNITREAD( S6D1, BUFFER, 1024, BLOCK);

DEVICE := 13;
UNITWRITE( DEVICE, BUFFER, 32, 0)

Obviously to understand Wizardry IV, you need to understand most (if not all) of the inserted device driver code for device 13.

Earlier I stated that the mainline:

"...verif(ies) the following files are on the boot disk:
IS.APPLE
LINES.24
HAS.CACHE
HAS.STROPS
SYSTEM.RELOC
HAS.KRNSRCH"

That was slightly mis-stated. The code is using these files as "configuration" markers, much like information in a "Program.INI" file. Boolean variables are set TRUE or FALSE based on the existence of each file in the Disk 1 directory. Wizardry then checks the boolean variable and executes different code based on its value.

This doesn't mean for example that you can simply remove SYSTEM.RELOC and expect the program to work. The program was built and the directory was created with the understanding that that file would be present on the boot disk. I suspect that for a different computer disk system that they created the disk system without using SYSTEM.RELOC, and then did not include SYSTEM.RELOC on the boot disk.

Before showing you Huffman encoding, I'd like to show you this interesting Pascal bug I ran into:

PROGRAM TEST( INPUT, OUTPUT);
VAR
X : INTEGER;

BEGIN
FOR X := 1 to 10 DO
WRITELN( X);

FOR X := 10 to 10 DO
WRITELN( X);

FOR X := 32767 to 32767 DO
WRITELN( X)
END.

What is displayed when this program is run?

Obviously the "obvious answer" is not the correct one, otherwise I would not be presenting this to you.

**********

There is a good article on Huffman encoding at Wikipedia:

http://en.wikipedia.org/wiki/Huffman_coding

I found it to be helpful in understanding Huffman encoding, but parts of it were written a bit more complexly than for the average person. If you have questions about it, and you want to understand how it works, ask me a few questions and I will try to answer them. You can also look in Knuth's third volume for an understanding of weighted unbalanced binary trees.

The binary tree structure that drives the Huffman interpretation of strings for Wizardry IV are the 3 tables of 256 bytes each that are loaded to $0D00, $0E00, and $0F00, as found in ASCII.HUFF.

There are approximately 3000 message lines stored in the file ASCII.KRN.

In this posting, when I use the term "message" it usually refers to just one line of text. A multiple line message is simply a display of multiple "messages".

**********

ASCII.KRN description

The first word (2 bytes) in the file is the relative block offset to the ASCII.KRN "index" table. The next word (2 bytes) in the file is the number of bytes in the "index" table.

For the WizardryIV at Asimov, the values are:

Index Table: $74 (116)
Length: $EA (234)

Since ASCII.KRN begins at block v159 (I will generally use "v" to indicate "virtual" block address), the index table is at block v159+116 = v275.

v275 is on Disk 1 at Block 214.
Block 214 is $D6 is 11010110.
Converting to DOS: T$1A S$3 (DOS)

Here is the index table (Values are in decimal and each represents a 2 byte value):

0: 1 62 124 185 209 235 276 304
8: 357 428 514 614 715 901 1013 1048
16: 1104 1134 1374 1704 1913 2019 2055 2121
24: 2600 5048 12011 12298 12322 12345 12366 13007
32: 13053 13099 13150 13197 13244 13292 13335 13382
40: 13432 13479 14035 14081 14128 14174 14214 15051
48: 15800 16553 17402 17420 18101 18256 18350 18370
56: 18417 18558 18706 18857 18953 19352 19550 19858
64: 20001 20202 20304 20402 20556 21100 21454 21651
72: 21802 21953 22156 22300 22402 22608 22802 23103
80: 23305 23509 23801 24004 24250 24311 24358 24375
88: 24516 24651 24803 24950 25064 25202 25403 25805
96: 26015 26106 26205 26304 26321 26338 26360 26376
104: 26392 26413 26429 26451 26467 26483 26502 26560
112: 28015 29009 30250 30501 32767

Each entry in this table represents the value of the first message in a block (2 * 256 bytes), starting with the first block of ASCII.KRN. So the first block of the file will have any messages numbered from 1 to 61, then the next block in the file will have messages numbered from 62 to 123, and so on with the exception of the last entry that shows 32767. When trying to find a message in the file, this table is searched and the index to the entry in this table is the relative block offset that contains the message. Each block is self contained with messages, they do not "split" from one block to the next. Also, not all message numbers are used -- there are "gaps" in the sequences. All message numbers used in WizardryIV are positive integers from 1 to 32766.

Oh hey, there's that 32767. The answer to the Pascal bug is: 1 to 10; 10; 32767; then a sequence of consecutive numbers starting from -32768 and increasing to +32767 and then repeating indefinitely from -32768.

There are 2 parts to retrieving a message from the file:
1: find the start of the message;
2: convert the HUFF bits to ASCII bytes.

For the following discussion I will refer to an example of displaying message #2043 as part of a multi-line message. 2043 is between 2019 and 2055. This is entry 21 in the index table with the value 2019. 21 is the relative block offset (from the first block in ASCII.KRN) to the block containing the message #2043.

Add 21 to the first block, v159 + 21 = v180.
Converting to DOS format: v180 is at Disk 1 block 179 = $B3 =
T$16 S$9 (DOS)

I will refer to to this as a HUFF message block or a block with HUFF bytes (or bits), and I will now describe the contents of a HUFF message block.

The text of the messages start at the top of the buffer and work their way down. Each consists of a string of HUFF bytes representing the HUFF bits.

At the bottom of the block is the structure for the block describing where each message is. This structure starts at the last byte of the block and works upwards. These are usually byte values, although sometime there is a need for 2 bytes to describe a value.

Here is the end of block v180:

480: 48 27 28 29 01 22 05 05
488: 18 17 17 01 24 18 06 08
496: 31 21 12 30 30 11 10 10
504: 15 13 20 11 16 26 14 03

The last byte in the block (03) is a count of the number of groups of consecutive lists of message numbers in the block. For example, a block might have messages 2019-2032, 2040-2045, and 2050-2054. We would say this block has 3 groups of consecutive messages. Lets refer to this as GROUPCNT.

The next value (remember we are working from the end and moving up) in the block is the number of messages in the first set of consecutive messages (CONSECUTIVE). In my example this is 14 (2019 to 2032). The next 14 bytes in the file represent how many HUFF bytes are needed by each message. Following those (If this is not the last GROUP in the block), the next 1 (or 2) bytes represent the "GAP" between groups of numbers. This is the number of missing messages + 1. In my example this is 1 byte (8) representing messages 2033 to 2039.

This is one place where a 2 byte value might be needed. If the gap is greater than 127, then the high order bit of the first byte encountered is set, the remainder of the byte represents the gap size DIV 256 and the next byte represents gap size MOD 256.

The next byte encountered (again moving from the end to the top of the buffer) is the next CONSECUTIVE value and we continue upward in the block until the last GROUPCNT group has been processed.

Starting from the end:

Offset Value Description
511:....3...... GROUPCNT
510:...14...... CONSECUTIVE1
................ 14 bytes representing HUFF byte counts
495:....8...... GAP1
494:....6...... CONSECUTIVE2
................ 6 bytes representing HUFF byte counts
487:....5...... GAP2
486:....5...... CONSECUTIVE3
................ 5 bytes representing HUFF byte counts

The last byte in this table is therefore:
481:...27...... number of HUFF bytes in last message (#2054) in this block.

So how do we find message 2043? We start at the end of the block and count the number of HUFF bytes for each message that precedes our message (!!!)

Since 2019 is the first message in this block, we are looking for message 2043 minus 2019 = message 24 (relative to the first).

You know, this stuff seemed interesting to me when I was discovering it, but I'm afraid I'm putting my readers to sleep. If you find this interesting, please let me know.

There are 3 groups. The first group has 14 messages. Add the HUFF byte counts for these 14 messages:
26+16+11+20+13+15+10+10+11+30+30+12+21+31 = 256.

Each time we add a number we decrement the relative counter (24) downward by 1. We have "skipped" the first 14 messages and have 10 more to go.

Next, we account for the gap of 8 (7 missing messages), so our relative counter is down to 3.

The next group has 6 messages and we process the first 3 byte counts:
18+24+1 = 43.

For each message counted we decrement our relative counter by 1, so after these 3 messages we are now positioned from the top of the HUFF block to the beginning of message 2043.

We counted 299 (256 + 43) HUFF bytes before our message, so message #2043 begins at offset 299 from the top of this HUFF message block, and it has 17 HUFF bytes.

Note, the number of HUFF bytes in a message is NOT the number of ASCII characters in the message! The purpose of Huffman encoding is to compress the data.

Since 299 is greater than 255, the message is in the second half of the block.

256: 224 208 138 206 83 156 17 116
264: 205 219 212 30 46 131 130 131
272: 143 126 144 209 138 206 195 179
280: 5 221 136 30 174 10 174 165
288: 243 233 44 79 195 239 52 75
296: 87 237 145 224 120 239 233 53
304: 62 221 17 60 106 221 57 13
312: 207 233 76 12

Step 1 is complete. Here is message #2043:

299: 224 120 239 233 53 62 221 17 60 106 221 57 13 207 233 76 12

These numbers represent bits. Each byte is shifted right and the rightmost bit shifted out is the one that drives the HUFF binary tree traversal. By convention, 1 means go down the left branch, 0 means go down the right branch.

HUFF bits for an ASCII character might only occupy part of a byte, and they might also cross byte boundaries.

It might be better for discussion purposes to lay these out in binary format and put the high order bit on the right:

.................... Reversed
299: 224 11100000 00000111
300: 120 01111000 00011110
301: 239 11101111 11110111
302: 233 11101001 10010111
303: 53 00110101 10101100
304: 62 00111110 01111100
305: 221 11011101 10111011
306: 17 00010001 10001000
307: 60 00111100 00111100
308: 106 01101010 01010110
309: 221 11011101 10111011
310: 57 00111001 10011100
311: 13 00001101 10110000
312: 207 11001111 11110011
313: 233 11101001 10010111
314: 76 01001100 00110010
315: 12 x0001100 0011000x

Think of this as a conveyer belt rotating to the left with 1 bit falling off at a time. One very long sequence of bits.

Once enough bits have fallen off to recognize the bits represent an ASCII character, we write the ASCII character down, and start over with the next bit.

Since these messages represent Pascal "strings", the first converted character is the length field.

00000111 00011110 11110111 10010111 10101100 01111100 10111011
10001000 00111100 01010110 10111011 10011100 10110000 11110011
10010111 00110010 0011000x

Using the HUFF binary tree structure, the bits are converted to ASCII characters by traversing the tree.

0000011100 = CHR( 20) = Length
01111 = CHR( 32) = " "
01111 = CHR( 32) = " "
01111 = CHR( 32) = " "
00101111 = CHR( 65) = "A"
010110 = CHR( 110) = "n"
00111110 = CHR( 100) = "d"
010111 = CHR( 114) = "r"
01110 = CHR( 101) = "e"
0010000 = CHR( 119) = "w"
01111 = CHR( 32) = " "
000101011 = CHR( 71) = "G"
010111 = CHR( 114) = "r"
01110 = CHR( 101) = "e"
01110 = CHR( 101) = "e"
010110 = CHR( 110) = "n"
00011110 = CHR( 98) = "b"
01110 = CHR( 101) = "e"
010111 = CHR( 114) = "r"
0011001 = CHR( 103) = "g"
00011000 = CHR( 44) = ","

" Andrew Greenberg,"


Hmmm. Each bit stream for these ASCII characters starts with "0". Maybe the HUFF binary tree was not constructed properly (?)

Hmmm. After awhile, all I start to see are ones and zeros!

If anyone wants me to post more information about the actual unbalanced binary Huffman tree used by WizardryIV, let me know and I will work on it.

If you have questions or comments I'd love to hear them!

Tommy
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #283714 is a reply to message #283450] Sun, 08 February 2015 03:28 Go to previous messageGo to next message
TommyGoog is currently offline  TommyGoog
Messages: 112
Registered: January 2013
Karma: 0
Senior Member
>
> Hmmm. Each bit stream for these ASCII characters starts with "0". Maybe the HUFF binary tree was not constructed properly (?)
>
> Tommy

The Huffman encoding used in Wizardry IV is badly flawed.

At the end of this message I have included two tables showing the Huffman binary bit representation for each ASCII (and 8 bit) character. The flaw is that there is one character represented by the single bit "1" while all the other characters begin with a "0" and at least 4 more bits. This will never be a normal Huffman tree unless that one character represents half the characters in the text messages.

And that leads me to a likely explanation for their mistake!

Before constructing the Huffman binary tree, they first counted the occurrence of each character in the entire message. In this case they examined all the messages in file ASCII.KRN used in the Wizardry game. These are represented by STRING variables in PASCAL so each string begins with a 1 byte value containing the number of characters in the string. For example, "WIZARDRY" is 8 characters, so there are 9 bytes for the string:

08 87 73 90 65 82 68 82 89

So in this instance the character CHR(8) is counted as a character, along with CHR( 87), CHR(73), etc.

But what if their program had a bug? Let's say they didn't use the built in STRING declaration but used:

ARRAY[0..80] OF CHAR

....when they should have used:

PACKED ARRAY[0..80] OF CHAR.

And let's say when they were counting characters they were working on 8 bit byte values? The ASCII internal representation for "WIZARDRY" would now be:

08 00 87 00 73 00 90 00 65 00 82 00 68 00 82 00 89 00

Now when they counted the characters, they would have found CHR(0) 9 times!

And when they built the WEIGHTED binary Huffman tree, the CHR(0) would occupy one entire branch of the root node.

*****

Or they could have had a simple bug in their Huffman tree construction..., but I like me hypothesis better.

What does it mean to have the bug? It means they could have improved the compression by another 15-18 percent.

Unless I have screed up somewhere, there is another mystery with ASCII.KRN messages. As noted in my earlier post, there seem to be messages numbered above 28,000 that contain a lot of non-printable ASCII characters and values greater than 127 (high order bit set in the byte). I'm not sure if these are actual messages in the game, or random left-over garbage, or messages that are handled specially in Wizardry IV. Time will tell.

Worth noting is that I think they must have used these messages from 28000 to 32766 when counting characters and building the Huffman binary tree.

I have split the messages into 2 tables: 1. Messages 1 to 27,999; 2. Messages 28,000 to 32766. In addition, I have split the first "count" column into two parts: 1. "LENGTH" counts; 2. Character counts.

HUFFMAN CHARACTER OCCURRENCES
-----------------------------
FIRST MESSAGE INDEX = 1
LAST MESSAGE INDEX = 27999

LENCNT CHRCNT CHR( ORD)= CHR HUFFBITS
------ ------ ---------- --- --------
90 28 CHR( 0)= 1
164 0 CHR( 1)= 010011
12 0 CHR( 2)= 0011110
47 0 CHR( 3)= 00110110
68 0 CHR( 4)= 00101110
102 1 CHR( 5)= 00100111
132 0 CHR( 6)= 00110111
91 0 CHR( 7)= 00011001
80 0 CHR( 8)= 00011101
63 35 CHR( 9)= 00101010
67 11 CHR( 10)= 000111111
72 13 CHR( 11)= 0000101111
108 9 CHR( 12)= 00011010
103 13 CHR( 13)= 0000111111
108 15 CHR( 14)= 00010000
95 14 CHR( 15)= 000100111
38 0 CHR( 16)= 0000101100
27 0 CHR( 17)= 00000100111
23 0 CHR( 18)= 0000011001
17 0 CHR( 19)= 0000101110
22 0 CHR( 20)= 0000011100
23 0 CHR( 21)= 0000100101
32 0 CHR( 22)= 00000011000
27 0 CHR( 23)= 000000101111
33 0 CHR( 24)= 0000010100
31 0 CHR( 25)= 0000011000
36 0 CHR( 26)= 00000100010
28 0 CHR( 27)= 00000100101
42 0 CHR( 28)= 0000100110
42 0 CHR( 29)= 00000101011
63 0 CHR( 30)= 0000100010
76 0 CHR( 31)= 000010000
105 9776 CHR( 32)= 01111
120 461 CHR( 33)= ! 00100110
165 267 CHR( 34)= " 000110111
144 49 CHR( 35)= # 000100010
156 19 CHR( 36)= $ 000011100
88 6 CHR( 37)= % 0000101011
173 11 CHR( 38)= & 0000101101
29 99 CHR( 39)= ' 000010100
2 55 CHR( 40)= ( 00000111110
0 108 CHR( 41)= ) 0000100111
0 80 CHR( 42)= * 0000100011
0 13 CHR( 43)= + 000000110110
1 443 CHR( 44)= , 00011000
0 119 CHR( 45)= - 000011001
1 655 CHR( 46)= . 00100100
0 40 CHR( 47)= / 00000100100
0 26 CHR( 48)= 0 00000110110
0 37 CHR( 49)= 1 0000011010
0 13 CHR( 50)= 2 00000101111
1 15 CHR( 51)= 3 00000011010
0 3 CHR( 52)= 4 000000111111
0 7 CHR( 53)= 5 00000100110
0 6 CHR( 54)= 6 00000100011
0 2 CHR( 55)= 7 0000000100101
0 10 CHR( 56)= 8 000000100111
0 9 CHR( 57)= 9 00000011001
0 45 CHR( 58)= : 000000111110
0 1 CHR( 59)= ; 0000000110111
0 11 CHR( 60)= < 00000011101
0 7 CHR( 61)= = 000000100110
0 16 CHR( 62)= > 000000101010
0 116 CHR( 63)= ? 0000011110
0 85 CHR( 64)= @ 00000110111
0 654 CHR( 65)= A 00101111
0 234 CHR( 66)= B 000100110
0 306 CHR( 67)= C 000101111
0 366 CHR( 68)= D 00011100
0 537 CHR( 69)= E 0011000
0 214 CHR( 70)= F 000011110
0 319 CHR( 71)= G 000101011
0 338 CHR( 72)= H 000101110
0 504 CHR( 73)= I 00100011
0 15 CHR( 74)= J 000011010
0 143 CHR( 75)= K 0000101010
0 368 CHR( 76)= L 000110110
0 315 CHR( 77)= M 000101010
0 300 CHR( 78)= N 000111110
0 468 CHR( 79)= O 00100010
0 303 CHR( 80)= P 00010100
0 16 CHR( 81)= Q 000000101110
0 408 CHR( 82)= R 0010100
0 536 CHR( 83)= S 00101100
0 602 CHR( 84)= T 00101101
0 177 CHR( 85)= U 000100100
0 69 CHR( 86)= V 0000010110
0 256 CHR( 87)= W 000011101
0 12 CHR( 88)= X 0000000111011
0 263 CHR( 89)= Y 000011011
0 15 CHR( 90)= Z 000000100101
0 0 CHR( 91)= [ ----------------
0 0 CHR( 92)= \ ----------------
0 0 CHR( 93)= ] ----------------
0 376 CHR( 94)= ^ 000100101
0 0 CHR( 95)= _ ----------------
0 0 CHR( 96)= ` ----------------
0 3222 CHR( 97)= a 011010
0 520 CHR( 98)= b 00011110
0 825 CHR( 99)= c 00111111
0 1275 CHR( 100)= d 00111110
0 4918 CHR( 101)= e 01110
0 806 CHR( 102)= f 00101011
0 890 CHR( 103)= g 0011001
0 1887 CHR( 104)= h 010001
0 2474 CHR( 105)= i 010101
0 45 CHR( 106)= j 00000011100
0 375 CHR( 107)= k 00010110
0 1873 CHR( 108)= l 010010
0 920 CHR( 109)= m 0011100
0 2618 CHR( 110)= n 010110
0 3369 CHR( 111)= o 011011
0 629 CHR( 112)= p 00100101
0 24 CHR( 113)= q 000000100010
0 2599 CHR( 114)= r 010111
0 2509 CHR( 115)= s 010100
0 3372 CHR( 116)= t 01100
0 1456 CHR( 117)= u 010000
0 356 CHR( 118)= v 000100011
0 597 CHR( 119)= w 0010000
0 41 CHR( 120)= x 00000010110
0 953 CHR( 121)= y 0011010
0 41 CHR( 122)= z 0000011101
0 0 CHR( 123)= { ----------------
0 0 CHR( 124)= | ----------------
0 0 CHR( 125)= } ----------------
0 43 CHR( 126)= ~ 00000010100
0 0 CHR( 127)= ----------------
0 0 CHR( 128)= ----------------
0 0 CHR( 129)= ----------------
0 0 CHR( 130)= ----------------
0 0 CHR( 131)= ----------------
0 0 CHR( 132)= ----------------
0 0 CHR( 133)= ----------------
0 0 CHR( 134)= ----------------
0 0 CHR( 135)= ----------------
0 0 CHR( 136)= ----------------
0 0 CHR( 137)= ----------------
0 0 CHR( 138)= ----------------
0 0 CHR( 139)= ----------------
0 0 CHR( 140)= ----------------
0 0 CHR( 141)= ----------------
0 0 CHR( 142)= ----------------
0 0 CHR( 143)= ----------------
0 0 CHR( 144)= ----------------
0 0 CHR( 145)= ----------------
0 0 CHR( 146)= ----------------
0 0 CHR( 147)= ----------------
0 0 CHR( 148)= ----------------
0 0 CHR( 149)= ----------------
0 0 CHR( 150)= ----------------
0 0 CHR( 151)= ----------------
0 0 CHR( 152)= ----------------
0 0 CHR( 153)= ----------------
0 0 CHR( 154)= ----------------
0 0 CHR( 155)= ----------------
0 0 CHR( 156)= ----------------
0 0 CHR( 157)= ----------------
0 0 CHR( 158)= ----------------
0 0 CHR( 159)= ----------------
0 0 CHR( 160)= ----------------
0 0 CHR( 161)= ----------------
0 0 CHR( 162)= ----------------
0 0 CHR( 163)= ----------------
0 0 CHR( 164)= ----------------
0 0 CHR( 165)= ----------------
0 0 CHR( 166)= ----------------
0 0 CHR( 167)= ----------------
0 0 CHR( 168)= ----------------
0 0 CHR( 169)= ----------------
0 0 CHR( 170)= ----------------
0 0 CHR( 171)= ----------------
0 0 CHR( 172)= ----------------
0 0 CHR( 173)= ----------------
0 0 CHR( 174)= ----------------
0 0 CHR( 175)= ----------------
0 0 CHR( 176)= ----------------
0 0 CHR( 177)= ----------------
0 0 CHR( 178)= ----------------
0 0 CHR( 179)= ----------------
0 0 CHR( 180)= ----------------
0 0 CHR( 181)= ----------------
0 0 CHR( 182)= ----------------
0 0 CHR( 183)= ----------------
0 0 CHR( 184)= ----------------
0 0 CHR( 185)= ----------------
0 0 CHR( 186)= ----------------
0 0 CHR( 187)= ----------------
0 0 CHR( 188)= ----------------
0 0 CHR( 189)= ----------------
0 0 CHR( 190)= ----------------
0 0 CHR( 191)= ----------------
0 0 CHR( 192)= ----------------
0 0 CHR( 193)= ----------------
0 0 CHR( 194)= ----------------
0 0 CHR( 195)= ----------------
0 0 CHR( 196)= ----------------
0 0 CHR( 197)= ----------------
0 0 CHR( 198)= ----------------
0 0 CHR( 199)= ----------------
0 0 CHR( 200)= ----------------
0 0 CHR( 201)= ----------------
0 0 CHR( 202)= ----------------
0 0 CHR( 203)= ----------------
0 0 CHR( 204)= ----------------
0 0 CHR( 205)= ----------------
0 0 CHR( 206)= ----------------
0 0 CHR( 207)= ----------------
0 0 CHR( 208)= ----------------
0 0 CHR( 209)= ----------------
0 0 CHR( 210)= ----------------
0 0 CHR( 211)= ----------------
0 0 CHR( 212)= ----------------
0 0 CHR( 213)= ----------------
0 0 CHR( 214)= ----------------
0 0 CHR( 215)= ----------------
0 0 CHR( 216)= ----------------
0 0 CHR( 217)= ----------------
0 0 CHR( 218)= ----------------
0 0 CHR( 219)= ----------------
0 0 CHR( 220)= ----------------
0 0 CHR( 221)= ----------------
0 0 CHR( 222)= ----------------
0 0 CHR( 223)= ----------------
0 0 CHR( 224)= ----------------
0 0 CHR( 225)= ----------------
0 0 CHR( 226)= ----------------
0 0 CHR( 227)= ----------------
0 0 CHR( 228)= ----------------
0 0 CHR( 229)= ----------------
0 0 CHR( 230)= ----------------
0 0 CHR( 231)= ----------------
0 0 CHR( 232)= ----------------
0 0 CHR( 233)= ----------------
0 0 CHR( 234)= ----------------
0 0 CHR( 235)= ----------------
0 0 CHR( 236)= ----------------
0 0 CHR( 237)= ----------------
0 0 CHR( 238)= ----------------
0 0 CHR( 239)= ----------------
0 0 CHR( 240)= ----------------
0 0 CHR( 241)= ----------------
0 0 CHR( 242)= ----------------
0 0 CHR( 243)= ----------------
0 0 CHR( 244)= ----------------
0 0 CHR( 245)= ----------------
0 0 CHR( 246)= ----------------
0 0 CHR( 247)= ----------------
0 0 CHR( 248)= ----------------
0 0 CHR( 249)= ----------------
0 0 CHR( 250)= ----------------
0 0 CHR( 251)= ----------------
0 0 CHR( 252)= ----------------
0 0 CHR( 253)= ----------------
0 0 CHR( 254)= ----------------
0 0 CHR( 255)= ----------------



HUFFMAN CHARACTER OCCURRENCES
-----------------------------
FIRST MESSAGE INDEX = 28000
LAST MESSAGE INDEX = 32766

LENCNT CHRCNT CHR( ORD)= CHR HUFFBITS
------ ------ ---------- --- --------
0 763 CHR( 0)= 1
0 69 CHR( 1)= 010011
1 59 CHR( 2)= 0011110
0 61 CHR( 3)= 00110110
60 56 CHR( 4)= 00101110
1 23 CHR( 5)= 00100111
3 40 CHR( 6)= 00110111
1 23 CHR( 7)= 00011001
1 35 CHR( 8)= 00011101
1 41 CHR( 9)= 00101010
153 31 CHR( 10)= 000111111
0 34 CHR( 11)= 0000101111
0 39 CHR( 12)= 00011010
0 24 CHR( 13)= 0000111111
0 36 CHR( 14)= 00010000
0 32 CHR( 15)= 000100111
1 35 CHR( 16)= 0000101100
0 13 CHR( 17)= 00000100111
0 19 CHR( 18)= 0000011001
0 38 CHR( 19)= 0000101110
0 2 CHR( 20)= 0000011100
1 0 CHR( 21)= 0000100101
2 0 CHR( 22)= 00000011000
0 0 CHR( 23)= ----------------
0 0 CHR( 24)= ----------------
0 0 CHR( 25)= ----------------
0 0 CHR( 26)= ----------------
0 0 CHR( 27)= ----------------
0 2 CHR( 28)= 0000100110
0 0 CHR( 29)= ----------------
0 0 CHR( 30)= ----------------
0 1 CHR( 31)= 000010000
1 22 CHR( 32)= 01111
0 7 CHR( 33)= ! 00100110
1 3 CHR( 34)= " 000110111
1 0 CHR( 35)= # 000100010
0 1 CHR( 36)= $ 000011100
0 1 CHR( 37)= % 0000101011
0 1 CHR( 38)= & 0000101101
0 1 CHR( 39)= ' 000010100
0 1 CHR( 40)= ( 00000111110
0 2 CHR( 41)= ) 0000100111
0 2 CHR( 42)= * 0000100011
0 0 CHR( 43)= + ----------------
0 3 CHR( 44)= , 00011000
0 1 CHR( 45)= - 000011001
0 0 CHR( 46)= . ----------------
0 0 CHR( 47)= / ----------------
0 0 CHR( 48)= 0 ----------------
0 1 CHR( 49)= 1 0000011010
0 1 CHR( 50)= 2 00000101111
0 2 CHR( 51)= 3 00000011010
0 3 CHR( 52)= 4 000000111111
0 1 CHR( 53)= 5 00000100110
0 1 CHR( 54)= 6 00000100011
0 0 CHR( 55)= 7 ----------------
0 4 CHR( 56)= 8 000000100111
0 2 CHR( 57)= 9 00000011001
0 1 CHR( 58)= : 000000111110
0 0 CHR( 59)= ; ----------------
0 2 CHR( 60)= < 00000011101
0 1 CHR( 61)= = 000000100110
0 1 CHR( 62)= > 000000101010
0 0 CHR( 63)= ? ----------------
0 1 CHR( 64)= @ 00000110111
0 8 CHR( 65)= A 00101111
0 3 CHR( 66)= B 000100110
0 0 CHR( 67)= C ----------------
0 5 CHR( 68)= D 00011100
0 5 CHR( 69)= E 0011000
0 4 CHR( 70)= F 000011110
0 5 CHR( 71)= G 000101011
0 2 CHR( 72)= H 000101110
0 4 CHR( 73)= I 00100011
0 2 CHR( 74)= J 000011010
0 3 CHR( 75)= K 0000101010
0 2 CHR( 76)= L 000110110
0 2 CHR( 77)= M 000101010
0 3 CHR( 78)= N 000111110
0 6 CHR( 79)= O 00100010
0 1 CHR( 80)= P 00010100
0 2 CHR( 81)= Q 000000101110
0 7 CHR( 82)= R 0010100
0 1 CHR( 83)= S 00101100
0 2 CHR( 84)= T 00101101
0 0 CHR( 85)= U ----------------
0 1 CHR( 86)= V 0000010110
0 5 CHR( 87)= W 000011101
0 0 CHR( 88)= X ----------------
0 1 CHR( 89)= Y 000011011
0 1 CHR( 90)= Z 000000100101
0 1 CHR( 91)= [ 0000000111010
0 6 CHR( 92)= \ 000000110111
0 2 CHR( 93)= ] 00000010000
0 2 CHR( 94)= ^ 000100101
0 1 CHR( 95)= _ 0000000100000
0 0 CHR( 96)= ` ----------------
0 12 CHR( 97)= a 011010
0 2 CHR( 98)= b 00011110
0 5 CHR( 99)= c 00111111
0 6 CHR( 100)= d 00111110
0 13 CHR( 101)= e 01110
0 4 CHR( 102)= f 00101011
0 9 CHR( 103)= g 0011001
0 9 CHR( 104)= h 010001
0 14 CHR( 105)= i 010101
0 2 CHR( 106)= j 00000011100
0 5 CHR( 107)= k 00010110
0 6 CHR( 108)= l 010010
0 0 CHR( 109)= m ----------------
0 15 CHR( 110)= n 010110
0 12 CHR( 111)= o 011011
0 1 CHR( 112)= p 00100101
0 0 CHR( 113)= q ----------------
0 11 CHR( 114)= r 010111
0 8 CHR( 115)= s 010100
0 14 CHR( 116)= t 01100
0 2 CHR( 117)= u 010000
0 2 CHR( 118)= v 000100011
0 4 CHR( 119)= w 0010000
0 1 CHR( 120)= x 00000010110
0 5 CHR( 121)= y 0011010
0 1 CHR( 122)= z 0000011101
0 1 CHR( 123)= { 0000000000000011
0 1 CHR( 124)= | 000000000011101
0 1 CHR( 125)= } 0000000000001001
0 0 CHR( 126)= ~ ----------------
0 0 CHR( 127)= ----------------
0 0 CHR( 128)= ----------------
0 1 CHR( 129)= 000000000001100
0 0 CHR( 130)= ----------------
0 1 CHR( 131)= 00000000111010
0 0 CHR( 132)= ----------------
0 3 CHR( 133)= 00000000010110
0 1 CHR( 134)= 00000000101100
0 1 CHR( 135)= 00000000101110
0 1 CHR( 136)= 00000000111111
0 0 CHR( 137)= ----------------
0 0 CHR( 138)= ----------------
0 0 CHR( 139)= ----------------
0 0 CHR( 140)= ----------------
0 0 CHR( 141)= ----------------
0 1 CHR( 142)= 00000000111110
0 0 CHR( 143)= ----------------
0 0 CHR( 144)= ----------------
0 0 CHR( 145)= ----------------
0 1 CHR( 146)= 0000000101110
0 1 CHR( 147)= 0000000000010010
0 0 CHR( 148)= ----------------
0 0 CHR( 149)= ----------------
0 0 CHR( 150)= ----------------
0 0 CHR( 151)= ----------------
0 1 CHR( 152)= 0000000000010111
0 0 CHR( 153)= ----------------
0 1 CHR( 154)= 000000000100111
0 0 CHR( 155)= ----------------
0 1 CHR( 156)= 000000011000
0 0 CHR( 157)= ----------------
0 0 CHR( 158)= ----------------
0 0 CHR( 159)= ----------------
0 1 CHR( 160)= 00000101010
0 1 CHR( 161)= 00000000011110
0 0 CHR( 162)= ----------------
0 0 CHR( 163)= ----------------
0 1 CHR( 164)= 0000000100100
0 0 CHR( 165)= ----------------
0 1 CHR( 166)= 00000000011011
0 0 CHR( 167)= ----------------
0 0 CHR( 168)= ----------------
0 1 CHR( 169)= 00000000111100
0 0 CHR( 170)= ----------------
0 3 CHR( 171)= 00000001011111
0 0 CHR( 172)= ----------------
0 0 CHR( 173)= ----------------
0 1 CHR( 174)= 00000000011010
0 0 CHR( 175)= ----------------
0 2 CHR( 176)= 0000000110110
0 0 CHR( 177)= ----------------
0 0 CHR( 178)= ----------------
0 0 CHR( 179)= ----------------
0 0 CHR( 180)= ----------------
0 0 CHR( 181)= ----------------
0 1 CHR( 182)= 0000000000001111
0 0 CHR( 183)= ----------------
0 0 CHR( 184)= ----------------
0 0 CHR( 185)= ----------------
0 0 CHR( 186)= ----------------
0 2 CHR( 187)= 000000000010111
0 1 CHR( 188)= 000000000100110
0 0 CHR( 189)= ----------------
0 0 CHR( 190)= ----------------
0 1 CHR( 191)= 0000000000001011
0 1 CHR( 192)= 000000101011
0 1 CHR( 193)= 000000000010110
0 0 CHR( 194)= ----------------
0 2 CHR( 195)= 00000000100101
0 0 CHR( 196)= ----------------
0 2 CHR( 197)= 000000000001101
0 1 CHR( 198)= 000000000010011
0 2 CHR( 199)= 00000000011101
0 3 CHR( 200)= 0000000111110
0 1 CHR( 201)= 000000000011011
0 1 CHR( 202)= 00000000100011
0 2 CHR( 203)= 00000000100010
0 0 CHR( 204)= ----------------
0 2 CHR( 205)= 0000000010000
0 1 CHR( 206)= 00000000110010
0 1 CHR( 207)= 0000000011000
0 4 CHR( 208)= 0000000101011
0 4 CHR( 209)= 00000000101011
0 3 CHR( 210)= 0000000011100
0 0 CHR( 211)= ----------------
0 0 CHR( 212)= ----------------
0 0 CHR( 213)= ----------------
0 0 CHR( 214)= ----------------
0 0 CHR( 215)= ----------------
0 1 CHR( 216)= 000000000100011
0 2 CHR( 217)= 000000000001000
0 0 CHR( 218)= ----------------
0 1 CHR( 219)= 000000000100010
0 0 CHR( 220)= ----------------
0 1 CHR( 221)= 00000000000001111
0 0 CHR( 222)= ----------------
0 0 CHR( 223)= ----------------
0 0 CHR( 224)= ----------------
0 0 CHR( 225)= ----------------
0 0 CHR( 226)= ----------------
0 0 CHR( 227)= ----------------
0 1 CHR( 228)= 00000000000001110
0 1 CHR( 229)= 00000000101001
0 0 CHR( 230)= ----------------
0 3 CHR( 231)= 00000000110111
0 1 CHR( 232)= 00000000011001
0 0 CHR( 233)= ----------------
0 0 CHR( 234)= ----------------
0 0 CHR( 235)= ----------------
0 1 CHR( 236)= 0000000100001
0 0 CHR( 237)= ----------------
0 0 CHR( 238)= ----------------
0 0 CHR( 239)= ----------------
0 2 CHR( 240)= 00000000100111
0 0 CHR( 241)= ----------------
0 0 CHR( 242)= ----------------
0 0 CHR( 243)= ----------------
0 0 CHR( 244)= ----------------
0 1 CHR( 245)= 000000000011110
0 0 CHR( 246)= ----------------
0 0 CHR( 247)= ----------------
0 2 CHR( 248)= 0000000101001
0 1 CHR( 249)= 00000101110
0 0 CHR( 250)= ----------------
0 0 CHR( 251)= ----------------
0 1 CHR( 252)= 000000011110
0 1 CHR( 253)= 00000111111
0 1 CHR( 254)= 000011000
0 124 CHR( 255)= 0011101


Tommy
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #283792 is a reply to message #283714] Mon, 09 February 2015 00:51 Go to previous messageGo to next message
sicklittlemonkey is currently offline  sicklittlemonkey
Messages: 570
Registered: October 2012
Karma: 0
Senior Member
On Sunday, 8 February 2015 21:28:20 UTC+13, TommyGoog wrote:
> The Huffman encoding used in Wizardry IV is badly flawed.

All epic ... but that is a great find! : - D

I did Huffman and RLE graphics compression on the 6502 back in '91, and it was slow, but about as good as LZW (which makes sense). Pretty sure I didn't make a silly mistake like that.

Wonder id they could have saved a whole disk!

Cheers,
Nick.
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #284089 is a reply to message #283714] Sun, 15 February 2015 08:31 Go to previous messageGo to next message
TommyGoog is currently offline  TommyGoog
Messages: 112
Registered: January 2013
Karma: 0
Senior Member
In this post I dissect SYSTEM.RELOC and display the information in a more readable format. The table below is beneficial if anyone else is trying to replicate my efforts.

SYSTEM.RELOC contains the "virtual disk" translation information. The first column contains a range of virtual disk addresses (Pascal block numbering) and the next 5 columns contain the ranges on the physical disks corresponding to that virtual disk range.

For example, the file SYSTEM.PASCAL (main Wizardry IV code) indicates the segment COMBAT starts at virtual address $10 relative to the start of SYSTEM..PASCAL (which starts at $46). So the virtual address for the COMBAT segment is $46 + $10 = $56 = 86 (decimal). Finding 86 in the left column shows that COMBAT does not exist on DISK1, but does exist on the other 4 disks, and 86 corresponds to block 2 on those real disks. So the segment COMBAT can be loaded from any of those 4 disks starting from block 2.

Note there are a few "gaps" in the virtual disk addresses where there is no physical address for the virtual address (245, 250-251, 270-271, 279-280).

I have uploaded this table to my GOOGLE spreadsheet in a slightly different format.

VIRTUAL DISK1 DISK2 DISK3 DISK4 DISK5
-------- -------- -------- -------- -------- --------
0....1 0....1 0....1 0....1 0....1 0....1
2...85 2...85
86..132 2...48 2...48 2...48 2...48
133..141 86...94
142..152 95..105 49...59 49...59 49...59 49...59
---------------------------------------------------------
153..158 106..111
159..181 60...82 60...82 60...82 60...82
182..200 112..130 83..101 83..101 83..101 83..101
201..208 131..138
209..224 102..117 102..117 102..117 102..117
---------------------------------------------------------
225..227 139..141 118..120 118..120 118..120 118..120
228..229 142..143 121..122 121..122 121..122
230..230 144..144 123..123 123..123 123..123 121..121
231..232 145..146 124..125 124..125 124..125
233..233 147..147 126..126 126..126 126..126 122..122
---------------------------------------------------------
234..234 148..148 127..127 127..127 127..127
235..235 149..149 128..128 128..128
236..236 150..150 129..129 128..128
237..237 151..151 129..129 130..130 129..129 123..123
238..238 152..152 130..130
---------------------------------------------------------
239..239 153..153 131..131 130..130
240..240 154..154 132..132 124..124
241..241 133..133 131..131
242..243 134..135 131..132
244..244 136..136
---------------------------------------------------------
245..245
246..249 155..158
250..251
252..253 125..126
254..265 132..143
---------------------------------------------------------
266..269 127..130
270..271
272..275 144..147
276..276 148..148 131..131
277..277 133..133 132..132
---------------------------------------------------------
278..278 149..149
279..280
281..282 137..138
283..285
286..292 139..145 134..140 150..156 133..139
---------------------------------------------------------
293..299 159..165 146..152 141..147 157..163 140..146
300..300 153..153
301..313 166..178 154..166 148..160 164..176 147..159
314..314 179..179
315..315 180..180 160..160
---------------------------------------------------------
316..339 181..204 167..190 161..184 177..200 161..184
340..352 191..203
353..356 205..208 204..207 185..188 201..204 185..188
357..357 189..189 205..205
358..360 206..208
---------------------------------------------------------
361..361 190..190 209..209
362..362 210..210 189..189
363..367 211..215
368..368 208..208 191..191 216..216
369..369 209..209 217..217
---------------------------------------------------------
370..370 210..210 192..192 218..218
371..371 209..209 211..211 193..193 219..219 190..190
372..380 191..199
381..381 194..194 200..200
382..382 195..195
---------------------------------------------------------
383..383 196..196 201..201
384..385 202..203
386..387 197..198 204..205
388..403 206..221
404..406 210..212 212..214 199..201 220..222 222..224
---------------------------------------------------------
407..407 215..215 202..202
408..408 213..213 216..216 203..203 223..223 225..225
409..414 214..219
415..415 217..217 224..224 226..226
416..416 218..218 204..204
---------------------------------------------------------
417..417 205..205
418..418 219..219 206..206 225..225 227..227
419..419 220..220 226..226 228..228
420..420 221..221
421..421 222..222 227..227 229..229
---------------------------------------------------------
422..437 223..238
438..438 239..239 207..207
439..460 208..229
461..461 240..240 230..230 228..228 230..230
462..466 241..245
---------------------------------------------------------
467..467 246..246 231..231
468..481 232..245
482..482 247..247 246..246
483..487 248..252
488..488 253..253 247..247
---------------------------------------------------------
489..489 248..248
490..490 249..249 229..229
491..504 230..243
505..507 244..246 231..233
508..509 234..235
---------------------------------------------------------
510..511 250..251 236..237
512..513 247..248
514..523 252..261
524..533 254..263
534..535 238..239
---------------------------------------------------------
536..537 249..250
538..539 240..241
540..541 262..263
542..549 264..271 264..271 251..258 242..249
550..555 220..225 272..277 272..277 259..264 250..255
---------------------------------------------------------
Tommy
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #284270 is a reply to message #284089] Wed, 18 February 2015 16:25 Go to previous messageGo to next message
TommyGoog is currently offline  TommyGoog
Messages: 112
Registered: January 2013
Karma: 0
Senior Member
Gross Bug Alert!

Warning: portions of this post might not be suitable for young children or Computer Science 101 students.

There is a gross bug in WizardryIV. It is not just a "random store" but a random store to 180 byte locations while the proper 180 bytes are not updated.

Some background
===============

Shortly after booting, the "main" window is defined and a "secondary" window is defined. The main window displays the title "WizardryIV" and the author credits. The secondary window displays the solicit "S)tart Game". Also displayed in the main window above the secondary window are pictures of enemies that are randomly displayed (both in number and composition).

A window in Wizardry is defined by 2 parts: 1. Header; 2. Data. The header is 10 bytes long and consists of information about the window (size, location on screen, priority, etc.).

The Data portion is variable in size depending on the height and width of the window, and contains the representation for the content that is displayed on the screen: ASCII characters and/or "picture" data.

The main window displays 36 columns x 20 rows of information on the screen (not including the "frame"). The Data portion therefore contains 36 x 20 x 2 bytes. Each screen location is represented by an INTEGER value, which is then mapped to the 8 bytes on the Hi-res screen in a box that is 7 dots wide and 8 dots high.


The code:
=========

The call that begins the sequence is in KANJIREA segment:

UNITWRITE( BASE12, KANMP02, 17, MAINWIN.I);

The BASE12 variable has the value 13, so this is one of the "special" UNITWRITEs to execute Wizardry 6502 code.

The value of KANMP02 is not used. The 17 is the "function". MAINWIN.I is the address to the main window buffer.

The purpose for this function 17 is to set the high order byte for each INTEGER value in MAINWIN for the rows that display the enemy pictures (row 10 to 14, counting from 1) of the MAINWIN visible data. This is row 11 to 15 on the Hi-res screen counting from the top row of 0. When the high order byte is non zero, special "non-ASCII" picture processing occurs.

So far, so good.

There is some preliminary processing to retrieve the parameters from the calling stack, determine the function code to execute and eventually we reach $F7B6 with the last parameter (MAINWIN.I) stored at $34.35.

F7B6- 20 A8 F7 JSR $F7A8
F7B9- E6 0F INC $0F
F7BB- A9 01 LDA #$01
F7BD- A2 01 LDX #$01
F7BF- A0 66 LDY #$66
F7C1- 91 0E STA ($0E),Y
F7C3- 88 DEY
F7C4- 88 DEY
F7C5- D0 FA BNE $F7C1
F7C7- 91 0E STA ($0E),Y
F7C9- C6 0F DEC $0F
F7CB- CA DEX
F7CC- 10 F3 BPL $F7C1
F7CE- 60 RTS


F7A8- 18 CLC Add #$293 to address at $78.79
F7A9- A9 93 LDA #$93 Store result at $0E.0F
F7AB- 65 78 ADC $78
F7AD- 85 0E STA $0E
F7AF- A9 02 LDA #$02
F7B1- 65 79 ADC $79
F7B3- 85 0F STA $0F
F7B5- 60 RTS

That value of #$293 had me scratching my head for awhile. It is the offset from the beginning of the main window to row 10 of the data area: 10 byte header + 9 (rows) * 36 * 2 = 658. $293 = 659. Since the bytes are stored low byte first, this is the offset to the high order byte for the first "char" on row 10 of the main window address in $78.79.


The Problem:
============

At this point for this call, $78.79 does NOT contain the window address! $78.79 was initialized early in the boot process to $0000! So the storing of 180 bytes of #$01 is relative to address $0000! Therefore addresses at $293 to $3F9 inclusive (every other byte) are clobbered.


Consequence
===========
What is the consequence? Nothing!

The area of memory at $293 to $3F9 is used as an I/O buffer, but otherwise does not contain useful information at this point in the code.

What about the bytes that were not updated in the main window?

The story gets a bit more interesting, because shortly after the function 17 UNITWRITE call is the following function 13 call:

UNITWRITE( BASE12, KANMP02, 13, MAINWIN.I);

And this function 13 takes us to this code:

F7CF- A5 34 LDA $34 $78.79 := MAINWIN
F7D1- 85 78 STA $78
F7D3- A5 35 LDA $35
F7D5- 85 79 STA $79
F7D7- 20 B6 F7 JSR $F7B6
(and a lot more code)

So this function 13 call does actually set the bytes to #$01 in the main window data area.

Hope you enjoyed this!

Tommy
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #284350 is a reply to message #284270] Thu, 19 February 2015 19:54 Go to previous messageGo to next message
sicklittlemonkey is currently offline  sicklittlemonkey
Messages: 570
Registered: October 2012
Karma: 0
Senior Member
On Thursday, 19 February 2015 10:25:54 UTC+13, TommyGoog wrote:

Interesting as always ...

> The area of memory at $293 to $3F9 is used as an I/O buffer, but otherwise does not contain useful information at this point in the code.

It will trample the reset vector at $3F2-$3F4 though.

Cheers,
Nick.
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #284438 is a reply to message #284270] Fri, 20 February 2015 22:47 Go to previous messageGo to next message
TommyGoog is currently offline  TommyGoog
Messages: 112
Registered: January 2013
Karma: 0
Senior Member
I've updated my google spreadsheet with an apple screenshot showing the 24 monster pics from 200.MONSTERS.

https://docs.google.com/spreadsheets/d/1wyI3dCHVUeowkjVgLPjR rAb8CVHYgKxMwT1Kv5sZ0CU/edit#gid=454639809

In the first part of 200.MONSTERS are 24 bit mapped pics of the monsters.

MONSTERS: PACKED ARRAY[ 0..23] OF
PACKED RECORD
PICS: PACKED ARRAY[ 0..4] OF PACKED ARRAY[ 0..5} OF
PACKED ARRAY[ 0..7] OF 0..255;
PAD : PACKED ARRAY[ 0..15] OF 0..255;
END;

24 monsters
5 rows per monster
6 columns per monster
8 bytes per hi-res "box"
Each byte has value 0 to 255.

Each monster occupies 30 hi-res "boxes". 6 across and 5 down. Each box has 8 values representing 8 x 7 dots on the screen.

Tommy
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #285098 is a reply to message #284438] Sat, 28 February 2015 14:51 Go to previous messageGo to next message
TommyGoog is currently offline  TommyGoog
Messages: 112
Registered: January 2013
Karma: 0
Senior Member
There is a small bug in the "M)ake Scenario" code in WizardryIV. Sometimes the copy using 2 disk drives fails.

When you start the game an option is to "M)ake Scenario" where you are advised that to play the game you must make copies of each of your Master Diskettes. If you have only 1 disk drive, or answer the question that you have only 1 disk drive, then everything works normally, but if you have 2 disk drives "it depends".

If you start WizardryIV with the boot disk in S6D1 and another disk in S6D2, then everything works normally too.

If you start WizardryIV with the boot disk in S6D1 and no disk in S6D2 the bug shows itself. You answer "M" to "M)ake Scenario" and then answer "2" for "2) Disk Drives". Then you place a Master Diskette in Drive 1 and press ENTER. You are then told to "Insert blank Diskette into Drive 2, then press ENTER". You do so and quickly see "Formatting Disk" (but no disk I/O seems to occur) and this is followed by the "Make Scenario Diskette, 0 of 3 cycles completed" followed by "Read Error! "Make has failed".

During the boot process, the Pascal runtime for WizardryIV checks the slots in the AppleII to see if any have DISK II controllers. If a slot has one, it then checks to see if disk drives are attached. It does this by starting the disk(s) and watching the "data latch" change. If there is no disk in the drive, it cannot determine if there really is a drive there or not. When it finds a diskette in the drive, it then updates some internal tables (at $FC63,X and FC71,X) with Slot# * 16 and Drive # (0 = Drive 1; 1 = Drive 2). These tables are indexed by the Pascal "Device number", where the following assignments are usually made:

Device Slot/Drive
4.......S6 D1
5.......S6 D2
9.......S5 D1
10......S5 D2
11......S4 D1
12......S4 D2

During the WizardryIV boot, if no diskette is in the drive, then #$FF is stored in the tables at $FC63,X and $FC71,X.

The bug in "M)ake Scenario" occurs because the code blindly picks up the values for Device 5 (which are still #$FF) and tries to use them on instructions such as " LDA $C08E,X" where X is now #$FF.

Tommy
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #285681 is a reply to message #285098] Thu, 12 March 2015 22:21 Go to previous messageGo to next message
TommyGoog is currently offline  TommyGoog
Messages: 112
Registered: January 2013
Karma: 0
Senior Member
Auxiliary Memory in Wizardry IV for Disk Caching

Wizardry IV supports multiple 64K memory banks in the auxilary slot. Think RAMWorks.

I've spent a fair amount of time in the past 3-4 weeks understanding how Wizardry IV uses the Apple IIe Auxiliary memory for disk caching. I thought I'd share my findings with you since this was a very arduous task and I think the code is "neat". (Neat as in interesting, not necessarily as in tidy or clean.)

References:

Apple IIe Technical Reference Manual
Apple IIe Extended 80-Column Text Card (Rev B).pdf
Apple IIe Extended 80-Column Text Card Suplement.pdf
Apple II Miscellaneious Techinical Note #2.htm (identification routines)

In order to make this post shorter(ha! ha!), I've put all my "work-in-progress" coding efforts into my "Spreadsheet".

https://docs.google.com/spreadsheets/d/1wyI3dCHVUeowkjVgLPjR rAb8CVHYgKxMwT1Kv5sZ0CU/edit?usp=sharing

These are the sheet names:

Boot
Interp
InterpBC00
WizOutSoFar

From time-to-time I will likely update these spreadsheets.

So far I have completed the BOOTstrap code, and a lot of the INTERPretter code. My Pascal de-compiling has completed the first rough draft of Segment DOCOPY, about half of KANJIREA, and about half of the main segment. So I've completed about 10% of the Pascal code.

The code is heavily documented, so this post is only to point you in the right direction to examine the Auxiliary memory code.

And if you think this code is difficult to understand (it is!), it was a lot more difficult before I put in comments and before I knew what it was really trying to do!

So far I have only re-engineerred the first part of Wizardry IV so my information now might not be the final word on how everything really works.

First up, "What can be in the Auxiliary Slot, and how does WizIV identify it?"

Near the end of the BOOTstrap code, and before the INTERPreter code is executed, Wizardry IV identifies the type of computer (Apple II, Apple II+, Apple IIe, etc.). See $08CD in the Boot spreadsheet. If it identifies the computer as Apple IIe, it then identifies the memory in the Auxiliary slot (if any exists).

The code at $08CD determines the type of Apple.

At $09FA is a JSR $0AC4 to do some "AUX Memory Bank stuff".

At $0AC4 some code is moved from $A95 to zero page at $00 to $2E and then executes JSR $0000. The code needs to be in z-page during the testing which swaps (or attempts to swap) memory banks.

I was a bit confused for awhile about this code that stores into $0800 and then checks $0C00 to see if it was changed. If you have just the 1K Ram card in the Auxiliary slot, then there is something called Sparse Memory Mapping, where $0800 and $0C00 are the same address.

The routine returns with the Carry clear to indicate Auxiliary memory exists (not just the 1K memory).

At $0ADD Auxiliary memory banks are identified.

The code from $0ADD to $0AED stores the bank number in each of up to 128 64K Banks. For AppleWin, there is only 1 Bank of 64K bytes, but RamWorks cards will have multiple 64K Banks -- Each able to address $0000..$BFFF, $D000...DFFF (bank 1), $D000..DFFF(bank 2) and $E000..$FFFF.

I will refer to the 64K banks in Aux memory with "Bank #" and the number. For example "Bank #0" is the first Auxiliary bank in AppleWin. I will use "bank 1" and "bank 2" to identify the 2 parts of a auxiliary bank or main RAM memory bank. For example, "Bank #0 has a bank 1 and a bank 2 Section, different from the main RAM bank 1 and bank 2". And I will refer to the 3 parts of an Auxiliary Bank as Sections ($0200..0BFFF, $D000..DFFF, $D000..FFFF).

At $0AF3 to $0AFF the code checks to see if the store was to a valid bank. If a valid bank is found, then 2 tables are updated. At $0200 the Bank # is written to 3 consecutive bytes. At $0300 the values "03 04 01" are written. In addition, code is transferred from $0A1F..$0AC3 to $0040..$00E4 in the valid bank -- each 64K bank gets a copy. We will run into this code at $0040 later. Wizardry IV stops checking banks if it finds 64 valid banks.

The tables at $0200 and $0300 are transferred to $FC24 and $FBE4 respectively. For AppleWin they are the following:

$FC24: 00 00 00 00
$FBE4: 03 04 01 05

$FC is updated with 3 * number of found banks.

If more banks had been found, then $FBE4 would look like: 03 04 01 03 04 01 03 04 01 ... 03 04 01 05.

At this point the bootstrap completes and execution soon begins at $D000 for SYSTEM.INTERP.

Ok, that was the easy part.

In the WizIV Pascal mainline, BCACHE is a global boolean variable set to TRUE when HAS.CACHE is found on the boot disk.

Early in WizIV (procedure SOLICSM) you can "M)ake Scenario" disk copies. This causes DOCOPY() to be called from the mainline code. DOCOPY() takes advantage of Aux disk caching.

Procedure P010C0A() does a lot of the preliminary work. It turns "off" the "virtual disk I/O" before making the copy.

WizIV uses 3 separate parts of memory to do the copy:

1. Aux memory
2. A large chunk of the Pascal stack
3. A couple of previously acquired buffers (called the disk cache buffers,
each 1 block)

In P010C0A() a call to CACHEMAGic (perhaps a better name is CACHEINI) uses UNITWRITE() to initialize the following:

$EE := 0;
$E8 := 1;
$E9 := 0;
$EA := 0; $EB := 0;

The first time I saw that I said to myself, "what the...". Oh wait, is this a child-friendly forum?

When $EE is negative, then a disk read request results in Aux cache being checked first for the data.

When $EE is 0 or positive, no such check occurs.

$E8 set to 1 is reserving a Section in the first Aux Bank (Bank #0) at $D000..$DFFF bank 1 for use as an internal table keeping track of the disk blocks in the Aux Bank(s). This value of 1 is related to the order of the values "03 04 01" in $FBE4.

Those values (03 04 01) represent 3 "Sections" for a 64K Aux Bank. The values are used as indexes into 2 tables:

0 $E850 0F $E856 02
1 $E851 5F $E857 02
2 $E852 40 $E858 40
3 $E853 08 $E859 D0
4 $E854 18 $E85A D0
5 $E855 00 $E85B 00

The table at $E850 is the size (Blocks) of each Section.
The table at $E856 is the starting address (High byte) of each Section. Therefore:

03 is the D000..DFFF (bank 1) Section in an Aux Bank.
04 is the D000..DFFF (bank 2) + E000..FFFF Section in an Aux Bank.
01 is the 0200..BFFF Section in an Aux Bank.

$E9 is the number of cache records in the reserved D000..DFFF Section of Bank #0.

A cache record in $D000 is 4 words (8 bytes):

FirstBlock
SectionOffset
AuxIndex
LastBlock(+1)

FirstBlock is the first disk block represented by this cache record.

LastBlock(+1) is 1 greater than the last block represented by this record.

So those 2 combined define the range of disk blocks defined by this cache record.

AuxIndex is an index (0, 1, 2, 3, 4, 5, 6, ...) into the $FC24 and $FBE4 tables.
This identifies the Auxiliary Bank# and it's Section containing the data for the first block represented by this cache record.

SectionOffset is the block offset into the Section identified by AuxIndex where the real data begins for the first block represented by this cache record.

$EA.EB is the number of blocks written to an Aux Section (?) or Aux Bank (?)

Whenever you see "UNITWRITE( BASE12,...)", BASE12 was initialized to 13, so this is the "virtual device" interface to the INTERP Pascal code.

In P010C0A(), "UNITWRITE( BASE12, UWINOUT, 25, 0)" returns the total amount of Aux memory in all the Banks (with the exception of the reserved cache records Section at $D000..DFFF bank 1 in Aux Bank #0). This is stored at AUXBUFN.

TOTCACHE is then calculated as the total of all 3 cache areas (Aux mem, Pascal stack, 2 cache buffers).

P010C0A() returns and next is "UNITWRITE( BASE12, MEMBUFF^, 20, DCTEMP)". This initializes the disk in drive 2 (drive 1 if you only have 1 disk drive).

UNITREAD( BASE12, DSKBLCKS, 21, 0) returns the number of blocks per physical disk. This is the value 280 which is hard coded in the WizIV INTERP code..

WizIV starts with block #6 and reads as much of the Master disk into the 3 cache areas as it can. It then writes that to the newly initialized disk and repeats until the last block on the disk is written. It then does the blocks from 0 to 5 inclusive.

It's not clear to me why they delay writing blocks 0 to 5 this way.

DO1CYCLE() is called with the first disk block to read and the total size of the 3 cache areas. DO1CYCLE calls READMAST().

In READMAST(), CACHEMAG() is called again and then "UNITWRITE( BASE12, UWINOUT, 25, 0)" to fill up the Aux Cache while reading from the Master disk.

If you want to trace all the UNITWRITE() code, it starts in INTERP at $DC7B for pCode = $9E (Call Standard Procedure) and soon branches to $DEB0, then to $ED10, and for function 25 to $F084 to process "Cache stuff".

$F084 processes 5 variations for UNITWRITE().

UNITWRITE( 13, 0, 25, 0)
UNITWRITE( 13, -1, 25, 0)
UNITWRITE( 13, -2, 25, 0)
UNITWRITE( 13, -3, 25, 0)
UNITWRITE( 13, ++, 25, 0) Do I/O to Aux Cache

Parameter 2 = 0: return size of Aux memory
(taking into account that $E8 has reserved a Section).
Parameter 2 = -1: Set $EE to 0 (turn off looking in Cache on disk read requests)
Parameter 2 = -2: Set $EE to -1 (turn on looking in Cache on disk read requests)
Parameter 2 = -3; Init $E9, $EA, $EB to 0; Init $E8 to 1
Parameter 2 = ++; (2 positive words);
First word = Disk Block Request; Second word = Size of I/O requested.

Since READMAST() passed 2 positive values in Parameter 2, JMP $E85C, and then JMP $E9F3.

The code in $E9F3 enters a loop to process as much I/O as possible for each Aux Section. It calls EAD8 to set up the Aux cache records and then calls EA8F to read from the physical disk to Cache.

The general structure of $EAD8 is:

The code searches linearly through the Aux cache records until the Requested Block is < FirstBlock in a cache entry. It is searching for the place to "insert" the current Requested Block such that the FirstBlocks are in no worse than ascending order. At that point it jumps out of the loop and into the "middle" of ANOTHER LOOP.

OUTER LOOP:
JSR E988 Read 1 block of Aux cache record from $D000 to $0200.
Loop:
.....If all cache records searched JMP to "middle of next loop"
.....If Requested block < Aux record FirstBlock JMP to "middle of next loop"
end of Loop
end of OUTER LOOP

BEGIN ANOTHER LOOP
JSR E988 Read Aux cache record to $D000
Middle:
.....JSR $D024 Block Transfer
.....JSR $E985 Write Aux $D000
End ANOTHER LOOP
INC $E9
RTS

The "Block Transfer" in ANOTHER LOOP is transferring the cache records from the "insertion point" down one record position in order to open up a slot to store the current Requested Block information. This is complicated a bit because only 1 block of cache records are kept in memory (at $0200) at a time. Therefore it sometimes needs to save the last entry of a cache record block and then insert that entry as the first entry in the next cache record block (and of course move all those records down 1 slot). This can possibly repeat for additional cache record blocks.

There are 4 entry points to acess the Aux Banks:

$E985 Write Aux $D000 cache records
$E988 Read Aux $D000 cache records
$E994 Read (or Write) from (to) Aux Section X + Offset ($34)
$E9A0 Read (or Write) from (to) Aux Section X + Offset ($34)

The 4 entry points converge and code is moved from $E9DB..EDF2 to $0CB0. It does this so it can switch to Aux Bank #A using page 0, page 1 and $D000...$FFFF.

The code moved to $0CB0 switches to Aux Bank #A, page 0, page 1, $D000..$FFFF and JSR to $0040. Remember that code that was transferred during the Boot process? There it is!

The code in $0040 determines which "Section" of Aux Bank needs to be Read (or Written) and switches to bank 1 or bank 2 as needed. This code then transfers data from (to) the Aux Bank #A.

Eventually the code returns from the "UNITWRITE( BASE12, UWINOUT, 25, 0)" after filling up the Aux cache. The READMAST() code then checks to see if more cache needs to be filled and then fills the Pascal stack space, and if needed the 2 Disk Cache buffers before returning to DO1CYCLE().

DO1CYCLE() then calls WRITBLNK() with the 3 cache areas in order to write the cache to the initialized disk.

WRITBLNK() does "UNITWRITE( BASE12, UWINOUT, 25, 0)" with UWINOUT set to -2 setting $EE negative.

With $EE negative, the following "UNITREAD( 4, BASE55B, 512, MP02 + MP03)" with the device = 4 (Boot device, usually S6D1) will now look at Aux Cache first before going to the physical disk.

The code to pull from Aux Cache starts near $E85C, and really starts at $E87A.

If you think the code for putting blocks into cache was confusing, wait until you see this code!

Think of some of the possible cases that need to be covered:

An I/O request might span several Aux Banks (say Aux Bank #3 and Aux Bank #4).

An I/O request might span several Sections in one Aux Bank
(say $0200..BFFF, and $D000..FFFF).

An I/O request might span multiple cache record blocks in the reserved
$D000..$DFFF in Bank #0.

An I/O request might start with the first block in a cache record and
end with the last block in the cache record.

An I/O request might start with the first block in a cache record and
end before the last block in the cache record.

An I/O request might start in the middle of a cache record and end
at the last block of the record.

An I/O request might start in the middle of a cache record" and end
before the last block in the cache record.

An I/O request might start in one cache record and continue to the
next cache record.

An I/O request might not find the first block any where in the Aux cache.

An I/O request might find the first part of the request in Aux cache,
but the rest is not in Aux cache.

An I/O request might not find the first part ot the request in Aux cache,
but part or all the rest is in Aux cache.

Here is the general process used:

Take the Requested Disk Block address and look for it in the cache records.

Part A)

If all the cache records are searched and its not found, check to see if any of the rest of the I/O is in the cache records.

If none of it is in cache, then a "normal" disk I/O is performed for the entire request.

If some of the rest is in cache, then split this request into 2 parts (for now). The first one will read from the physical disk up to the point where the cache has the next block.

This can happen for example if there is a "gap" in the disk blocks found in the cache.

For example, record 1 shows range 5 to 15, and record 2 shows range 25 to 28, and our I/O request is for block 23 to 27. Read blocks 23 and 24 from the physical disk, but retrieve 25-27 from cache.

Near the bottom of this section of code (near $E966) if additional I/O is required it starts over near the top (at $E8A6).

Part B)

If the Requested Disk Block is in a cache record then determine if all the I/O is described by this cache record. If not, then it is split into 2 separate I/O requests similar to above. After the first blocks are retrieved from cache, the rest will be treated as a second I/O near the top (at $E8A6).

One thing that complicates this a bit is that only 1 block of the cache records (from $D000..DFFF in Aux Bank #0) is read into $0200 (main Ram memory) at a time. So if there are more than 64 cache records, sometimes another cache I/O is required to get the next set of cache records.

For awhile I thought there might be a bug in the code from $E91A to $E92A. There are 3 general paths to get to this code:

1. Find Requested Disk Block in a cache record (starting at $E905)
2. No find after searching all cache records.
$E8CC --> $E8D7 --> E8E6 --> E91A
3. Requested Disk Block is in a "gap" between cache records.
$E8F3 --> $E903 --> $E8DB --> $E8E6 --> $E91A

If you want a real challenge, look at this code and see if you can find any problems. You might spend hours on this!

One crucial assumption that the code makes is that a cache record will not descibe more than $5F disk blocks (largest contiguous ram memory).

There are really 5 cases to cover:

1. The Requested Block and the I/O Length Request are contained
in 1 cache record

2. The Requested Block is in a cache record, but the Length runs past
the end of the cache record

3. The Requested Block is greater than the last block described by
the cache records.

4. The Requested Block is in a "gap" and none of the I/O is described by
the next cache record

5. The Requested Block is in a "gap" and at least some of the I/O is
described by the next cache record

I've spent literally hours on the 11 "6502" instructions starting at $E91A to try to find a bug. None have been found.

This is some of the trickiest 11 instructions and conditions you might ever find.

If you have questions, I'd love to hear them! If you have any comments, I'd love to hear them! If you've read this far, I'd love to hear from you!

Ok, now where is my Pascal re-engineering pen? Enough of this 6502 coding.

Tommy
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #285701 is a reply to message #285681] Fri, 13 March 2015 11:45 Go to previous messageGo to next message
qkumba is currently offline  qkumba
Messages: 1584
Registered: March 2013
Karma: 0
Senior Member
> It's not clear to me why they delay writing blocks 0 to 5 this way.

Perhaps they wanted to ensure that all data were saved before copying the directory?
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #285757 is a reply to message #285701] Sat, 14 March 2015 10:24 Go to previous messageGo to next message
TommyGoog is currently offline  TommyGoog
Messages: 112
Registered: January 2013
Karma: 0
Senior Member
On Friday, March 13, 2015 at 10:45:57 AM UTC-5, qkumba wrote:
>> It's not clear to me why they delay writing blocks 0 to 5 this way.
>
> Perhaps they wanted to ensure that all data were saved before copying the directory?

That explanation would make some sense if disks 2-5 had directories (they don't), or if the directory on disk 1 encompassed only disk 1 (it doesn't).

In some respects the entire set of 5 disks is treated as one big extended disk with one directory on disk 1.
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #285791 is a reply to message #285757] Sat, 14 March 2015 19:46 Go to previous messageGo to next message
gbody4 is currently offline  gbody4
Messages: 47
Registered: October 2012
Karma: 0
Member
Tommy,
perhaps it only copies the data that identifies the disk as being wizardry if the data copy completes with no errors. This way the disk would not be identified as wizardry if the disk was bad.

GeoffB
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #286985 is a reply to message #285681] Tue, 31 March 2015 15:30 Go to previous messageGo to next message
TommyGoog is currently offline  TommyGoog
Messages: 112
Registered: January 2013
Karma: 0
Senior Member
"Tickets...Tickets Please!"
Mordorcharge Card "copy protection".
"Messages" 28000 to 28060.

When you enter a new level of the maze (after the first one) you have this dialog to answer:

"Tickets...Tickets, Please!
Enter the Validation Code of this
Mordorcharge Card to continue:
2919 3014 1529

Validation Code >

I have discovered 2 ways to disable the "copy protection" in Wizardry IV:

1. To allow any 4 digit number to "pass the validation":
Disk 1 ("A") T$0C S$0C A$03, change $04 to $00.

2. To remove the "Tickets" dialog completely:
Disk 1 ("A") T$0C S$0C A$91, change $CE to $D7
Disk 1 ("A") T$0C S$0C A$92, change $0A to $D7

In the game box was a booklet with 8000 combinations of numbers that looked like credit card numbers (XXXX-XXXX-XXXX) and there was a Validation Code for each one. The document itself was printed with red paper and black ink in a style making a photocopy impossible to read. After entering the proper Validation Code, the game continued.

The 12 digit number is randomly constructed from 3 groups of 4 digit numbers. Each group has 20 different 4 digit numbers. This means there are a total of 8000 credit card combinations (20 x 20 x 20).

In this post I will describe the Wizardry IV processing and how the numbers compare with Bob Colbert's list of codes and his process to produce the Validation Code (see Computist issue 50, 1988 January).

ftp://ftp.apple.asimov.net/pub/apple_II/documentation/magazi nes/hardcore_computist/issue51.pdf

The Wizardry IV UTILITY segment contains the Modorcharge Card protection scheme code. The Wizardry IV copy protection code begins with procedure TICKETS (P01070A).

Message 28000 is retrieved from ASCII.KRN (recall the earlier post about Huffman compression), but message 28000 is really not a message at all. The length field is ignored and the next 2 bytes that make up message 28000 are converted to an integer value (60), and that value represents the number of messages used for the MordorCharge Card Validation Code calculations.

In fact, none of the messages from 28001 to 28060 are displayed messages. Those messages each represent the value of 2 integers and after a message is retrieved from the file it is converted into 2 integer values.

The first integer for each message represents the value of the 4 digit number that is displayed. The second integer for each message is used in the calculation of the Validation Code. I will refer to the first integer (the displayed number) as the "Left" number and the second integer (the calculation number) as the "Right" number.

Each of the messages from 28001 to 280060 is 5 bytes long (including the length byte). The 4 bytes for each message are the 2 integer values in low-byte-first order. Here is a table describing the first few messages from 28000 to 28060. I have used decimal values to indicate the byte values for these STRING messages.

28000 2 60 0
28001 4 56 4 187 8
28002 4 62 4 13 7
28003 4 169 4 39 2
28004 4 187 4 42 2
28005 4 195 4 77 2
28006 4 2 5 61 0

TICKETS() generates 3 random numbers and MODs them by (60 DIV 3) producing numbers in the range [0..19]. Each number is then multiplied by 3 and then +1 is added to the second, and +2 is added to the third. Then 28001 is added to each.

Therefore each number is one of 20 messages from 28001 to 28060.
The first number (MSG1) is a message from the set: 28001, 28004, 28007, .....
The second number (MSG2) is a message from the set: 28002, 28005, 28008, .....
The third number (MSG3) is a message from the set: 28003, 28006, 28009,...

The 3 random MSG numbers are used to retrieve 3 pairs of 2 integers (total of 6 integers). I will refer to these as MSG1L, MSG1R, MSG2L, MSG2R, MSG3L, MSG3R. One way to envision this is a list of 3 columns and each column having a Left and a Right side. Each column contains 20 integers.

The Validation Code calculation in Wizardry IV is:

1000 + [(11 * MSG1R) MOD 10000 + (7 * MSG2R) MOD 10000 + MSG3R] MOD 9000.

This calculation and the starting values for the 3 Right-hand numbers confused me when compared with Bob Colvert's numbers. Here are the tables:

Wizardry IV table (from messages 28001 to 28060):

....MSG1........MSG2........MSG3
....L-R.........L-R.........L-R

1080-2235...1086-1805...1193-551
1211-554....1219-589....1282-61
1386-1122...1516-207....1529-1026
1556-1264...1588-1862...1602-2002
1607-449....1669-1995...1712-563
1735-410....1753-1477...1757-107
2138-825....2194-427....2219-0
2293-1440...2301-1600...2313-741
2338-199....2362-828....2377-447
2395-197....2437-1255...2451-532
2470-1634...2480-704....2770-417
2788-1578...2800-2057...2812-1422
2892-1811...2897-1080...2910-1866
2919-1629...2922-2302...2941-1741
2990-2356...3014-1094...3032-390
3137-729....3243-1700...3278-1383
3303-231....3369-1617...3414-1233
3463-920....3538-1592...3547-1616
3587-1206...3779-1272...3816-1925
3852-1887...3868-855....3996-850


Bob Colvert's table:

....MSG1........MSG2........MSG3
....L-R.........L-R.........L-R

1080-8771...1086-0......1193-0
1211-1280...1219-1488...1282-8510
1386-6528...1516-7814...1529-9475
1556-8090...1588-9399...1602-1451
1607-9125...1669-1330...1712-9012
1735-8696...1753-6704...1757-8556
2138-4261...2194-9354...2219-8449
2293-1026...2301-7565...2313-9190
2338-6375...2362-3161...2377-8896
2395-6353...2437-6150...2451-8981
2470-3160...2480-2293...2770-8866
2788-2544...2800-1764...2812-9871
2892-5107...2897-4925...2910-1315
2919-3105...2922-3479...2941-1190
2990-1102...3014-5023...3032-8839
3137-3205...3243-8265...3278-9832
3303-6727...3369-7684...3414-9682
3463-4306...3538-7509...3547-1065
3587-7452...3779-6269...3816-1374
3852-4943...3868-3350...3996-9299

Also interesting is Bob's method to calculate the Validation Code:
1. Use the Left hand numbers from each message (MSG1, MSG2, MSG3) to look up the corresponding Right hand numbers.
2. Sum those 3 numbers.
3. If the sum is greater than 9999, subtract 9000 (or 18000, or 27000) from the number to make it less than 10000.

I have validated that each of the 8000 combination of "MordorCard Credit" numbers can be calculated to the same values using either method!

Ok, now go back and look at the numbers and look at the process used to calculate the Validation Codes. Do you see why they both work yet seem to be so different?

Well, I spent a lot of time on that question (more than I should have) and now know why they are mathematically identical.

Anyone interested in an explanation?

I've updated my spreadsheets at google with the latest re-engineered Wizardry IV code to date.

So far I've completed segments: KANJIREA, DOCOPY, DOCACHE, and SHOPS. I've also completed about half of the mainline WIZARDRY segment and part of the UTILITIE segment. So I've completed approximately 128 of the 592 Pascal procedures and functions.

I'm guessing that by the time I'm done I won't be able to compile the entire Pascal program using AppleWin even with my modified version containing 4 disk drives. It won't all fit on 4 diskettes!

Well, back to work....

Tommy
Re: Wizardry IV bootstrap bug in SYSTEM.INTERP [message #287317 is a reply to message #286985] Mon, 06 April 2015 17:15 Go to previous message
TommyGoog is currently offline  TommyGoog
Messages: 112
Registered: January 2013
Karma: 0
Senior Member
Fun with Pascal! Can you spot the bug? Plus the Boolean expression from Hell!

What's wrong with this function?

FUNCTION GETADDR( VAR A:STRING) : INTEGER;

VAR
MP04 : ARRAY[ 0..0] OF INTEGER;

BEGIN
GETADDR := MP04[ -1]
END;

I will give a hint near the end of this post, but first I will show you the "Boolean expression from Hell!"

6E51 00 SLDC. PUSH #0000
6E52 00 SLDC. PUSH #0000
6E53 CD 01 20 CXP. CALL EXTERNAL PROCEDURE: 20 IN SEGMENT: 01
6E56 63 SLDC. PUSH #0063
6E57 8E MODI. PUSH ((TOS-1) MOD (TOS))
6E58 23 SLDC. PUSH #0023
6E59 C3 EQUI. PUSH ((TOS-1) = (TOS))
6E5A 00 SLDC. PUSH #0000
6E5B 00 SLDC. PUSH #0000
6E5C CD 01 20 CXP. CALL EXTERNAL PROCEDURE: 20 IN SEGMENT: 01
6E5F 32 SLDC. PUSH #0032
6E60 8E MODI. PUSH ((TOS-1) MOD (TOS))
6E61 23 SLDC. PUSH #0023
6E62 C3 EQUI. PUSH ((TOS-1) = (TOS))
6E63 ED SLDO. PUSH BASE.06
6E64 0B SLDC. PUSH #000B
6E65 C3 EQUI. PUSH ((TOS-1) = (TOS))
6E66 84 LAND. PUSH ((TOS-1) AND (TOS))
6E67 8D LOR. PUSH ((TOS-1) OR (TOS))
6E68 A9 21 LDO. PUSH BASE.21
6E6A 01 SLDC. PUSH #0001
6E6B C3 EQUI. PUSH ((TOS-1) = (TOS))
6E6C 8D LOR. PUSH ((TOS-1) OR (TOS))
6E6D A5 81 5F LA0. PUSH #BASE.015F
6E70 EF SLDO. PUSH BASE.08
6E71 A4 02 IXA. PUSH #((TOS-1)[TOS]). ARRAY SIZE=02
6E73 EE SLDO. PUSH BASE.07
6E74 C0 10 01 IXP. INDEX PACKED ARRAY. USE TOS & TOS-1, UB1, UB2. ???
6E77 BA LDP. PUSH FIELD. TOS: RTBIT;#;^
6E78 8D LOR. PUSH ((TOS-1) OR (TOS))
6E79 B6 01 01 LOD. PUSH ACTREC(-01).01
6E7C B6 01 81 CD LOD. PUSH ACTREC(-01).01CD
6E80 17 SLDC. PUSH #0017
6E81 C3 EQUI. PUSH ((TOS-1) = (TOS))
6E82 B6 01 81 CD LOD. PUSH ACTREC(-01).01CD
6E86 01 SLDC. PUSH #0001
6E87 C4 GEQI. PUSH ((TOS-1) >= (TOS))
6E88 B6 01 81 CD LOD. PUSH ACTREC(-01).01CD
6E8C 04 SLDC. PUSH #0004
6E8D C8 LEQI. PUSH ((TOS-1) <= (TOS))
6E8E 84 LAND. PUSH ((TOS-1) AND (TOS))
6E8F 8D LOR. PUSH ((TOS-1) OR (TOS))
6E90 84 LAND. PUSH ((TOS-1) AND (TOS))
6E91 B2 01 80 F7 LDA. PUSH #ACTREC(-01).F7
6E95 EF SLDO. PUSH BASE.08
6E96 A4 02 IXA. PUSH #((TOS-1)[TOS]). ARRAY SIZE=02
6E98 EE SLDO. PUSH BASE.07
6E99 C0 10 01 IXP. INDEX PACKED ARRAY. USE TOS & TOS-1, UB1, UB2. ???
6E9C BA LDP. PUSH FIELD. TOS: RTBIT;#;^
6E9D 01 SLDC. PUSH #0001
6E9E C3 EQUI. PUSH ((TOS-1) = (TOS))
6E9F 84 LAND. PUSH ((TOS-1) AND (TOS))
6EA0 00 SLDC. PUSH #0000
6EA1 00 SLDC. PUSH #0000
6EA2 CD 01 20 CXP. CALL EXTERNAL PROCEDURE: 20 IN SEGMENT: 01
6EA5 08 SLDC. PUSH #0008
6EA6 8E MODI. PUSH ((TOS-1) MOD (TOS))
6EA7 03 SLDC. PUSH #0003
6EA8 C3 EQUI. PUSH ((TOS-1) = (TOS))
6EA9 84 LAND. PUSH ((TOS-1) AND (TOS))
6EAA 8D LOR. PUSH ((TOS-1) OR (TOS))
6EAB A1 02 UJP. IF NOT (TOS) THEN JUMP TO 6EAF

Yes folks, all those lines form one boolean expression. Good luck trying to figure it out.

Now the hint for the function GETADDR: There are no bugs. It's non-standard Pascal, but it is intentional to have the negative subscript. The code works exactly the way it is intended. This is a function in Wizardry IV.

Tommy
  Switch to threaded view of this topic Create a new topic Submit Reply
Previous Topic: "contour" animation
Next Topic: A quest for rare software
Goto Forum:
  

-=] Back to Top [=-
[ Syndicate this forum (XML) ] [ RSS ] [ PDF ]

Current Time: Sat Apr 20 12:13:46 EDT 2024

Total time taken to generate the page: 0.12595 seconds