Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Re-engineered: Wizardry III, Legacy of Llylgamyn

1,254 views
Skip to first unread message

TommyGoog

unread,
Apr 10, 2012, 3:53:17 AM4/10/12
to
Recently I completed a rather interesting project that I'd like to
share with the Apple][ community. I re-engineered the two main
executable files, WIZARDRY.CODE and WIZUTIL.CODE, for "Wizardry III,
Legacy of Llylgamyn" into Pascal code.

The .dsk images for the source code have been uploaded to ASIMOV:

ftp://ftp.apple.asimov.net/pub/apple_II/images/games/rpg/wizardry/wizardry_III/Wizardry_iii_SourceCode.zip/Wizardry.code
Wiz3A.DSK
Wiz3B.DSK
Wiz3C.DSK
Wiz3D.DSK

ftp://ftp.apple.asimov.net/pub/apple_II/images/games/rpg/wizardry/wizardry_III/Wizardry_iii_SourceCode.zip/WizUtil.code
Wiz3U.DSK

There are also the "extended listing" files for the Pascal compiles
and the the 6502 assemblies:

ftp://ftp.apple.asimov.net/pub/apple_II/images/games/rpg/wizardry/wizardry_III/Wizardry_iii_SourceCode.zip/Wizardry.code
WizardryListing.txt
WizAssm6502.txt

ftp://ftp.apple.asimov.net/pub/apple_II/images/games/rpg/wizardry/wizardry_III/Wizardry_iii_SourceCode.zip/WizUtil.code
WizUtilListing.txt
WizUtilCopyProt6502.txt

I'm hoping by posting here that I might start a dialog with others
concerning my findings in the code. If no-one is interested, then
this may turn into a long monologue from me. I'd like to share some
or all of the following (in no particular order):

1. My technique and approach to re-engineering the files.
2. Explanation of how parts of the code work.
3. Explanation of the data files in the game (such as
SCENARIO.DATA).
4. Differences in the various "versions" of Wizardry III (for
example: May 1983 compared to Aug 1983).
5. Bugs or software defects that I found in the code (or that you
might find).
6. Anti-piracy (copy-protection) technique used.

If there is a better forum or website (or Google Group) to present
this information, please let me know.

--Tommy

Tempest

unread,
Apr 10, 2012, 9:19:37 AM4/10/12
to
I'm looking forward to hearing what you've found out. Any particular reason you started with Wizardry III rather than I or II (or IV or V for that matter)?

TommyGoog

unread,
Apr 11, 2012, 12:01:47 AM4/11/12
to
On Apr 10, 8:19 am, Tempest <temp...@atariprotos.com> wrote:
> I'm looking forward to hearing what you've found out.  Any particular reason you started with Wizardry III rather than I or II (or IV or V for that matter)?

To solve the game!? :)

In 1990 I purchased a boxed set for Wizardry 1, 2, 3. By the summer
of 1991 I had completed the first 2 scenarios but got stuck most of
the way through Wizardry 3. My first goal at the time was to figure
out how to "win" the game :) Once I started I was really intrigued by
the code itself and what the authors had created. It was a challenge
to figure out the code similar to figuring out how to win a RPG.

From Sep 1991 until sometime in 1992 I worked a quite a bit at
deciphering the code. I spent a few months in 1995 completing a rough
interpretation (pseudo-Pacal code) of the generated p-code for all the
subroutines. In Aug 2011 I saw the box of paper in my back room and
thought about converting all the p-code listings I had into actual
Pascal code as a personal challenge.

In Oct 2011 I started the process of re-engineering Pascal code for
WizUtil.Code and completed the first draft of code in November. I
completed the re-engineering of Wizardry.Code in Feb 2012.

Up until that time I had used AppleWin.exe with 2 disk drives, but to
fully compile Wizardry III you need at least 4 disk drives (unless you
write your own Pascal linker). So I got ahold of AppleWin source code
from BerliOS and modified it to use 4 disk drives. On Feb 15 I had my
first complete compilation of all segments of Wizardry III. After
that I spent some time fixing small formatting problems and updating
some variable names and structures to be better than their generic
names that I had originally used.

Early on I noticed that the "version" in my boxed set was different
from the "version" at ASIMOV, so I uploaded my boxed set version of
Wizardry III to ASIMOV (in March), and have recently uploaded the
source code that I re-engineered. I will have a lot more to say about
the differences in the 2 versions in an upcoming posting.

--Tommy



TommyGoog

unread,
Apr 11, 2012, 10:01:21 PM4/11/12
to
I thought I'd share some of the "tools" and documentation I used in
this project.

To make things more readable I've shortened the ASIMOV paths in
the
following. They all originate from:
ftp://ftp.apple.asimov.net/pub/apple_II

1. Apple Pascal 1.1
/images/programming/pascal/ucsd/
2. Apple Pascal - Operating System Reference Manual
/documentation/programming/pascal/Apple Pascal - Operating System
Reference Manual.pdf
3. Apple Pascal Language Reference Manual (and Addendum)
/documentation/programming/pascal/Apple Pascal Language Reference
Manual
with Addendum.pdf
4. Pascal File System:
http://code.google.com/p/profuse/wiki/PascalFileSystem
5. Pascal User Manual and Report, Kathleen Jensen, Niklaus Wirth
I have a paperback book with this title. I have found the Niklaus
Wirth
Report here:
/documentation/programming/pascal/Pascal-Wirth-
PascalRevisedReport.pdf
6. Hyde_P-Source - A Guide to the Apple Pascal System 1983.
From this manual I used the idea of "variant records" to access
Apple
memory from Pascal, but otherwise I did not use it:
/documentation/programming/pascal/Hyde_P-Source-A Guide to the
APPLE
Pascal System_1983.pdf
7. Apple ][ Pascal 1.1 P-Code Interpreter 6502 Disassembly
This manual gave me the inspiration to create Pascal code from the
Wizardry files. Back in 1991, I did not know of this reference and
I did
my own discovery of much of the internals for the P-code
interpreter.
/documentation/programming/pascal/Apple 2 Pascal 11
PCodeIntDism.pdf

8. What's Where In the Apple
/documentation/misc/What's Where in the Apple-A Complete Guide to
the
Apple Computer.pdf
9. Beneath Apple DOS, Don Worth and Pieter Lechner
/documentation/os/dos/Beneath Apple DOS_alt.pdf
10. Apple ][ Reference Manual
/documentation/misc/Apple 2 Manuals.zip/a2rm.pdf

11. Applewin (V. 1.20.1.0)
/emulators/applewin/AppleWin1.20.1.0.zip
12. Copy2+ V5.2
I'm not sure where I got this version from. There are others like
Copy2+ V5.0 here:
/images/disk_utils/copy_ii/Copy II+ v5.0.dsk
13. Apple Disk Transfer (ADT version 1.22)
I downloaded version 1.22 in 2005 (?) and used it to transfer
Wizardry III (Aug version) eventually to my PC.
14. WinDiff.exe
I've had this tool for a long time. I think I got it with Microsoft
Visual Studio V 6.0. You might also be able to get it from here:
http://www.microsoft.com/download/en/details.aspx?id=18546
15. Pascal P-code disassembler
(I wrote my own).

--Tommy

TommyGoog

unread,
Apr 11, 2012, 10:18:21 PM4/11/12
to
Differences in Versions of Wizardry III (Part 1 of xx)
------------------------------------------------------

Each Wizardry III diskette has a "version number" displayed on the
copyright screen. The authors were not careful about updating the
version number after software release(s). Therefore, the version
number remained the same on at least 2 separate released versions of
Wizardry III.

I am of course assuming that the version at ASIMOV for May 1983 is
an accurate rendition of a released version of the software.

The version number that I have seen displayed for Wizardry III is:
VERSION -4- OF 04-MAY-83 SER:

One version at ASIMOV has a creation date of "6/27/1998". Using the
Apple Pascal system to examine the Wizardry III directory shows that
files were last updated on 04 May 1983.

There is another version (that I uploaded on 3/22/2012) that also
displays:
VERSION -4- OF 04-MAY-83 SER:

Those files were last updated on 20 Aug 1983.

I will now describe the differences between the May and Aug versions.
Some differences are in the code, and others are in the data file(s).

I apologize in advance about this posting since much of the
material is not very interesting, especially if you are not
intimately knowledgeable of the code and data for the program.

I was not very surprised about most of the bug fixes to the program
(including the correcting of the name "LLYLGAMN" in a message), but I
was surprised a bit at the amount of "tweaking" of the data in
SCENARIO.DATA.

Using the Pascal system (or using a disk sector editor to examine T$0 S
$B) displays the Pascal Volume "name". On the May version of the boot
side it erroneously shows "KODBV2" (Knight of Diamonds - Boot) and on
the Aug version it shows "LOLBOOT".

The only other significant differences in this sector are the dates on
the files:

Wizardry.code 4-May-83 20-Aug-83
WizUtil.code 4-May-83 2-Jul-83

(An insignificant change between the 2 versions is the unused byte
following the file name "SYSTEM.LIBRARY".)

Normally Track $0 Sector 5 (T$0 S$5) is the end of the Pascal
directory. Wizardry III has "stolen" the last sector of the directory
(T$0 S$5) for its own use. Here the 2 versions differ a quite a
bit. The information here is used during the "copy protection"
code. In the May version, a number of the values found from byte $1F
to $67 are used, although a lot is just leftover residual data that is
simply garbage. In the Aug version, only the values from bytes $0 to
$1E are used.

On Track 35 ($22), Sectors 1-9 and 15 ($F) have residual garbage that
is not there on the Aug version (these are Pascal blocks 275 to 279
($113 to $117 in hex). In the Aug version, block 279 ($117) is used
in the copy protection scheme and did not transfer using ADT.

The following files are identical on the May and Aug boot diskette:
SYSTEM.CHARSET
SYSTEM.STARTUP
PICTURE.BITS
SYSTEM.LIBRARY
RTSTRP.APPLE
SYSTEM.PASCAL
SYSTEM.MISCINFO

The files that are different are: WIZARDRY.CODE and WIZUTIL.CODE.

--Tommy

TommyGoog

unread,
Apr 11, 2012, 11:14:04 PM4/11/12
to
Differences in versions of Wizardry III (Part 2 of xx)
------------------------------------------------------

WIZARDRY.CODE differences

Starting at Block 6 (T$0 S$3) is WIZARDRY.CODE. The first part of
this file is the Segment Dictionary. Starting at address $40 you can
see the segment names: WIZARDRY, UTILITIE, SHOPS, SPECIALS, …RUNNER.
Above these names are 4 byte records for up to 16 segments. The first
4 bytes are for WIZARDRY, the next 4 bytes are for UTILITIE, etc. The
4 bytes are the length and relative offset to the segments procedure
table. The length and offset are in "little-endian" notation.

In the segment dictionary we notice differences in the lengths for
WIZARDRY, SHOPS, SPECIALS, CUTIL, ROLLER, REWARDS, and RUNNER. I will
now describe the changes to these segments.

WIZARDRY segment
================

One odd thing about the WIZARDRY.CODE file is that the WIZARDRY
segment is found at the beginning of the file and not at the end.
Since this is the ML for the file I would expect to find it at the end
of the file similar to the WIZUTIL.CODE mainline segment. Because of
this a binary comparison of the released Wizardry III with my compiled
version generates a lot of differences even though the actual
executable code is identical.

OVERUNDR subroutine
-------------------

In my source code you can find a routine called OVERUNDR that is
called from ADDLONGS and SUBLONGS. This subroutine was added to the
code in the Aug version. It is number $29, so all references to
procecures (and functions) with larger values are now different
between the two versions. For example, INT2BCD was $29 in the May
version and is now $2A.

DRAWSCR subroutine
------------------

The code in DRAWSCR (6502 code) has been altered. Following the
label L4EDF is a test and BEQ L4F15. This is new code in the Aug
version. This code has something to do with painting the HiRes screen
based on the values found in the staging area (the Lo-Res screen).
The Aug version seems to now process values 0 and 64 differently than
before. For 0 and 64, the values stored at the Hi-Res screen location
are cleared to 0 for the 8 bytes that represent the "character".

DRAWLINE subroutine
-------------------

The code in DRAWLINE is nearly identical in both versions, except for
one instruction near the top where the May version had "ORA #$80" to
ALWAYS set the value, but the Aug version has an "ADD #$80" to TOGGLE
the value.


SHOPS segment
=============

TRANSACT subroutine
-------------------

A fix was added to TRANSACT. If a new "back room" object is sold to
Boltac's, instead of setting the inventory count to 1 (as in May), it
is now set to the "back room" value + 1. By "back room" value I am
referring to those items that are added to the list of objects for
sell after another item has "sold out".

2325 8 22:5 562 IF OBJECT.BOLTACXX < - 1 THEN
2326 8 22:6 570 OBJECT.BOLTACXX := 1 - OBJECT.BOLTACXX
2327 8 22:5 571 ELSE

ENTMAZE subroutine
------------------

In ENTMAZE three global variables are now initialized to 0:
SAVEX, SAVEY, SAVELEV.

CONGRATS subroutine
-------------------

Messages in CONGRATS have been altered. An extraneous blank character
has been inserted into a message:

2719 CENTSTR( MAINWIN, 'WILL YOU SURRENDER THE ORB TO');
old: WILL YOU SURRENDER THE ORB TO
new: WILL YOU SURRENDER THE ORB TO

Since messages in Pascal are on EVEN word boundaries, this change has
a ripple effect on the next few lines of P-code that are generated.

Other messages in CONGRATS have also been changed:

YOUR PARTY THE STAR OF LLYLGAMN (*),
YOUR PARTY THE STAR OF LLYLGAMYN (*)

INVITE YOU TO SELECT
AND INVITE YOU TO SELECT

OTHERS WHO
OTHERS

ALSO DESERVE THIS AUGUST TITLE!
WHO ALSO DESERVE THIS AUGUST TITLE!


SPECIALS segment
================
53 procedures in May 1983
49 procedures in Aug 1983

INSPECT subroutine
------------------

In INSPECT before calling EXITPICK a call to DELWIN is made to release
TEMPWIN memory.

DOSEARCH subroutine
-------------------

In DOSEARCH, the variable ENCB4RUN (encounter before run) is now set
to FALSE. This is after "Search" was answered "Y" and is before
entering combat.

SWITCHLOC subroutine
--------------------

SWITCHLOC has been completely rewritten.
1. The local variables are removed (MP01 and MP2C).
2. A test is now performed to see if we should go to
RUNNER or NEWMAZE
3849 9 43:1 4 IF SAVELEV = MAZELEV THEN
3850 9 43:2 11 XGOTO := XRUNNER
3851 9 43:1 11 ELSE
3852 9 43:2 17 XGOTO := XNEWMAZE;
3. Calls to SWITCH are made for 3 items:
3853 9 43:1 21 SWITCH( MAZEX, SAVEX);
3854 9 43:1 28 SWITCH( MAZEY, SAVEY);
3855 9 43:1 35 SWITCH( MAZELEV, SAVELEV);

Old code looks like it used RANDOM to determine new direction
you are facing.

Old code always went to RUNNER.

In the old code, routines $2D to $30 have been removed. These are all
related to the change with SWITCHLOC.


CUTIL segment
=============

CACTION routine
---------------

This code was modified to build up the response string (FDSUPRT) as
the menu option string is being built. This was done to increase
performance. Later in the code the input buffer is checked to see if
a character is available and if it is a valid one. If it is, then the
menu is not displayed. There are many times when you know you are
going to type "F F F P P P" during an encounter. This speeds up the
process by not re-displaying the option menu every time.

5099 12 2:7 518 FDSUPRT := CONCAT( FDSUPRT, 'UPRT');
5100 12 2:7 548 IF CHKKEYBD THEN
5101 12 2:8 555 BEGIN
5102 12 2:9 555 GETKEY;
5103 12 2:9 558 BASE04 := -1;
5104 12 2:9 562 FOR CHX := 1 TO LENGTH( FDSUPRT) DO
5105 12 2:0 577 IF INCHAR = FDSUPRT[ CHX] THEN
5106 12 2:1 585 BASE04 := CHX - 1
5107 12 2:8 586 END

5113 12 2:7 603 IF BASE04 = -1 THEN
5114 12 2:8 609 BEGIN
5115 12 2:9 609 UNITCLEAR( 1);
5116 12 2:9 612 BASE04 := MENU( COMB1WIN,
5117 12 2:9 615 CONCAT( FDSUPRT1, 'U)SE/P)ARRY
/R)UN/T)AKE BACK'))
5118 12 2:8 664 END;


CASTCHK routine
---------------

The old code was testing both conditions in one IF statement. The new
code has them split into 2 IF statements.

4858 12 14:3 16 IF SPELLI < 22 THEN
4859 12 14:4 21 BEGIN
4860 12 14:5 21 IF CHARACTR[ MYCHARX].MAGESP[ SPELLGR]
> 0 THEN

ROLLER segment
==============

RITEPASS subroutine
-------------------
There was a grammatical error with the word "it's" that has been
corrected.
old:
THE TEMPLE PRIESTS LINK UP THIS
ANCESTRAL SPIRIT WITH IT'S
DESCENDANT...

new:
THE TEMPLE PRIESTS LINK UP THIS
ANCESTRAL SPIRIT WITH ITS
DESCENDANT...

NOCHANGE subroutine
-------------------

An "EXIT()" was inserted at the end of this routine to return back a
couple of levels.
8310 17 22:1 89 EXIT( CHGCLASS)

The problem they are trying to fix is the following:
If you are one class and you can only change to one class then
don’t solicit to "change class".

In the May 1983 code, it would still fall into the "change into xxx"
code, which is kind of silly if you are one class and can only change
into that one class.

There is still an interesting bug that they perhaps did not think of.
It is possible that you are currently one class of character (say
FIGHTER), but you do not have the attributes for a FIGHTER, but you do
qualify to change to another character class (MAGE). The Aug code
will not allow you to change from FIGHTER to MAGE in this case.


REWARDS segment
===============

In DISARMTR a bug was fixed. If TRAPTYP was 3 for a chest, and you
typed the wrong trap name, then there was no damage done. To fix this
a DOTRAPDM call was inserted following the case statement.
10169 19 25:5 296 DOTRAPDM


RUNNER segment
==============
The May code had 62 procedures.
The Aug code has 70 procedures.

DRAWMAZE subroutine
-------------------

Calls to new routines were added after STEP2 (STEPFRWD, STEP3,
STEPFRWD, and STEP4):

11504 20 2:1 99 STEP2;
11505 20 2:1 101 STEPFRWD;
11506 20 2:1 103 STEP3;
11507 20 2:1 105 STEPFRWD;
11508 20 2:1 107 STEP4;

MAZELINB subroutine
-------------------
This subroutine was added in Aug.

In the May version, all the calls to DRAWLINE were directly made with
9 parameters, the first 3 always being the same.

In the Aug version, the call to DRAWLINE is now in MAZELINB with the
first 3 parameters always the same, and the other 6 parameters passed
in.

The net effect is a reduction in the overall amount of P-code
generated.

Since MAZELINB was inserted as procedure $C (12), references to all
procedures later in the code are now different.

RUNMAIN subroutine
------------------
A call to PRSTATS was inserted when toggling the stats screen
with "O".

RUNINIT subroutine
------------------

A call to PRSTATS was inserted when starting RUNNER.


Next up is WIZUTIL.CODE changes....

--Tommy

TommyGoog

unread,
Apr 12, 2012, 12:33:28 AM4/12/12
to
Differences in versions of Wizardry III (Part 3 of xx)
------------------------------------------------------

WIZUTIL.CODE differences


WIZBOOT segment
===============

COPYPROT subroutine (6502 code)
-------------------------------

This code is called from CHKCOPY. It is quite different between the
two versions. In a later post I will likely try to explain the
differences.

CHKCOPY subroutine
------------------

Like COPYPROT there are a lot of differences to this routine.


UTILS segment
=============

EXITUTIL subroutine
-------------------

In EXITUTIL, the May code had SYSTEM.CHARSET on the first FINDFILE,
but SYSTEM.PASCAL on the second. This was corrected in Aug to have
SYSTEM.CHARSET on both.

TRANSFER subroutine
-------------------

Two changes were introduced into this code.
1. An attempted WRITE to the "TO" disk is made before trying to
transfer the character.
2. The "game name" test in May was for the full name, but in Aug
it is testing only the first ten characters
("The Legacy").

TRANBAD subroutine
------------------
A new message was introduced, and another was dropped.

May code:
CANT COPY TO BACKUP - PUT IN SOURCE
DUPLICATE NAME - PUT IN SOURCE
ALL SLOTS USED - PUT IN SOURCE
NOT A LOL DISK - PUT IN SOURCE

Aug code:
DUPLICATE NAME - PUT IN SOURCE
ALL SLOTS USED - PUT IN SOURCE
WRITE PROTECTED - PUT IN SOURCE
NOT A LOL DISK - PUT IN SOURCE


MAKESCEN segment
================

COPYSCEN subroutine
-------------------

A bug fix was attempted here by changing three values from 280 to 279
(or more likely changing the value of a CONST…my code has hard-coded
values).
There is still a minor bug in this code though.

Next up...SCENARIO side of diskette.

--Tommy

TommyGoog

unread,
Apr 14, 2012, 3:12:45 AM4/14/12
to
Differences in versions of Wizardry III (Part 4 of xx)
------------------------------------------------------

MASTER Scenario (flip side of the BOOT diskette)

The BOOT side and the MASTER SCENARIO side both have WIZARDRY.CODE.
For the May version the same code exists on the BOOT side as the
SCENARIO side. For the Aug version it is also the same on both sides
(but obviously different between May and Aug).

SCENARIO.MESGS is the same in the May version and the Aug version.

In the May version, the order of files in the directory and on the
disk is:

WIZARDRY.CODE
SCENARIO.DATA
SCENARIO.MESGS

In the Aug version, the order is:

WIZARDRY.CODE
SCENARIO.MESGS
SCENARIO.DATA

The boot sector (T$0 S$0) is different between the May version and the
Aug version. The May version boot sector is identical to the boot
sector on UCSD Pascal 1.1 disk 1. When the MASTER scenario is used to
boot the Apple, the boot code cannot find SYSTEM.APPLE so it displays:

NO FILE SYSTEM.APPLE

The boot sector for Aug displays the following message:

FLIP DISKETTE, THEN PRESS [RETURN]

Booting the MASTER SCENARIO side of the diskette can therefore easily
distinguish between the May and the Aug versions of Wizardry III.

Interesting note: In the Aug version they fixed the "volume name" on
the BOOT side from KODBV2 to "LOLBOOT", yet on the MASTER SCENARIO
side, both the May and Aug versions have "KODBTTM" (Knight of
Diamonds).

Like the boot side, the MASTER SCENARIO side for Wizardry "steals"
sector 5 for it's own use. There is a "serial number", or "disk
identifier" number, in the first 7 bytes (some code thinks it is 8
bytes!?) of the sector that are different from the serial number on
the BOOT side. When transferring characters to your DUPLICATE
scenario, these values are checked. They are also checked when
backing up characters and when recovering characters.

When making a DUPLICATE scenario from the MASTER SCENARIO, T$0 S$5 is
copied and then modified. (See SSB4BL5 in WIZUTIL). The first 7
bytes are a "serial number". The bytes at offsets $A, $B, and $C are
modified as follows:

$A is set to ($B MOD 255) + 1
$B is cleared to 0.
$C is set to a RANDOM value.

I don't think the values in $A, $B, or $C are otherwise used. I
suppose if you make another DUPLICATE scenario, but use this first
copy then the value in the new DUPLICATE scenario will have different
values.

Note: Most of the code is extremely well written, but then there is
code like the following:

1049 8 11:3 198 SB10 := SSB4BL5[ 11] + 1;
1050 8 11:3 208 IF SB10 = 256 THEN
1051 8 11:4 217 SB10 := 1;
1052 8 11:3 221 SSB4BL5[ 10] := SB10;
1053 8 11:3 229 SSB4BL5[ 11] := 0;
1054 8 11:3 235 SSB4BL5[ 12] := (SSB4BL5[ 12] +
RANDNUM) MOD 256

I think lines 1049 to 1052 can be written as:
SSB4BL5[ 10] := (SSB4BL5[ 11] MOD 255) + 1;

And line 1054 (assuming RANDNUM truly is random):
SSB4BL5[ 12] := RANDNUM MOD 256;

In the May version, the first 14 bytes on T$0 S$5 are:
06 30 30 30 33 32 30 00 F000320@
00 00 00 01 6A 00

In the Aug version, these values are:
06 30 30 30 30 30 39 00 F000009@
00 00 00 01 52 00

The rest of the values in sector 5 are not used, but have random
residual garbage. May and Aug have about 43 bytes that don't compare.


SCENARIO.DATA differences
=========================

There are several different sections to SCENARIO.DATA (OBJECTS,
ENEMIES, MAZE, REWARDS, etc.) I've written some software to display
all the records in each section for each of the data attributes. In
the following descriptions, the heading refer to record names in the
source code.

OBJECTS (in SCENARIO.DATA)
-------
CHGCHANC
SOPIC PHILTRE May 0
Aug 80

In the Aug version, SOPIC PHILTRE has an 80% chance to change into
a BROKEN ITEM.

CHANGETO
DIALKO AMULET May 41
Aug 0

In the Aug version, DIALKO AMULET will change to a BROKEN ITEM
instead of PLATE ARMOR+1 (still at 25% chance).

SPELLPWR
BOOK OF DEATH May 0
Aug 47

Now BOOK OF DEATH has MABADI associated with it instead of
NOTHING.
(Similar to BOOK OF LIFE having DI (43) associated with it.)


ENEMIES (in SCENARIO.DATA)
-------
CLASS
GARIAN RAIDER May 0
Aug 3

EXPAMT.LOW
GARIAN PRIEST May 150
Aug 100

TEAMPERC
MAN AT ARMS May 20
Aug 30
TeamPercent was increased, therefore there is a greater chance to
be paired with a GARIAN RAIDER.


HPREC.LEVEL HPREC.HPFAC HPREC.HPMINADD AC
NINJA May 3 4 3 4
Aug 2 6 4 5

HPREC is used to calculate Hit Points.

Each enemy has 2 reward value "packages" associated with it (REWARD
and REWARD2). Gold and "found" items are based on these reward
packages. The following had their REWARD2 value changed from package
19 to package 17.

LEPRECHAUN
UNICORN
CRUSADER
PIXIE
CENTAUR

EXPAMT.LOW MAGSPELS
PIXIE May 300 1
350 2

HPREC.HPMINADD
MASTER NINJA May 0
Aug 7

The following had REWARD2 changed from 23 to 21:

CRUSADER LORD
FAERIE
SERAPH
T'IEN LUNG
ROC

EXPAMT.LOW
CRUSADER LORD May 600
Aug 700

HPREC.HPFAC HPREC.HPMINADD
FAERIE May 10 0
Aug 8 8

MAGSPELS
ELVEN MAGE May 4
Aug 5

RECSN 3A 3B 3C
SAMURAI May 2 0 0 0
Aug 3 1 6 4

EXPAMT.LOW
WIGHT May 1000
Aug 3000

HPREC.HPFAC HPREC.HPMINADD
ARCHDEMON May 10 10
Aug 2 60

HPREC.LEVEL HPREC.HPFAC HPREC.HPMINADD
CYCLOPS May 10 10 0
Aug 8 3 60

HPREC.HPFAC HPREC.HPMINADD
ARCHANGEL May 10 10
Aug 2 60

TEAMPERC
PO'LE May -1
Aug 0

TEAMPERC
L'KBRETH May -1
Aug 0


MAZE (in SCENARIO.DATA)
----

For level 1 of the maze, the ENMYCALC table was changed slightly.
This table determines the type of enemies you will encounter under
different circumstances. The values for MINENEMY for records 1 and 2
were altered:

May:
ENMYCALC MINENEMY MULTWORS WORSE01 RANGE0N PERCWORS
1 2 0 0 5 -1
2 7 0 0 5 -1
3 7 0 0 5 -1

May:
ENMYCALC MINENEMY MULTWORS WORSE01 RANGE0N PERCWORS
1 7 0 0 5 -1
2 2 0 0 5 -1
3 7 0 0 5 -1


REWARDS (in SCENARIO.DATA)
-------

There are 24 different "reward packages". These determine if there is
a trap, what kind of trap, the gold reward, and any item reward. I
will only list a few of the changes here in detail. The following
records were changed:

0, 1, 3, 4, 5, 6, 7, 8, 9, 13, 15, 17, 21

Each reward package can have up to 9 (?) different sub-records
associated with it. Some of the changes they made might not be quite
correct. For example, for reward 0 they changed the values for the
second set of rewards from zeroes to normal type values, but did not
bump the REWRDCNT value that determines the number of rewards in the
package. A sub-record has these fields:

REWDPERC BITEM TRIES AVEAMT MINADD MULTX TRIES2 AVEAMT2 MINADD2
REWDPERC BITEM MININDX MFACTOR MAXTIMES RANGE PERCBIGR

(Line 1 above describes the sub-record when BITEM is 0, otherwise
line 2 describes the sub-record.)

Also for reward package 15, they decremented the REWRDCNT field from 2
to 1, but did not change the values for the second sub-record to
zeroes (to be consistent).

I will show the changes for reward package 9.

May:
REWRDCNT REWDPERC BITEM TRIES AVEAMT MINADD MULTX TRIES2 AVEAMT2
MINADD2
6 100 0 6 10 40 5 3 1 3
REWDPERC BITEM MININDX MFACTOR MAXTIMES RANGE PERCBIGR
100 1 31 10 2 10 50
50 1 61 10 2 10 25
25 1 74 10 2 10 30
75 1 53 0 0 1 -1
75 1 53 0 0 1 -1
Aug:
REWRDCNT REWDPERC BITEM TRIES AVEAMT MINADD MULTX TRIES2 AVEAMT2
MINADD2
6 100 0 6 10 40 5 3 1 3
REWDPERC BITEM MININDX MFACTOR MAXTIMES RANGE PERCBIGR
90 1 30 12 2 12 50
80 1 54 10 2 10 50
50 1 74 10 2 10 30
40 1 84 10 2 10 50
50 1 62 10 2 10 35


This pretty much completes the description of differences between the
May and Aug versions of Wizardry III.

I think the next thing I'll tackle is trying to display the records
that make up SCENARIO.DATA. For example, all the OBJECTS and their
associated record values, all the ENEMIES and their records, the MAZE
description (Walls, Hidden, where fights are likely to occur...).

--Tommy

Alex Lee

unread,
Apr 14, 2012, 5:13:20 AM4/14/12
to
Would your study into Wizardry III ever allow a newer version that
could be run from a single 3.5" disk instead of the multiple 5.25"
disks?

Fairly trivial thing I know, but I thought I'd just get it out there :-)

- Alex

TommyGoog

unread,
Apr 14, 2012, 9:52:27 PM4/14/12
to
I think the answer is "yes", but I'm not sure exactly what you are
asking.

On what type of computer (or simulator) would you use this 3.5" disk
version of Wizardry III?

I'm familiar with Apple][+, Apple][e, and with Windows computers. I'm
familiar with Apple][ diskettes formatted for DOS and for Pascal. I
am not familiar with later Apple systems (Mac, etc.) or the 3.5" disk
formats.

There is clearly enough storage space on a 3.5" disk to hold the
Pascal run-time system, and the executable and data files, along with
a "backup the characters" file.

You would also need to find a way to transfer characters from a
Wizardry I or Wizardry II game to the 3.5" floppy, since Wizardry III
requires you to have created your characters on those levels.

Since I have supplied the Pascal source code, it is possible for
anyone to take that code and modify it to work in any disk format (and
any computer) where a Pascal run-time system will work.

When I played the game (last year) and did my re-engineering of the
code, it was all done with the AppleWin.exe simulator on a WindowsXP
PC. I don't think that simulator supports 3.5" diskettes, but it does
support a hard-drive in slot 7. Unfortunately, Apple Pascal 1.1 does
not recognize slot 7 as a disk device.

I'm not sure why you would really need or want it on a 3.5" disk.

By the way, it is relatively easy to modify the code so that all you
need at the start of the game is the BOOT disk and then your DUPLICATE
SCENARIO disk without having to insert the MASTER SCENARIO disk in-
between.

--Tommy

Steve Nickolas

unread,
Apr 14, 2012, 9:54:54 PM4/14/12
to
On Sat, 14 Apr 2012, TommyGoog wrote:

> On Apr 14, 4:13 am, Alex Lee <ale...@mac.com> wrote:
>> Would your study into Wizardry III ever allow a newer version that
>> could be run from a single 3.5" disk instead of the multiple 5.25"
>> disks?
>>
>> Fairly trivial thing I know, but I thought I'd just get it out there :-)
>>
>> - Alex
>
> I think the answer is "yes", but I'm not sure exactly what you are
> asking.
>
> On what type of computer (or simulator) would you use this 3.5" disk
> version of Wizardry III?

Prolly either an Apple IIc+ or an Apple IIgs, using Pascal 1.3?

-uso.

Vladimir Ivanov

unread,
Apr 15, 2012, 12:32:35 PM4/15/12
to

On Sat, 14 Apr 2012, TommyGoog wrote:

> On what type of computer (or simulator) would you use this 3.5" disk
> version of Wizardry III?

Apple 3.5" External drive on:
- //e equipped with SuperDrive controller card
- //c+
- IIgs

Unidisk 3.5" drive on:
- //e equipped with Liron controller card
- //c with ROM version > 255 (e.g. 0, 3, 4)
- //c+
- IIgs

> When I played the game (last year) and did my re-engineering of the
> code, it was all done with the AppleWin.exe simulator on a WindowsXP
> PC. I don't think that simulator supports 3.5" diskettes, but it does
> support a hard-drive in slot 7. Unfortunately, Apple Pascal 1.1 does
> not recognize slot 7 as a disk device.

3.5" floppy images are simply hard-drive images. ProDOS treats both as
block devices with block size of 512 bytes, and so do emulators.


So, if you feel that adventurous about porting the game, check ProDOS and
the existing Pascal implementations for it.

ftp://ftp.apple.asimov.net/pub/apple_II/images/programming/pascal/

Perhaps Kyan and Lazer Pascal could be interesting start. I have
experience only with Instant Pascal, and while groundbreaking learning
tool for the Apple II (and a heroic programming effort), it does not have
enough free memory for "serious" work.

You should also check whether Apple Pascal 1.2 and 1.3 have support for
ProDOS devices.
Message has been deleted
Message has been deleted

TommyGoog

unread,
Apr 16, 2012, 1:41:40 AM4/16/12
to
I'd like to share my spreadsheets of Wizardry data that I currently
have on my computer. I tried entering the data as comma separated
data to comp.sys.apple2, but there are line length issues that seem to
make that not a plausible idea. I am now exploring GoogleDocs
(spreadsheets) to see if this is a better way to share the
information.

https://docs.google.com/spreadsheet/ccc?key=0ApO5bjpT_F-OdExERF9FRWF3bktJaVAwOFVvWk8xdWc

The first part of the spreadsheet should look like the following:

OBJECTS:
#,NAME,NAMEUNK,OBJTYPE,ALIGN,CURSED,SPECIAL,CHANGETO,CHGCHANC,PRICE.HIGH,PRICE.MID,PRICE.LOW,BOLTACXX,SPELLPWR,CLASSUSE-
FIGHTER,MAGE,PRIEST,THIEF,BISHOP,SAMURAI,LORD,NINJA,HEALPTS,WEPVSTY2-0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,WEPVSTY3-0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,ARMORMOD,WEPHITMD,WEPHPDAM.LEVEL,WEPHPDAM.HPFAC,WEPHPDAM.HPMINAD,XTRASWNG,CRITHITM,WEPVSTY-0,1,2,3,4,5,6,7,8,9,10,11,12,13
0,BROKEN ITEM,BROKEN ITEM,
6,0,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,,
1,ORB OF EARITHIN,CRYSTAL SPHERE,
5,0,0,30,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,,


If this is a good way to share this information, please let me know.
If there is a better way, also please let me know.

Thanks,
--Tommy

TommyGoog

unread,
Apr 21, 2012, 6:37:35 AM4/21/12
to
Scenario.Data and Scenario.MSGS revealed (Part 5 of xx)
-------------------------------------------------------

I've updated several sheets at Google/Spreadsheet for WizardryIII:

https://docs.google.com/spreadsheet/ccc?key=0ApO5bjpT_F-OdExERF9FRWF3bktJaVAwOFVvWk8xdWc#gid=0

In this posting, I will summarize what is there, and also include 6
maps in "text" format.

Sheets:

Scenario.Data TOC
Level1
Level2
Level3
Level4
Level5
Level6
Enemies
Rewards
Objects
Special Print Chars
Experience
Scenario.Mesgs


Scenario.Data TOC sheet
-----------------------

Block 0 of SCENARIO.DATA contains a structure I call the Scenario
Table of Contents (TSCNTOC).

250 1 1:D 3 TSCNTOC = RECORD
251 1 1:D 3 GAMENAME : STRING[ 40];
252 1 1:D 3 RECPER2B : ARRAY[ ZZERO..ZEXP] OF
INTEGER;
253 1 1:D 3 RECPERDK : ARRAY[ ZZERO..ZEXP] OF
INTEGER;
254 1 1:D 3 UNUSEDXX : ARRAY[ ZZERO..ZEXP] OF
INTEGER;
255 1 1:D 3 BLOFF : ARRAY[ ZZERO..ZEXP] OF
INTEGER;
256 1 1:D 3 RACE :
ARRAY[ NORACE..HOBBIT] OF STRING[ 9];
257 1 1:D 3 CLASS : PACKED
ARRAY[ FIGHTER..NINJA] OF STRING[ 9];
258 1 1:D 3 STATUS :
ARRAY[ OK..LOST] OF STRING[ 8];
259 1 1:D 3 ALIGN : PACKED
ARRAY[ UNALIGN..EVIL] OF STRING[ 9];
260 1 1:D 3 SPELLHSH : PACKED ARRAY[ 0..50] OF
INTEGER;
261 1 1:D 3 SPELLGRP : PACKED ARRAY[ 0..50] OF
0..7;
262 1 1:D 3 SPELL012 : PACKED ARRAY[ 0..50] OF
TSPEL012;
263 1 1:D 3 END;

Scenario.Data starts at block 170 of the Master Scenario disk (Track
$15 Sector $B).

Block 1 of Scenario.Data contains the MAGE spell names. An "*" before
a name indicates the beginning of a new group of spells. There are 7
groups.

Block 2 of Scenario.Data contains the PRIEST spell names. Like the
MAGE spells, an "*" before a name indicates the beginning of a new
group of spells. There are 7 groups.

Block 3 of Scenario.Data is the start of the data pointed to by
RECPER2B (Records per 2 blocks), RECPERDK (Records per Disk), and
BLOFF( Block Offset).


Level 1 to Level 6
------------------

Sheets labeled "Level1" to "Level6" contain information about each
level of the maze.

I have created a "hi-res" picture of the level with walls, doors,
hidden doors, and openings and placed it here.

Next there is a description of the maze as described by the TMAZE
record with W, S, E, N.

Following that is the "FIGHTS" array (for each location of this
level).

Next is the SQREXTRA array for each square of this level. Each value
is an index into SQRETYPE.

SQRETYPE is an array that is indexed by the values in SQREXTRA. For
example, for level 1 of the maze at coordinate 0,0 are stairs. This
is indicated by SQREXTRA having 13 in that location. The values for
AUX0, AUX1, and AUX2 give more information about the "extra" stuff
there. For the stairs at 1, 0, 0 it indicates the transfer is to
0,0,0 (back to the shops).

ENMYCALC is next. It determines the type of enemies that you will
encounter during melee.


Enemies sheet
-------------

This sheet describes the information in Scenario.Data associated with
each of the enemies you will encounter.

There are a total of 78 monsters or enemies in the game.


Rewards sheet
-------------

There are 24 "reward packages" in the game. The rewards are based on
the type of enemies defeated.


Objects sheet
-------------

There are 104 items or objects in the game that are described by this
table.


Special Print Chars sheet
-------------------------

The special print characters in Scenario.Data are actually a
collection of values that are used in the "print" subroutines to
display the "hi-res" pictures of enemies, treasure chest, and beach/
island.

This sheet is a display of the 30 special hi-res pictures in the game.


Experience Levels sheet
-----------------------

Each type of character (Fighter, Mage, Priest, …) advances to a higher
level based on this table.

Once a character reaches level 12, then the constant additive value at
level 0 is used to determine the next level.


Scenario.Mesgs sheet
--------------------

The file Scenario.Mesgs contains special messages that are displayed
during the game. Each message has an index. The index is usually
found in the SQRTYPE array under AUX1.

I don't think message 21 is ever displayed in this game (?)

Some messages have additional information about them. For example,
the cost for "A powerful magic crystal" (25000) follows the message.

The answers to riddles also follow the riddle text.


Maps of the 6 levels (Text Format)
==================================

+--++--++ ++--++--++--++--++--++--++--++--++--++--++--++--++--++--+
+--++--++--+
19 || || // // // //
||
+--++ ++//++--++ ++--++ ++--++ ++--++--++ ++ ++ ++ ++ ++ +
+ ++ ++--+
+--++ ++//++--++ ++--++ ++--++ ++--++--++ ++ ++ ++ ++ ++ +
+ ++ ++--+
18 / || || // || || || || || || //
|| || /
+//++ ++--++//++//++//++//++//++ ++//++//++--++--++--++--++--++--+
+--++--++//+
+//++ ++--++//++//++//++//++//++ ++//++//++--++--++--++--++--++--+
+--++--++//+
17 / || // // ||
|| // // // // /
+--++ ++//++//++//++//++//++//++ ++--++ ++--++--++--++--++//++--+
+--++--++--+
+--++ ++//++//++//++//++//++//++ ++--++ ++--++--++--++--++//++--+
+--++--++--+
16 | || || || || || || || ||
|| // // // // // // |
+ ++ ++ ++ ++--++--++--++ ++ ++ ++ ++//++--++--++--++--++--+
+--++--++--+
+ ++ ++ ++ ++--++--++--++ ++ ++ ++ ++//++--++--++--++--++--+
+--++--++--+
15 | || || // || || || || || || // // // // ||
|| || // |
+ ++ ++ ++//++--++//++//++//++ ++ ++//++ ++--++--++--++--++//+
+//++ ++--+
+ ++ ++ ++//++--++//++//++//++ ++ ++//++ ++--++--++--++--++//+
+//++ ++--+
14 | || || || || || || //
|| // // // || // |
+ ++ ++ ++ ++--++--++ ++ ++ ++ ++--++--++--++--++--++--++ +
+ ++ ++--+
+ ++ ++ ++ ++--++--++ ++ ++ ++ ++--++--++--++--++--++--++ +
+ ++ ++--+
13 | || || // || || //
|| // // // // // |
+ ++ ++//++--++--++--++--++--++ ++ ++//++ ++--++--++--++--++//+
+//++ ++--+
+ ++ ++//++--++--++--++--++--++ ++ ++//++ ++--++--++--++--++//+
+//++ ++--+
12 | || // // // || || || // // // // ||
|| || // |
+ ++ ++--++--++--++--++--++ ++ ++ ++ ++//++--++--++--++--++--+
+--++--++--+
+ ++ ++--++--++--++--++--++ ++ ++ ++ ++//++--++--++--++--++--+
+--++--++--+
11 | || // || || ||
|| // // // // // // |
+--++ ++ ++ ++--++--++//++ ++ ++--++ ++--++--++--++--++//++--+
+--++--++--+
+--++ ++ ++ ++--++--++//++ ++ ++--++ ++--++--++--++--++//++--+
+--++--++--+
10 / || || || // || // ||
|| // // // // /
+//++ ++//++//++//++--++--++ ++ ++//++//++--++--++--++--++--++--+
+--++--++//+
+//++ ++//++//++//++--++--++ ++ ++//++//++--++--++--++--++--++--+
+--++--++//+
9 / || || || || // || || || //
|| || /
+--++ ++ ++--++ ++--++--++ ++ ++--++--++ ++ ++ ++ ++ ++ +
+ ++ ++--+
+--++ ++ ++--++ ++--++--++ ++ ++--++--++ ++ ++ ++ ++ ++ +
+ ++ ++--+
8 || // // //
||
+--++--++--++--++--++--++--++--++--++--++--++--++--++--++--++--++--+
+--++--++--+
+--++--++--++--++--++--++--++--++--++--++--++--++--++--++--++--++--+
+--++--++--+
7 | // || // // || ||
|| |
+ ++ ++ ++ ++ ++ ++ ++--++ ++ ++ ++--++--++ ++ ++//++//+
+ ++--++--+
+ ++ ++ ++ ++ ++ ++ ++--++ ++ ++ ++--++--++ ++ ++//++//+
+ ++--++--+
6 | // || || || || || || ||
|| || |
+ ++ ++ ++ ++ ++ ++ ++--++//++--++--++//++//++ ++//++ ++--+
+//++ ++ +
+ ++ ++ ++ ++ ++ ++ ++--++//++--++--++//++//++ ++//++ ++--+
+//++ ++ +
5 | || ||
|| // // |
+ ++ ++ ++ ++ ++ ++ ++ ++//++ ++--++--++--++ ++--++--++--+
+//++--++--+
+ ++ ++ ++ ++ ++ ++ ++ ++//++ ++--++--++--++ ++--++--++--+
+//++--++--+
4 | || || //
|| // // // || |
+ ++ ++ ++ ++ ++ ++ ++ ++ ++--++--++ ++--++ ++--++--++--+
+ ++ ++ +
+ ++ ++ ++ ++ ++ ++ ++ ++ ++--++--++ ++--++ ++--++--++--+
+ ++ ++ +
3 | || // || ||
|| || |
+--++ ++ ++ ++ ++ ++ ++ ++--++ ++ ++ ++--++//++ ++--++--+
+ ++//++--+
+--++ ++ ++ ++ ++ ++ ++ ++--++ ++ ++ ++--++//++ ++--++--+
+ ++//++--+
2 :: || // || || //
|| //
+//++..++ ++ ++ ++ ++ ++ ++ ++ ++//++//++ ++--++--++ ++ +
+ ++//++--+
+//++..++ ++ ++ ++ ++ ++ ++ ++ ++//++//++ ++--++--++ ++ +
+ ++//++--+
1 | :: || || || // // ||
|| || |
+ ++ ++..++ ++ ++ ++ ++ ++ ++--++ ++ ++ ++ ++ ++--++--+
+--++ ++ +
+ ++ ++..++ ++ ++ ++ ++ ++ ++--++ ++ ++ ++ ++ ++--++--+
+--++ ++ +
0 | // || || // || ||
|| // |
+--++--++ ++--++--++--++--++--++--++--++--++--++--++--++--++--++--+
+--++--++--+
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16 17 18 19


LEVEL: 2

+--++--++--++--++--++--++--++--++--++--++--++--++--++--++--++--++--+
+--++--++--+
19 | || :: || // //
|| // |
+--++--++--++--++ ++ ++--++--++ ++ ++ ++--++ ++--++--++--++ +
+ ++ ++--+
+--++--++--++--++ ++ ++--++--++ ++ ++ ++--++ ++--++--++--++ +
+ ++ ++--+
18 | // // || || || || || || ||
|| :: || |
+ ++--++ ++--++ ++ ++ ++ ++--++..++--++ ++--++ ++ ++--++--+
+--++--++//+
+ ++--++ ++--++ ++ ++ ++ ++--++..++--++ ++--++ ++ ++--++--+
+--++--++//+
17 | || || || || || // ||
|| |
+//++--++--++--++ ++..++ ++ ++//++--++--++--++--++--++--++--++--+
+ ++ ++ +
+//++--++--++--++ ++..++ ++ ++//++--++--++--++--++--++--++--++--+
+ ++ ++ +
16 | || // || || || // :: //
|| |
+--++ ++ ++//++ ++--++--++--++--++ ++ ++//++--++--++--++--++--+
+--++ ++--+
+//++ ++ ++//++ ++--++--++--++--++ ++ ++//++--++--++--++--++--+
+--++ ++--+
15 | || || || || || || ||
|| |
+ ++//++//++ ++--++--++--++--++ ++--++..++ ++--++--++ ++..++ +
+ ++ ++ +
+ ++--++//++ ++--++--++--++--++ ++--++..++ ++--++--++ ++--++ +
+ ++ ++ +
14 | // || // || || || :: // || || ||
|| |
+--++--++ ++--++--++ ++--++--++--++--++ ++ ++--++--++ ++ ++ +
+ ++ ++ +
+--++--++ ++--++--++ ++--++--++--++--++ ++ ++--++--++ ++ ++ +
+ ++ ++ +
13 | || // || |/ ||
|| |
+--++--++--++--++--++--++--++--++--++--++ ++--++//++--++//++--++//+
+ ++ ++ +
+--++--++--++--++--++--++--++--++--++--++ ++--++//++--++//++--++..+
+ ++ ++ +
12 | || // || // || || || ||
|| |
+ ++ ++--++//++--++ ++ ++--++--++ ++--++--++ ++--++ ++--++ +
+..++//++ +
+ ++ ++--++//++--++ ++ ++--++--++ ++--++--++ ++--++ ++--++ +
+..++//++ +
11 | || || || || || || // || || // |/ || || ::
|| || || |
+//++//++--++--++ ++--++ ++//++ ++--++--++--++--++ ++ ++--++ +
+ ++ ++ +
+//++//++--++--++ ++--++ ++//++ ++--++--++--++--++ ++ ++--++ +
+ ++ ++ +
10 | || // :: || || // |/ ::
|| || || |
+ ++//++ ++--++--++--++--++--++--++ ++ ++--++--++--++--++--++--+
+--++ ++ +
+ ++//++ ++--++--++--++--++--++--++ ++ ++--++--++--++--++--++--+
+--++ ++ +
9 | || || || // || //
|| /: || |
+ ++ ++ ++ ++--++//++ ++--++ ++--++--++--++--++--++--++--++ +
+--++--++ +
+ ++ ++ ++ ++--++//++ ++--++ ++--++--++--++--++--++--++--++ +
+--++--++ +
8 | || || || || || || // || // //
|| :: |
+//++--++ ++//++ ++ ++--++--++--++--++--++--++--++--++--++--++--+
+--++ ++ +
+//++--++ ++//++ ++ ++--++--++--++--++--++--++--++--++--++--++--+
+--++ ++ +
7 | || || || //
|| :: // :: |
+ ++ ++--++ ++--++--++--++//++--++--++--++--++--++--++--++--++--+
+--++--++--+
+ ++ ++--++ ++--++--++--++//++--++--++--++--++--++--++--++--++--+
+--++--++--+
6 | || || || || || /| |/ || ||
|: :: || :: |
+ ++ ++ ++//++ ++ ++--++ ++//++ ++//++--++ ++//++--++//++--+
+ ++ ++--+
+ ++ ++ ++//++ ++ ++--++ ++//++ ++//++--++ ++--++--++--++--+
+ ++ ++--+
5 | || || || || |/ || || || ||
|| || |
+ ++ ++ ++ ++--++//++--++--++ ++//++//++ ++--++--++--++--++ +
+ ++..++--+
+ ++ ++ ++ ++--++//++//++--++ ++//++//++ ++--++--++--++--++ +
+ ++..++--+
4 | || || || || || |/ || || || ||
|| || |
+ ++ ++ ++//++ ++--++ ++--++--++ ++ ++--++--++--++--++ ++--+
+--++ ++--+
+ ++ ++ ++//++ ++--++ ++//++--++ ++ ++--++--++--++--++ ++--+
+//++ ++--+
3 | || || || // /| // || ||
|| // :: |
+ ++ ++..++--++--++--++--++--++--++..++--++--++--++--++ ++ ++ +
+//++--++ +
+ ++ ++..++--++--++--++--++--++--++..++..++--++--++--++ ++ ++ +
+//++--++ +
2 | || || || || || :: || // || ||
|| || |
+ ++//++ ++ ++ ++ ++ ++ ++ ++--++ ++--++ ++--++..++ ++ +
+ ++--++--+
+ ++//++ ++ ++ ++ ++ ++ ++ ++--++ ++--++ ++--++..++ ++ +
+ ++--++--+
1 | || // // || || /| || || || ||
|| |
+..++ ++ ++ ++ ++--++//++--++ ++ ++ ++//++//++ ++ ++ ++..+
+//++--++--+
+..++ ++ ++ ++ ++--++//++--++ ++ ++ ++//++//++ ++ ++ ++..+
+//++--++--+
0 | || || || // || |/ ||
|| |
+--++--++--++--++--++--++--++--++--++--++--++--++--++--++--++--++--+
+--++--++--+
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16 17 18 19


LEVEL: 3

+--++--++ ++--++--++--++--++--++ ++ ++ ++--++ ++ ++ ++ ++--+
+--++--++--+
19 | || || || || // || ||
|| || |
+ ++ ++ ++ ++--++--++ ++ ++ ++--++--++ ++--++--++ ++--++--+
+ ++ ++ +
+ ++ ++ ++ ++--++--++ ++ ++ ++--++--++ ++--++--++ ++--++--+
+ ++ ++ +
18 | // || || || /| ||
|| // |
+--++//++--++--++--++--++--++--++--++--++--++--++--++--++--++--++--+
+--++//++--+
+--++//++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++--+
+--++//++--+
17 || ||
|| ||
+//++ ++--++--++--++--++--++--++--++--++--++--++--++--++--++--++ +
+ ++ ++--+
+//++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +
+ ++ ++--+
16 | // || ||
|| || || |
+//++--++--++--++--++--++--++--++--++--++--++--++--++--++--++--++ +
+--++--++ +
+//++--++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +
+--++--++ +
15 || || |
| |
+--++ ++--++--++--++--++--++--++--++--++--++--++--++--++--++ ++ +
+ ++--++--+
+--++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +
+ ++--++--+
14 || | |
| |
+--++--++--++--++--++--++--++--++--++--++--++--++--++--++ ++ ++ +
+ ++ ++--+
+--++--++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +
+ ++ ++--+
13 || | | |
| |
+ ++ ++--++--++--++--++--++--++--++--++--++--++--++ ++ ++ ++ +
+ ++--++--+
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +
+ ++--++--+
12 | || || | | | |
| | |
+ ++--++--++--++--++--++--++--++--++--++--++--++ ++ ++ ++ ++ +
+ ++ ++ +
+ ++--++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +
+ ++ ++ +
11 || | | | | |
| | ||
+--++ ++--++--++--++--++--++--++--++--++--++ ++ ++ ++ ++ ++ +
+ ++//++--+
+--++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +
+ ++//++--+
10 || || | | | | | |
| | ||
+ ++ ++--++--++--++--++--++--++--++--++ ++ ++ ++ ++ ++ ++ +
+ ++//++ +
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +
+ ++//++ +
9 // || | | | | | | |
| | ||
+--++ ++--++--++--++--++--++--++--++ ++ ++ ++ ++ ++ ++ ++ +
+ ++ ++--+
+--++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +
+ ++ ++--+
8 /| | | | | | | | |
| |
+ ++--++--++--++--++--++--++--++ ++ ++ ++ ++ ++ ++ ++ ++ +
+ ++ ++--+
+ ++--++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +
+ ++ ++--+
7 | || || | | | | | | | | |
| | |
+--++ ++--++--++--++--++--++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +
+ ++ ++--+
+--++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +
+ ++ ++--+
6 | || | | | | | | | | | |
| | |
+ ++--++--++--++--++--++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +
+ ++--++ +
+ ++--++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +
+ ++--++ +
5 | || | | | | | | | | | | |
| | |
+--++ ++--++--++--++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +
+ ++--++//+
+--++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +
+ ++//++//+
4 | || | | | | | | | | | | | |
| | || |
+ ++--++--++--++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +
+ ++ ++--+
+ ++--++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +
+ ++ ++--+
3 || | | | | | | | | | | | | |
| |
+--++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +
+ ++ ++--+
+--++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +
+ ++ ++--+
2 /| | | | | | | | | | | | | |
| | ||
+--++--++--++--++--++--++--++--++--++--++--++--++--++--++--++--++--+
+--++//++--+
+--++//++//++--++--++--++--++--++//++--++--++--++--++--++--++--++--+
+--++//++--+
1 | || /| || // |/ || ||
|| // |
+ ++--++ ++ ++--++ ++ ++--++ ++--++--++ ++ ++ ++--++--++ +
+ ++ ++ +
+ ++--++ ++ ++--++ ++ ++--++ ++--++--++ ++ ++ ++--++--++ +
+ ++ ++ +
0 | || // || || || || || ||
|| || |
+--++--++ ++--++--++--++--++--++ ++ ++ ++--++ ++ ++ ++ ++--+
+--++--++--+
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16 17 18 19


LEVEL: 4

+ ++--++--++--++--++ ++--++--++--++--++ ++--++--++--++--++ ++--+
+//++//++--+
19 | || || || || || || ||
|| // // |
+ ++ ++--++--++ ++ ++ ++--++--++ ++ ++ ++--++--++ ++ ++//+
+--++--++ +
+ ++ ++--++--++ ++ ++ ++--++--++ ++ ++ ++--++--++ ++ ++//+
+--++--++ +
18 | || || // || || || || || || || || // || || ||
|| // |
+ ++ ++//++//++ ++ ++ ++ ++ ++ ++ ++ ++//++//++ ++ ++ +
+//++--++//+
+ ++ ++//++//++ ++ ++ ++ ++ ++ ++ ++ ++//++//++ ++ ++ +
+//++--++//+
17 | || || || || || || || || || || || || || || ||
|| || |
+ ++ ++//++--++ ++ ++ ++//++--++ ++ ++ ++--++//++ ++ ++ +
+//++ ++ +
+ ++ ++//++--++ ++ ++ ++//++--++ ++ ++ ++--++//++ ++ ++ +
+//++ ++ +
16 | :: :: :: :: :: :: ||
|| || // |
+ ++--++--++--++--++ ++--++--++--++--++ ++--++--++--++--++ ++ +
+ ++--++--+
+ ++--++--++--++--++ ++--++--++--++--++ ++--++--++--++--++ ++ +
+ ++--++--+
15 | ||
|| || |
+--++--++--++--++--++--++..++--++--++--++--++--++--++--++--++ ++ +
+ ++//++ +
+--++--++--++--++--++//++..++--++--++--++--++--++--++--++--++ ++ +
+ ++//++ +
14 | // // // || || || // || // || ||
|| || |
+//++--++--++//++//++--++ ++//++ ++ ++--++ ++--++--++ ++ ++ +
+--++--++--+
+//++--++--++//++//++--++ ++//++ ++ ++--++ ++--++--++ ++ ++ +
+--++--++--+
13 || || || || || || || // || ||
||
+--++--++ ++//++ ++ ++ ++ ++--++--++--++--++ ++ ++//++--++--+
+--++--++--+
+--++--++ ++//++ ++ ++ ++ ++--++--++--++--++ ++ ++//++--++--+
+--++--++--+
12 | || || || // || || //
|| :: :| // |
+ ++//++ ++//++--++//++ ++ ++--++ ++ ++//++--++--++--++ ++--+
+ ++//++--+
+ ++//++ ++//++--++//++ ++ ++--++ ++ ++//++--++--++--++ ++--+
+ ++//++--+
11 | || || || // // || || || || || // // ||
|| || |
+//++ ++..++//++//++//++ ++--++//++ ++--++ ++--++ ++//++--++//+
+//++--++//+
+//++ ++..++--++//++--++ ++--++//++ ++--++ ++--++ ++//++--++//+
+//++--++//+
10 | || || || || || || || || || || ||
|| || |
+ ++--++--++ ++ ++ ++ ++ ++--++--++--++--++--++--++ ++//++--+
+ ++ ++--+
+ ++--++--++ ++ ++ ++ ++ ++--++--++--++--++--++--++ ++//++--+
+ ++ ++--+
9 | // || || || || || || // || ||
|| |
+..++--++--++--++ ++ ++ ++ ++ ++//++ ++--++ ++--++ ++ ++ +
+ ++ ++ +
+..++--++--++--++ ++ ++ ++ ++ ++//++ ++--++ ++--++ ++ ++ +
+ ++ ++ +
8 | :: || || || // || || // ||
|| |
+ ++--++--++ ++--++ ++ ++--++--++--++--++--++--++--++--++ ++--+
+ ++ ++--+
+ ++--++--++ ++--++ ++ ++--++--++--++--++--++--++--++--++ ++--+
+ ++ ++--+
7 | || || || || ||
|| // // |
+..++--++ ++..++ ++ ++ ++--++//++--++//++//++--++//++--++ ++//+
+--++..++//+
+..++--++ ++..++ ++ ++ ++--++//++--++//++//++--++//++--++ ++//+
+--++..++//+
6 | || :: || || || || || || || || ||
|| // // |
+ ++..++--++ ++ ++--++ ++ ++--++ ++--++--++ ++ ++//++ ++--+
+ ++ ++--+
+ ++..++--++ ++ ++--++ ++ ++--++ ++--++--++ ++ ++//++ ++--+
+ ++ ++--+
5 | :: || || || || :: || || || ||
|| |
+--++--++--++--++--++--++ ++--++--++--++--++ ++--++//++ ++ ++--+
+ ++ ++--+
+--++--++--++--++--++--++ ++--++--++--++--++ ++--++//++ ++ ++--+
+ ++ ++--+
4 | :: || || ||
|| // // |
+ ++//++--++--++ ++--++--++--++--++--++--++--++--++ ++--++ ++//+
+--++--++//+
+ ++//++--++--++ ++--++--++--++--++--++--++--++--++ ++--++ ++//+
+--++--++//+
3 | || || || || // // // ||
|| || |
+ ++ ++--++ ++--++ ++--++//++//++//++--++ ++--++ ++--++ ++ +
+ ++ ++ +
+ ++ ++--++ ++--++ ++--++//++//++//++--++ ++--++ ++--++ ++ +
+ ++ ++ +
2 | || || :: :: // || || || || || || || || ||
|| // // |
+--++ ++--++--++ ++ ++ ++--++ ++ ++ ++--++ ++//++ ++ ++--+
+//++//++--+
+//++ ++--++--++ ++ ++ ++--++ ++ ++ ++--++ ++//++ ++ ++--+
+//++//++--+
1 | || || :: || || || || || // || || ||
|| || |
+ ++//++--++--++ ++--++ ++--++--++ ++--++ ++--++//++--++ ++ +
+//++//++ +
+ ++//++--++--++ ++//++ ++--++--++ ++//++ ++--++//++--++ ++ +
+//++//++ +
0 | || || :: || || || || || // ||
|| || |
+ ++--++--++--++--++ ++--++--++--++--++ ++--++--++--++--++ ++--+
+//++//++--+
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16 17 18 19


LEVEL: 5

+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++--++//++//++//++//++//+
+//++--++ +
19 || || || || || ||
|| // ||
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++//++--++//++--++//++--+
+--++ ++ +
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++//++--++//++--++//++--+
+--++ ++ +
18 || || ||
|| ||
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++--++--++ ++ ++ +
+--++--++ +
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++--++--++ ++ ++ +
+--++--++ +
17 || || ||
|| // ||
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++--++ ++//++ ++ +
+ ++ ++ +
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++--++ ++//++ ++ +
+ ++ ++ +
16 || || || ||
|| ||
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++--++//++--++ ++ +
+ ++--++ +
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++--++//++--++ ++ +
+ ++--++ +
15 || || ||
|| // ||
+--++--++--++--++--++--++//++--++--++--++--++ ++//++--++--++--++//+
+--++--++--+
+--++--++--++--++--++--++//++--++--++--++--++ ++//++--++--++--++//+
+--++--++--+
14 / /| // // // // || ||
|| // // || /
+//++//++//++--++--++--++ ++--++--++ ++--++--++ ++ ++ ++ ++ +
+--++//++//+
+//++//++//++--++--++--++ ++--++--++ ++--++--++ ++ ++ ++ ++ +
+--++//++//+
13 | /| || // // // || || //
|| // // || |
+--++//++//++--++--++--++ ++--++--++ ++--++--++--++--++--++--++--+
+--++--++//+
+--++//++//++--++--++--++ ++--++--++ ++--++--++--++--++--++--++--+
+--++--++//+
12 / // // // // // || || || || |/ |/ ||
|| || /
+--++--++--++--++--++ ++//++//++ ++--++ ++ ++--++ ++ ++ ++ +
+ ++ ++--+
+--++--++--++--++--++ ++//++//++ ++--++ ++ ++--++ ++ ++ ++ +
+ ++ ++--+
11 || || || || // || || || ||
|| ||
+ ++--++--++//++ ++ ++ ++ ++ ++--++//++--++ ++--++--++--++//+
+//++--++ +
+ ++--++--++//++ ++ ++ ++ ++ ++--++--++--++ ++--++//++--++//+
+//++--++ +
10 // // || || || || || || // |/ // ||
|| ||
+--++--++//++//++--++ ++//++//++ ++--++--++--++ ++--++--++ ++--+
+--++--++--+
+--++--++//++//++--++ ++//++//++ ++--++--++--++ ++--++//++ ++--+
+--++--++--+
9 / // || // || || || // // || ||
|| :: /
+--++--++//++//++//++ ++ ++ ++--++--++ ++ ++--++ ++ ++--++ +
+--++--++--+
+--++--++//++//++--++ ++ ++ ++--++--++ ++ ++--++ ++ ++--++ +
+--++--++--+
8 / // // || || || || || // || /| /|
|| || // /
+--++--++--++--++//++//++//++//++ ++ ++--++--++--++--++--++--++ +
+--++--++//+
+--++--++--++--++--++//++//++//++ ++ ++--++--++--++--++--++--++ +
+--++--++//+
7 / // // // |/ || /| || ||
|| || /
+--++--++--++--++--++--++--++--++--++--++--++ ++--++//++--++ ++--+
+ ++ ++--+
+--++--++--++--++--++--++--++--++--++--++--++ ++--++//++--++ ++--+
+ ++ ++--+
6 || ||
|| // ||
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++--++--++--++ ++ ++ +
+//++//++ +
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++--++--++--++ ++ ++ +
+//++//++ +
5 || // ||
|| || ||
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++//++//++//++--++--++ +
+ ++--++ +
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++//++//++//++--++--++ +
+ ++--++ +
4 || || || ||
|| // ||
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++--++ ++ ++ ++//+
+ ++--++ +
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++--++ ++ ++ ++//+
+ ++--++ +
3 || || // //
|| || ||
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++--++//++ ++--++--++ +
+ ++ ++ +
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++--++//++ ++--++--++ +
+ ++ ++ +
2 || || // //
|| ||
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++--++--++ ++ ++--+
+--++ ++ +
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++--++--++ ++ ++--+
+--++ ++ +
1 || || || || ||
|| || ||
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++--++//++//++//++//++//+
+//++--++ +
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++--++//++//++//++//++//+
+//++--++ +
0
|| // :/ ||
+ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++--++//++//++//++//++//+
+//++--++ +
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16 17 18 19


LEVEL: 6

+--++--++--++ ++ ++--++--++--++--++--++--++--++--++--++--++ ++ +
+--++--++..+
19 | || || |/ || || || // ||
|| |
+ ++ ++ ++--++ ++//++ ++ ++ ++--++--++ ++ ++--++ ++ ++ +
+ ++ ++ +
+ ++ ++ ++--++ ++--++ ++ ++ ++--++--++ ++ ++--++ ++ ++ +
+ ++ ++ +
18 | || || || || || || ||
|| |
+ ++ ++ ++ ++--++ ++ ++ ++--++ ++ ++--++--++ ++ ++ ++ +
+--++--++ +
+ ++ ++ ++ ++--++ ++ ++ ++--++ ++ ++--++--++ ++ ++ ++ +
+//++--++ +
17 | || || || // || ||
|| |/ |
+--++--++--++--++--++--++..++--++ ++--++--++ ++ ++ ++ ++//++ +
+--++--++--+
+--++--++--++--++--++--++..++--++ ++--++--++ ++ ++ ++ ++//++ +
+--++--++--+
16 | || ||
|| // // |
+ ++--++ ++--++ ++--++ ++ ++ ++ ++ ++ ++ ++ ++ ++//++ +
+ ++ ++ +
+ ++--++ ++--++ ++--++ ++ ++ ++ ++ ++ ++ ++ ++ ++//++ +
+ ++ ++ +
15 | || || ||
|| // |
+--++ ++ ++ ++ ++--++ ++ ++ ++--++--++--++--++ ++ ++ ++ +
+--++--++--+
+--++ ++ ++ ++ ++--++ ++ ++ ++--++--++--++--++ ++ ++ ++ +
+--++--++--+
14 | || || || || //
|| |
+ ++--++ ++--++--++ ++--++--++--++ ++--++--++--++--++--++--++--+
+ ++ ++//+
+ ++--++ ++--++--++ ++--++--++--++ ++--++--++--++--++--++--++--+
+ ++ ++//+
13 | || || || || //
|| || || |
+ ++ ++--++--++--++--++ ++--++ ++--++ ++--++--++--++ ++--++ +
+--++ ++--+
+ ++ ++--++--++--++--++ ++--++ ++--++ ++--++--++--++ ++--++ +
+--++ ++--+
12 || || || ||
|| || ||
+--++--++ ++--++--++ ++--++--++ ++--++--++ ++ ++ ++ ++--++--+
+--++ ++ +
+--++--++ ++--++--++ ++--++--++ ++--++--++ ++ ++ ++ ++--++--+
+--++ ++ +
11 || // || || ||
|| || ||
+ ++ ++ ++--++ ++--++--++ ++--++ ++ ++ ++ ++ ++--++--++ +
+ ++ ++--+
+ ++ ++ ++--++ ++--++--++ ++--++ ++ ++ ++ ++ ++--++--++ +
+ ++ ++--+
10 || || || || || || || || ||
|| ||
+ ++--++--++ ++--++--++ ++ ++--++ ++ ++--++--++--++ ++ ++ +
+--++--++ +
+ ++--++--++ ++--++--++ ++ ++--++ ++ ++--++--++--++ ++ ++ +
+--++--++ +
9 | || // || || || || || ||
|| || |
+ ++--++ ++ ++--++ ++--++ ++ ++--++ ++ ++--++--++--++ ++--+
+ ++ ++ +
+ ++--++ ++ ++--++ ++--++ ++ ++--++ ++ ++--++--++--++ ++--+
+ ++ ++ +
8 | || || || || || || ||
|| || |
+ ++--++--++--++--++--++ ++--++--++--++ ++--++--++ ++ ++--++ +
+ ++--++--+
+ ++--++--++--++--++--++ ++--++--++--++ ++--++--++ ++ ++--++ +
+ ++--++--+
7 || ||
|| ||
+ ++--++--++--++--++--++--++ ++--++--++ ++--++ ++--++--++ ++--+
+ ++ ++--+
+ ++--++--++--++--++--++--++ ++--++--++ ++--++ ++--++--++ ++--+
+ ++ ++--+
6 | || || || :: || || || // //
|| || |
+ ++ ++--++ ++//++ ++ ++--++--++--++--++ ++--++--++--++ ++--+
+--++ ++--+
+ ++ ++--++ ++--++ ++ ++--++--++--++//++ ++--++--++--++ ++--+
+--++ ++--+
5 || || || || || || ||
|| || ||
+--++ ++ ++--++ ++ ++ ++ ++ ++ ++ ++--++--++--++ ++ ++--+
+ ++ ++ +
+--++ ++ ++--++ ++ ++ ++ ++ ++ ++ ++--++--++--++ ++ ++--+
+ ++ ++ +
4 || || /| || || || || :: ||
|| || ||
+--++--++--++ ++--++--++--++ ++--++--++--++//++ ++ ++ ++--++ +
+--++ ++--+
+--++--++--++ ++--++--++--++ ++--++--++//++--++ ++ ++ ++--++ +
+--++ ++--+
3 || || || // /| || || ||
|| ||
+--++--++ ++ ++ ++ ++ ++--++--++ ++ ++ ++--++--++--++ ++--+
+ ++--++--+
+--++--++ ++ ++ ++ ++ ++--++--++ ++ ++ ++--++//++--++ ++--+
+ ++--++--+
2 || || // || ||
|| ||
+ ++ ++--++ ++--++ ++--++--++--++//++//++--++--++--++ ++--++--+
+--++ ++--+
+ ++ ++--++ ++--++ ++--++--++--++//++//++--++--++--++ ++--++--+
+--++ ++--+
1 | || || || || || || || || ||
|| // || |
+ ++--++ ++--++//++--++ ++ ++ ++ ++ ++ ++ ++ ++--++--++ +
+--++--++ +
+ ++--++ ++--++//++--++ ++ ++ ++ ++ ++ ++ ++ ++--++--++ +
+--++--++ +
0 | || || // // || // // ||
|| |
+--++--++--++ ++ ++--++--++--++--++--++--++--++--++--++--++ ++ +
+--++--++..+
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16 17 18 19


--Tommy

TommyGoog

unread,
May 1, 2012, 8:55:34 AM5/1/12
to
In this post I will describe the software defects ("bugs") I've
discovered in the code. These are not bugs that I've introduced into
the code, but bugs that the original authors wrote.

For example, if the original code misspelled a word in a message (such
as "INDENTIFY"), then the code that I re-engineered also had to
misspell the word in the source code so that it would match the
original code. As another example, if the original code used the
wrong subscript in an array, then the code that I wrote had to also
use the wrong subscript in the array.


Bug #1
------
During the transfer of characters to LOL, the message states that the
characters must be "IN CASTLE" and "LIVE". The code however, only
checks "STATUS=OK":

690 7 24:D 1 PROCEDURE REMOVCHR; (* P050118 *)

717 7 24:4 126 WRITELN( 'NOTE - CHARS MUST BE LIVE & IN
CASTLE');
706 7 24:4 72 IF PLAYREC2.STATUS = OK THEN


Bug #2
------
During the transfer of characters to Legacy of LLylgamyn, it is
possible to get caught in an infinite loop.

The following code tries to write a new value to BL5BUFF[ 12] on the
FROM disk. If the disk is write protected (as the instructions in the
manual suggest you do), then the value on the diskette is now
different from the value in BL5BUFF[ 12] and FRMRNDID (From Disk
Random ID).

674 7 23:D 1 PROCEDURE TRANSFER; (* P050117 *)

788 7 23:1 70 RDSCNTOC( 'INSERT SCENARIO WITH CHAR(S)
TO BE MOVED');
789 7 23:1 115 UNITREAD( DRIVE1, BL5BUFF,
SIZEOF( BL5BUFF), SERIALBL, 0);
790 7 23:1 127 MPD3 := BL5BUFF[ 10];
791 7 23:1 135 MOVELEFT( BL5BUFF, SERIALFR, 7);
792 7 23:1 146 BL5BUFF[ 12] := CHR( (ORD( BL5BUFF[ 12])
+ RANDNUM) MOD 256);
793 7 23:1 162 UNITWRITE( DRIVE1, BL5BUFF,
SIZEOF( BL5BUFF), SERIALBL, 0);
794 7 23:1 174 FRMRNDID := BL5BUFF[ 12];
795 7 23:1 182 REMOVCHR;

The following code tries to write the status of "LOST" into the
players record we are transferring FROM. The I/O of course will
fail, but no checking is performed. That is actually a good thing,
but...

690 7 24:D 1 PROCEDURE REMOVCHR; (* P050118 *)

738 7 24:1 406 PLAYREC1 := PLAYREC2;
739 7 24:1 414 PLAYREC1.STATUS := LOST;
740 7 24:1 418 WRCHARAC( PLAYREC1, PLAYINDX);
741 7 24:1 427 WRICACHE

...if the TO disk is full, or the player name already exists on the TO
disk, then...

674 7 23:D 1 PROCEDURE TRANSFER; (* P050117 *)

809 7 23:5 334 IF (PLAYREC1.NAME =
PLAYREC2.NAME) AND
810 7 23:5 340 (PLAYREC1.STATUS <> LOST)
THEN
811 7 23:6 347 PLAYINDX := -1

818 7 23:1 385 IF (PLAYINDX = - 1) OR
819 7 23:1 391 (PLAYINDX = SCNTOC.RECPERDK[ ZCHAR])
OR
820 7 23:1 404 (COPY( SCNTOC.GAMENAME, 1, 10) <>
'THE LEGACY') THEN
821 7 23:2 437 TRANBAD

...the following code now tries to put the character back onto the
FROM disk, but since FRMRNDID was never written to the disk, we are
stuck in the following REPEAT-UNTIL loop:

745 7 25:D 1 PROCEDURE TRANBAD; (* P050119 *)

749 7 25:2 0 REPEAT
750 7 25:3 0 IF PLAYINDX = -1 THEN
751 7 25:4 9 RDSCNTOC( 'DUPLICATE NAME - PUT
IN SOURCE')
759 7 25:2 190 UNTIL (SCNTOC.GAMENAME = FRMGAMNM)
AND (FRMRNDID = BL5BUFF[ 12]);


Bug #3
------
A wrong message is displayed when "M"aking a Scenario Disk.

Here is the code at the beginning of MAKESCEN:

868 8 1:D 1 SEGMENT PROCEDURE MAKESCEN; (* P050201 *)

1119 8 1:1 0 REPEAT
1120 8 1:2 0 WRITE( CHR( HOMECLR));
1121 8 1:2 8 WRITE( 'DO YOU HAVE 1) OR 2) DRIVES ?');
1122 8 1:2 47 READ( INCHAR);
1123 8 1:1 55 UNTIL (INCHAR = '1') OR (INCHAR = '2');
1124 8 1:1 64 DRIVECNT := ORD( INCHAR) - ORD( '0');

The following code is testing the wrong variable, BASE04, instead of
DRIVECNT. As it turns out, BASE04 is always 1 here:

1129 8 1:1 117 IF BASE04 = 1 THEN
1130 8 1:2 122 BEGIN
1131 8 1:3 122 GOTOXY( 0, 3);
1132 8 1:3 127 WRITELN( 'INSERT AND REMOVE DISKS WHEN
PROMPTED.');
1133 8 1:3 181 WRITELN;
1134 8 1:3 187 WRITELN( 'REMEMBER : "BLANK" = YOUR
BLANK DISK.');
1135 8 1:3 241 WRITELN( ' "MASTER" = MASTER
SCENARIO.');
1136 8 1:2 295 END;


Bug #4
------
While "C"hanging a name of a character, a different error message is
displayed than the one you might expect.

The code in GETNAME() passes by reference a string defined as 40
characters into a routine that defines the parameter as 80
characters. The called routine updates this string and can therefore
overwrite (or clobber) a variable that follows it.

PASSWD, TONAME, and FROMNAME are all 40 characters in length:

441 7 17:D 1 PROCEDURE CHGNAMES; (* P050111 *)

444 7 17:D 1 PASSWD : STRING[ 40];
445 7 17:D 22 TONAME : STRING[ 40];
446 7 17:D 43 FROMNAME : STRING[ 40];

CHARNAME is the default value of 80 characters in length:

453 7 18:D 1 PROCEDURE GETNAME( VAR CHARNAME : STRING;
(* P050112 *)

GETNAME passes TONAME by reference (a 40 character name):

488 7 17:2 185 GETNAME( FROMNAME, 10, 'FROM NAME >');
489 7 17:2 204 GETNAME( TONAME, 12, ' TO NAME >');

To demonstrate the bug:
1. Start the game and select "U"tilities and "C"hange Name
2. Enter a "From>" name that is valid
3. Enter a "To>" name that is > 42 characters (will be off the
screen to the right)
4. Enter a valid "To>" name.
5. Result is: NAME NOT FOUND

That is a different error than if you type a "To>" name < 40
characters.

Note: This is at least one reason why the code uses the "$R-"
compiler option, otherwise it would not even compile.


Bug #5
------
SERIAL is defined as "STRING[7]". Sometimes it is referenced in the
code as 7 bytes, and sometimes as 8 bytes:

139 1 1:D 6 SERIAL : STRING[ 7];

1143 9 1:D 1 SEGMENT PROCEDURE OPTIONS; (* P050301 *)
1165 9 1:1 466 MOVELEFT( IOBUFF, SERIAL, 8);

1187 1 3:D 3 FUNCTION CHKCOPY : BOOLEAN; (* P050003 *)
1194 1 3:1 11 MOVELEFT( BUFFER, SERIAL, 7);


Bug #6
------
Variable RANDNUM is referenced on the right side of an assignment
before it has been initialized. (Probably not really a big deal?)

When the following code is first executed, RANDNUM has not been
assigned a value yet:

227 7 4:D 1 PROCEDURE GETKEY; (* P050104 *)

233 7 4:1 21 RANDNUM := (RANDNUM + ORD( INCHAR)) MOD
1027


Bug #7
------
IORESULT is not properly used.

From the documentation for IORESULT:

"Note that IORESULT only gives a valid return the first time it is
referenced after an I/O operation. If it is referenced again (without
another I/O operation), it will always return 0."

If there was an IO error to begin with, then FINDFILE is always set to
0 here:

237 7 5:D 3 FUNCTION FINDFILE( DRIVE :
INTEGER; (* P050105 *)

248 7 5:1 17 IF IORESULT <> 0 THEN
249 7 5:2 23 FINDFILE := -ABS( IORESULT)

Note, FINDFILE in MAKESCEN has the identical bug.


Bug #8
------
During "Create Duplicate Scenario", the last block is not copied.

There are 280 blocks on a Pascal disk, numbered from 0 to 279.

979 8 8:D 1 PROCEDURE COPYSCEN; (* P050208 *)

1099 8 8:1 32 WHILE DSKBLKXX < 279 DO


Bug #9
------
The word "OR" shows up consecutively in a message:

690 7 24:D 1 PROCEDURE REMOVCHR; (* P050118 *)

719 7 24:4 185 WRITELN( 'ENTER LETTER OF CHAR TO MOVE,
OR');
720 7 24:4 233 WRITE( 'OR PRESS [RET] TO EXIT');


Bug #10
-------
Two variables are uninitialized when first used (XGOTO and XGOTO2):

When SPECIALS is first called from the mainline the variables have not
been given values:

2787 9 1:D 1 SEGMENT PROCEDURE SPECIALS; (* P010301 *)

4002 9 1:0 0 BEGIN (* SPECIALS *)
4003 9 1:1 0 IF XGOTO = XINSAREA THEN
4004 9 1:2 7 INSPECT
4005 9 1:1 7 ELSE
4006 9 1:2 11 IF XGOTO = XCAMPSTF THEN
4007 9 1:3 18 DUMAPIC;
4008 9 1:1 20 XGOTO := XGOTO2;


Bug #11
-------
IDENTIF is not Set/Cleared when an item is gotten.

67 1 1:D 3 POSS : RECORD
68 1 1:D 3 POSSCNT : INTEGER;
69 1 1:D 3 POSSESS : ARRAY[ 1..8] OF
RECORD
70 1 1:D 3 EQUIPED : BOOLEAN;
71 1 1:D 3 CURSED : BOOLEAN;
72 1 1:D 3 IDENTIF : BOOLEAN;
73 1 1:D 3 EQINDEX : INTEGER;
74 1 1:D 3 END;
75 1 1:D 3 END;


3374 9 25:D 3 FUNCTION GOTITEM( CHARX: INTEGER; (*
P010319 *)

3392 9 25:3 112 POSSX := POSS.POSSCNT + 1;
3393 9 25:3 119 POSS.POSSCNT := POSSX;
3394 9 25:3 124 POSS.POSSESS[ POSSX].EQINDEX :=
ITEMX;
3395 9 25:3 136 POSS.POSSESS[ POSSX].EQUIPED :=
FALSE;
3396 9 25:3 146 POSS.POSSESS[ POSSX].CURSED :=
FALSE

This would seem to be a strange method for "randomly" setting/clearing
this field.


Bug #12
-------
XGOTO2 is uninitialized variable when first tested in CASTLE.

7710 16 1:0 0 BEGIN (* CASTLE P010A01 *)

7728 16 1:1 97 IF XGOTO2 <> XBOLTAC THEN


Bug #13
-------
In DISP20, we execute a loop 21 times instead of 20.

When entering "*" to display all the characters on a disk, we go
through the loop one too many times. Depending on circumstances, I
suppose this could sometimes display a garbage name on the screen.

6993 16 10:D 1 PROCEDURE DISP20; (* P010A0A *)

7004 16 10:1 31 FOR CHARX := 0 TO
SCNTOC.RECPERDK[ ZCHAR] DO


Bug #14
-------
The code tries to display the message "** WRITE-PROTECT CHEAT!**", but
it will never be displayed.

Because of the nature of GETREC() and GETRECW(), IORESULT will never
be > 0 in the following code:

6970 16 8:D 1 PROCEDURE ADDPARTY; (* P010A08 *)

7029 16 8:0 0 BEGIN (* ADDPARTY *)

7051 16 8:1 173 MOVELEFT( IOCACHE[ GETREC( ZCHAR,
CHARI, SIZEOF( TCHAR))],
7052 16 8:1 186 CHARACTR[ PARTYCNT],
7053 16 8:1 192 SIZEOF( TCHAR));

7084 16 8:1 532 MOVELEFT( CHARACTR[ PARTYCNT],
7085 16 8:1 538 IOCACHE[ GETRECW( ZCHAR,
CHARI, SIZEOF( TCHAR))],
7086 16 8:1 551 SIZEOF( TCHAR));

7087 16 8:1 556 IF IORESULT > 0 THEN
7088 16 8:2 562 EXITADDP( '** WRITE-PROTECT CHEAT!
**');

The "character" record is read into the IOCACHE area. That record is
then updated. When the GETRECW() is executed at line 7085, this will
not try to do any I/O because that record is already in the IOCACHE
and the CACHEWRI flag has not yet been set.


Bug #15
-------
You are not allowed to "change class" if there is only 1 class to
"change to".

For example, let's say you are a FIGHTER with STRENGTH=11 and
PIETY=10. You can then lose 1 STRENGTH and gain 1 PIETY. You should
now be able to change to PRIEST, but instead NOCHANGE is called.

8246 17 21:D 1 PROCEDURE CHGCLASS; (* P010B15 *)

8272 17 21:1 47 IF ORD( CHG2LST[ FIGHTER]) +
8273 17 21:1 55 ORD( CHG2LST[ MAGE]) +
8274 17 21:1 64 ORD( CHG2LST[ PRIEST]) +
8275 17 21:1 73 ORD( CHG2LST[ THIEF]) +
8276 17 21:1 82 ORD( CHG2LST[ BISHOP]) +
8277 17 21:1 91 ORD( CHG2LST[ SAMURAI]) +
8278 17 21:1 100 ORD( CHG2LST[ LORD]) +
8279 17 21:1 109 ORD( CHG2LST[ NINJA]) < 2
THEN
8280 17 21:2 122 NOCHANGE;


Bug #16
-------
NECROLOGY ROD is defined to be the KANDI spell, but its spell hash
value is wrong (1885 vs. 1185).


8698 18 9:D 1 PROCEDURE CASTSPEL( SPELHASH: INTEGER);
(* P010C09 *)
8755 18 9:D 2 KANDI = 1185; (* 1885 *)

8978 18 9:0 0 BEGIN (* CASTSPEL *)
9013 18 9:4 244 ELSE IF SPELHASH = KANDI THEN
9014 18 9:6 255 DOKANDI

9065 18 20:D 1 PROCEDURE USEITEM; (* P010C14 *)

9113 18 20:1 244
CASTSPEL( SCNTOC.SPELLHSH[ THEITEM.SPELLPWR])


SPELLPWR = 42 for NECROLOGY ROD.
SPELLHSH[ 42] = 1885.

Spell 42 is KANDI, and is defined as 1185;
If you use the hash function on "KANDI" it should be 1185. The
SPELLHSH[42] value is a typo.
This makes the NECROLOGY ROD not a spell casting item.

8989 18 9:3 114 SPELHASH := LENGTH( SPELNAME);
8990 18 9:3 120 FOR SPELLI := 1 TO
LENGTH( SPELNAME) DO
8991 18 9:4 136 IF SPELHASH < 20000 THEN
8992 18 9:5 143 BEGIN
8993 18 9:6 143 HASHCALC :=
ORD( SPELNAME[ SPELLI]) - 64;
8994 18 9:6 152 SPELHASH := SPELHASH +
HASHCALC * HASHCALC * SPELLI
8995 18 9:5 158 END;


Bug #17
-------
The MILWA spell cancels the LOMILWA spell.

8698 18 9:D 1 PROCEDURE CASTSPEL( SPELHASH: INTEGER);
(* P010C09 *)

9005 18 9:2 205 ELSE IF SPELHASH = MILWA THEN
9006 18 9:4 216 BEGIN
9007 18 9:5 216 CHKSPCNT( 1, 25);
9008 18 9:5 220 DECPRIEST( 1);
9009 18 9:5 223 LIGHT := 30 + (RANDOM MOD 15)
9010 18 9:4 231 END


Bug #18
-------
When spells are FIZZLING, some spells decrement the "spell count", but
others do not. For exmaple:

8808 18 13:D 1 PROCEDURE DECPRIEST( PRIESTGR:
INTEGER); (* P010C0D *)
8809 18 13:D 2
8810 18 13:0 0 BEGIN
8811 18 13:1 0 IF NOT USEITEM THEN
8812 18 13:2 6
CHARACTR[ CAMPCHAR].PRIESTSP[ PRIESTGR] :=
8813 18 13:2 20
CHARACTR[ CAMPCHAR].PRIESTSP[ PRIESTGR] - 1;
8814 18 13:1 38 IF FIZZLES > 0 THEN
8815 18 13:2 45 EXITCAST( 'NO EFFECT')

8958 18 19:D 1 PROCEDURE DOMALOR; (* P010C13 *)
8959 18 19:D 1
8960 18 19:0 0 BEGIN
8961 18 19:1 0 IF NOT USEITEM THEN
8962 18 19:2 6 IF
(CHARACTR[ CAMPCHAR].MAGESP[ 7] = 0) OR
8963 18 19:2 23 (NOT
CHARACTR[ CAMPCHAR].SPELLSKN[ 19]) THEN
8964 18 19:3 41 EXITCAST( 'YOU CANT CAST IT');
8965 18 19:1 62 IF FIZZLES > 0 THEN
8966 18 19:2 69 EXITCAST( 'SPELL FAILS');
8967 18 19:1 85 IF NOT USEITEM THEN
8968 18 19:2 91
CHARACTR[ CAMPCHAR].MAGESP[ 7] :=
8969 18 19:2 105 CHARACTR[ CAMPCHAR].MAGESP[ 7]
- 1;


Bug #19
-------
In "FIGHTS" variable "DONE" is not initialized when first tested.

651 7 9:D 1 PROCEDURE NEWMAZE; (* P010109 *)
652 7 9:D 1
653 7 9:D 1 VAR
654 7 9:D 1 UNUSEDXX : INTEGER;
655 7 9:D 2 UNUSEDYY : INTEGER;
656 7 9:D 3 UNUDEDZZ : INTEGER;
657 7 9:D 4 DONE : BOOLEAN;

873 7 9:0 0 BEGIN (* NEWMAZE *)
891 7 9:1 54 FIGHTS;

761 7 10:2 63 IF NOT DONE THEN
762 7 10:3 69 FINDSPOT;


Bug #20
-------
Inconsistency in displaying a character's "level". Is it MAXLEVAC? Or
CHARLEV?

1614 7 41:D 1 PROCEDURE DSPPARTY; (* P010129 *)
1627 7 41:3 62 PRINTCHR( PARTYWIN, 'L');
1628 7 41:3 67 PRINTNUM( PARTYWIN,
CHARACTR[ PARTYXXX].MAXLEVAC, 3);

8477 18 1:D 1 SEGMENT PROCEDURE CAMP; (* P010C01 *)
8627 18 7:D 1 PROCEDURE DSPSTATS; (* P010C07 *)
8637 18 7:3 74 PRINTSTR( CAMPWIN, ' LEV');
8638 18 7:3 87 PRINTNUM( CAMPWIN, CHARLEV, 4);


Bug #21
-------
"YOU BOUGHT THE LAST ONE" can never be displayed.

OBJECT.BOLTACXX is tested before it has been decremented. It will
always be > 0 (or -1).

2068 8 17:1 130 IF OBJECT.BOLTACXX = 0 THEN
2069 8 17:2 137 AASTRAA( 'YOU BOUGHT THE LAST ONE')

2099 8 17:1 468 IF OBJECT.BOLTACXX > 0 THEN
2100 8 17:2 475 OBJECT.BOLTACXX := OBJECT.BOLTACXX - 1;


Bug #22
-------
"INDENTIFY" in message is mispelled.

2147 8 20:D 1 PROCEDURE SELLIDUN( ACTION: INTEGER); (*
P010214 *)
2311 8 20:4 74 PRINTSTR( BOLTCWIN, 'INDENTIFY');


Bug #23
-------
"CEMETERY" in message is mispelled.

2385 8 24:D 1 PROCEDURE CEMETARY; (* P010218 *)
2489 8 24:1 84 PRINTSTR( TEMPWIN, ' PRESS (RETURN) TO
LEAVE THE CEMETERY');


Bug #24
-------
FINDFILE reads 104 bytes (same as TCHAR Record), and must therefore
"find" the file in one of first 4 slots in directory.

I suspect their code has "SIZEOF( TCHAR)" instead of 104. But this is
reading DIRECTORY entries, not CHARACTER records.

2968 9 9:D 3 FUNCTION FINDFILE( DRIVE: INTEGER; (*
P010309 *)
2995 9 9:1 11 UNITREAD( DRIVE, DIR, 104, 2, 0);

The following is also "interesting", because we do not have "FILECNT"
entries in DIR at this point.

3001 9 9:3 37 FOR FILEX := 1 TO DIR[ 0].FILECNT
DO


Bug #25
-------
"HALITO" is wrong constant used instead of "SOPIC".

When determining the enemy spell to throw, line 4156 should have
"SOPIC" and not HALITO.

4134 11 6:D 1 PROCEDURE GETMAGSP( SPELLLEV: INTEGER); (*
P010506 *)

4148 11 6:1 50 1: IF TWOTHIRD THEN
4149 11 6:3 53 SPELLCAS := KATINO
4150 11 6:2 53 ELSE
4151 11 6:3 60 SPELLCAS := HALITO;
4152 11 6:3 67
4153 11 6:1 67 2: IF TWOTHIRD THEN
4154 11 6:3 70 SPELLCAS := DILTO
4155 11 6:2 70 ELSE
4156 11 6:3 77 SPELLCAS := HALITO;


Bug #26
-------
MYCHARX is set to -1, and is then used as an invalid subscipt in an
array.

Case entry "6" is for "TAKE BACK". Note MYCHARX is set to -1.
See line 5106, where MYCHARX is used as a subscript!?

4614 12 2:D 1 PROCEDURE CACTION; (* P010602 *)

5100 12 2:7 851 6: BEGIN
5101 12 2:9 851
BATTLERC[ 0].A.TEMP04[ MYCHARX].SPELLHSH := -1;
5102 12 2:9 868 MYCHARX := -1
5103 12 2:8 868 END;
5104 12 2:7 874 END;
5105 12 2:7 896
5106 12 2:6 896 UNTIL
BATTLERC[ 0].A.TEMP04[ MYCHARX].SPELLHSH <> -999


Bug #27
-------
Multiple "spell names" can be hashed to the same value.

This isn't really a "bug" per se, but some names of spells can be
entered by using different characters based on the "hash" algorithm.
I have a note from long ago that says "POGS" is equivalent to "DIOS".

4789 12 12:D 1 PROCEDURE GETSPELL; (* P01060C *)

4895 12 12:1 69 SPELLCST := SPELNAML;
4896 12 12:1 72 FOR SPELNAMI := 1 TO SPELNAML DO
4897 12 12:2 83 BEGIN
4898 12 12:3 83 SPELCHRA := ORD( SPELLNAM[ SPELNAMI])
- 64;
4899 12 12:3 91 SPELLCST := SPELLCST + (SPELCHRA *
SPELCHRA * SPELNAMI)
4900 12 12:2 97 END;


Bug #28
-------
INAUDCNT is not correctly updated.

The wrong variable is used in the subscripts (ALIVECNT) instead of X.

During the MELEE rounds, if you (or an enemy) is hit with MONTINO, it
is supposed to last a "few rounds". Since the wrong variable is used,
for starters the INAUDCNT is NOT decremented during the HEALHEAR
routine. In addition, there is likely a "random store" that occurs.

5265 12 26:D 1 PROCEDURE HEALHEAR; (* P01061A *)

5267 12 27:D 1 PROCEDURE DECINAUD( GROUPI: INTEGER; (*
P01061B *)
5268 12 27:D 2 ALIVECNT: INTEGER);
5269 12 27:D 3
5270 12 27:D 3 VAR
5271 12 27:D 3 X : INTEGER;
5272 12 27:D 4
5273 12 27:0 0 BEGIN
5274 12 27:1 0 FOR X := 0 TO ALIVECNT - 1 DO
5275 12 27:2 13 IF
BATTLERC[ GROUPI].A.TEMP04[ ALIVECNT].INAUDCNT > 0 THEN
5276 12 27:3 30
BATTLERC[ GROUPI].A.TEMP04[ ALIVECNT].INAUDCNT :=
5277 12 27:3 44
BATTLERC[ GROUPI].A.TEMP04[ ALIVECNT].INAUDCNT - 1
5278 12 27:0 57 END; (* DECINAUD *)


Bug #29
-------
There is a random store in HAMMAGIC code.

We go through the loop one too many times.

TEMP2 should take on values from 0 to (…ALIVECNT - 1).

5789 14 18:D 1 PROCEDURE HAMMAGIC; (* P010812 *)
5790 14 18:D 1
5791 14 18:0 0 BEGIN
5792 14 18:1 0 FOR TEMP1 := 1 TO 3 DO
5793 14 18:2 14 BEGIN
5794 14 18:3 14 FOR TEMP2 := 0 TO
BATTLERC[ TEMP1].A.ALIVECNT DO
5795 14 18:4 37
BATTLERC[ TEMP1].A.TEMP04[ TEMP2].DRAIN2LV := 1;
5796 14 18:3 67 BATTLERC[ TEMP1].B.UNAFFCT :=
0
5797 14 18:2 79 END
5798 14 18:0 81 END;

Note also that TEMP1 only goes from 1 to 3, even though there might be
4 enemy groups.


Bug #30
-------
SMAKANIT has random stores similar to above. ENEMYX should go from 0
to (…ALIVECNT - 1).

5965 14 25:D 1 PROCEDURE SMAKANIT; (* P010819 *)

6010 14 25:8 225 FOR ENEMYX := 0 TO
BATTLERC[ GROUPI].A.ALIVECNT DO
6011 14 25:9 243 BEGIN
6012 14 25:0 243 WITH
BATTLERC[ GROUPI].A.TEMP04[ ENEMYX] DO
6013 14 25:1 257 BEGIN
6014 14 25:2 257 HPLEFT := 0;
6015 14 25:2 262 STATUS := DEAD
6016 14 25:1 265 END
6017 14 25:9 267 END


Bug #31
-------
DOPRIEST has random stores.

The subscript should be "GROUPI" and not "BASE04":

6048 14 28:D 1 PROCEDURE DOPRIEST; (* P01081C *)

6084 14 28:1 264 IF SPELL = LATUMAPI THEN
6085 14 28:2 273 BEGIN
6086 14 28:3 273 FOR GROUPI := 1 TO 4 DO
6087 14 28:4 284
BATTLERC[ BASE04].A.IDENTIFI := TRUE;
6088 14 28:3 300 IDMONSTR := 32767
6089 14 28:2 300 END;


Bug #32
-------
A DOMAGE call to MODAC causes a random store.

DOMAGE passes the following to MODAC:
1. the group index
2. modify amount
3. first index
4. last alive index

The "first index" should be 0, and the "last alive index" should be (…
ALIVECNT - 1), but this code passes "1" and "ALIVECNT".

TEMP04 is normally indexed from 0 to 8. This accomodates up to 9
enemies in 1 group. Note also that the first enemy in this group
(because of the bug) is not affected, since his index is 0.

6141 14 29:D 1 PROCEDURE DOMAGE; (* P01081D *)

6167 14 29:1 194 IF SPELL = MAMORLIS THEN
6168 14 29:2 203 FOR GROUPI := 1 TO 4 DO
6169 14 29:3 214 MODAC( GROUPI, -3, 1,
BATTLERC[ GROUPI].A.ALIVECNT);

5616 14 7:D 1 PROCEDURE MODAC( GROUPI: INTEGER; (* P010807
*)
5617 14 7:D 2 ACMOD: INTEGER;
5618 14 7:D 3 CHARF: INTEGER;
5619 14 7:D 4 CHARL: INTEGER);
5620 14 7:D 5
5621 14 7:D 5 VAR
5622 14 7:D 5 X : INTEGER;
5623 14 7:D 6
5624 14 7:0 0 BEGIN
5625 14 7:1 0 FOR X := CHARF TO CHARL DO
5626 14 7:2 11
BATTLERC[ GROUPI].A.TEMP04[ X].ARMORCL :=
5627 14 7:2 25
BATTLERC[ GROUPI].A.TEMP04[ X].ARMORCL + ACMOD;
5628 14 7:0 48 END;


Bug #33
-------
TILTOWAIT does 10-150 damage.

The documentation for TILTOWAIT says it does 10-100 damage, but the
code gives 10-150 damage.

6190 14 29:1 440 IF SPELL = TILTOWAIT THEN
6191 14 29:2 449 IF BATG = 0 THEN
6192 14 29:3 456 FOR GROUPI := 1 TO 4 DO
6193 14 29:4 467 HITGROUP( GROUPI, 10, 15, 0)
6194 14 29:2 471 ELSE
6195 14 29:3 482 HITGROUP( 0, 10, 15, 0)


Bug #34
-------
A character's STATUS can be set outside the valid range of OK..LOST.

Casting the spell DI or KADORTO on a character with VITALITY = 3
changes its STATUS to "LOST + 1".

8884 18 17:D 1 PROCEDURE DIKADORT; (* P010C11 *)

8896 18 17:3 76 IF
CHARACTR[ HEALME].ATTRIB[ VITALITY] = 3 THEN
8897 18 17:4 94 CHARACTR[ HEALME].STATUS := LOST

8902 18 17:1 137 IF CHARACTR[ HEALME].STATUS = OK THEN
8903 18 17:2 150 EXITCAST( 'OK')
8904 18 17:1 155 ELSE
8905 18 17:2 159 BEGIN
8906 18 17:3 159 CHARACTR[ HEALME].STATUS :=
SUCC( CHARACTR[ HEALME].STATUS);


Bug #35
-------
In DOTRAPDM, it is possible for damage from a trap to actually help a
character.

Case 1 (POISON) shows that POISNAMT[ 1] is a field that can be
incremented beyond 1.

Case 2 (GAS) set POISNAMT[ 1] to exactly 1. Therefore, if it was
greater than 1 to start with, this actually helps the character.

9900 19 14:1 104 CASE TRAPTYPE OF
9901 19 14:1 109
9902 19 14:1 109 1: (* POISON *)
9903 19 14:1 109
9904 19 14:2 109
CHARACTR[ CHRXCHST].LOSTXYL.POISNAMT[ 1] :=
9905 19 14:2 123
CHARACTR[ CHRXCHST].LOSTXYL.POISNAMT[ 1] + 1;
9906 19 14:2 143
9907 19 14:1 143 2: (* GAS *)
9908 19 14:1 143
9909 19 14:2 143 FOR CHARX := 0 TO PARTYCNT - 1 DO
9910 19 14:3 156 IF (RANDOM MOD 20) <
CHARACTR[ CHARX].LUCKSKIL[ 3] THEN
9911 19 14:4 178
CHARACTR[ CHARX].LOSTXYL.POISNAMT[ 1] := 1;


Bug #36
-------
HPLEFT can be set to 0 without status being "Dead". I think this is a
bug.

11815 20 57:D 1 PROCEDURE PITOUCH; (* P010E39 *)

11832 20 57:5 126 IF CHARACTR[ CHARX].HPLEFT < 0 THEN
11833 20 57:6 137 BEGIN
11834 20 57:7 137 CHARACTR[ CHARX].HPLEFT := 0;
11835 20 57:7 146 CHARACTR[ CHARX].STATUS := DEAD
11836 20 57:6 153 END


--Tommy

Vladimir Ivanov

unread,
May 3, 2012, 4:15:58 PM5/3/12
to

On Tue, 1 May 2012, TommyGoog wrote:

> In this post I will describe the software defects ("bugs") I've
> discovered in the code. These are not bugs that I've introduced into
> the code, but bugs that the original authors wrote.

Tommy,

This level of reverse engineering is nothing but admirable.

Although I haven't played any Wizardry games, your posts are still
interesting and amazing. My humble advice is that you compile and sort all
this information in few PDFs and accompanying ZIPs, so they can be
properly archived. Perhaps one about UCSD Pascal bytecode and
decompilation techniques, one about reverse-engineered source, one about
the aforementioned "bugs", etc.

Perhaps you have the most in-depth knowledge of UCSD Pascal at current
time. :-)

Cheers,
-- Vlad

TommyGoog

unread,
May 25, 2012, 12:53:43 AM5/25/12
to

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

Hi Vlad,

Thank you for the compliment.

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


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

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

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

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

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

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

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

*** NOTHING ***

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

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

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

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


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


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

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

Here is the INTDV code:

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


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

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

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

diskaccessed = 1;

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

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

BYTE result = 0;

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

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

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

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

I found that a Disk_t object has the following field:

DWORD spinning;

And I also found the variable:

static BOOL floppymotoron = 0;

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

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

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

This is an existing bug in AppleWin!

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

]CALL-151

*800L

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


--Tommy

EricN

unread,
May 25, 2012, 10:54:13 PM5/25/12
to
Tommy, this really is amazing work and I want to second Vlad's request
that you publish and archive all that you've done. I have been
reading your posts in depth and hadn't initially planned on posting
any comments until you mentioned your discouragement. I'm sure there
are many more people out there like me who are enjoying the knowledge
that your are imparting and would love see more.

Thanks,
Eric

aiia...@gmail.com

unread,
May 25, 2012, 11:53:36 PM5/25/12
to
On Thursday, May 24, 2012 9:53:43 PM UTC-7, TommyGoog wrote:
> > Tommy,
> >
> > This level of reverse engineering is nothing but admirable.
> >
> >    -- Vlad
>
> Hi Vlad,
>
> Thank you for the compliment.
>
> I feel a bit discouraged and wonder to myself if I should
> spend the time writing about decompilation techniques. Is there
> really anyone that will seriously read about decompilation techniques
> in order to learn how to do them and then actually use those
> techniques themselves? Depending on the level of detail that I
> provide, it could fill a book, especially as it pertained to my effort
> with Wizardry III.


please post an article about decompile techniques. I think there is HUGE
interest, and would be invaluable to future researchers.

R

Michael J. Mahon

unread,
May 27, 2012, 2:47:23 AM5/27/12
to
I agree. The techniques have great value to many whose interest is
something other than Wizardry.

-michael

NadaNet 3.1 for Apple II parallel computing!
Home page: http://home.comcast.net/~mjmahon/

"The wastebasket is our most important design
tool--and it's seriously underused."

Geoff Body

unread,
May 27, 2012, 11:28:57 PM5/27/12
to
Tommy,
you have done a great job.
I enjoyed your posts and would like to see the techniques you used.

Geoff

TommyGoog

unread,
May 29, 2012, 10:43:33 PM5/29/12
to
Thank you Vlad, Eric, R, michael, and Geoff for the encouragement to
continue.

Starting with this post, I will begin to explain how to re-engineer
(or reverse engineer) Pascal P-code (bytecodes) into Pascal source
code. Compared to my previous posts, these will likely not be as
organized. I've had a difficult time trying to determine how much (or
little) to write. It is difficult to determine how much my audience
knows (or should know) and what they do not know (or need to learn).
The organization of information does not easily fit into an "A, B, C"
order of learning. I guess I'm saying that you need to know almost
everything in order to understand anything.

Since this is an Apple forum, and I've only worked with Apple Pascal
(UCSD Pascal 1.1), that is the basis for my writing. I've used an
AppleII+ and an Apple IIe and performed much of my work using an Apple
emulator (AppleWin) on a Windows XP computer. If you have a different
computer or different level of Pascal, you will need to adjust
accordingly.

I assume the reader knows everything about standard Pascal:

Variable types (BOOLEAN, INTEGER, REAL, CHAR)
Statements (Assignment, WHILE, REPEAT-UNTIL, FOR, IF-THEN, IF-THEN-
ELSE, CASE, GOTO)
Boolean expressions and operators
Procedures and Functions
Global variables and local variables
Scope of variables and procedures (lexical level)
Passing parameters by reference or by value
Scalar and subrange types
Structured types
Record types (including use of the WITH statement)
Arrays and Records (including variant records)
Pointer types
Dynamic memory allocation (NEW and DISPOSE)
File types (Textfiles, INPUT, OUTPUT)
Standard procedures (GET, PUT, RESET, NEW, DISPOSE, PACK, UNPACK,
ORD, CHR, etc.)
READ, READLN, WRITE, WRITELN

Since I am discussing Apple Pascal, I will also assume that you know
the differences between it and standard Pascal:

STRING variable type
INTERACTIVE file type
PACKED variable differences
LONG INTEGER
MARK and RELEASE (replacement for DISPOSE)
SEGMENT procedures and functions
UNITS
EXTERNAL procedures and functions (assembly language - 6502 code)

Built-in functions (LENGTH, POS, CONCAT, COPY, DELETE, INSERT, STR)

Input and Output functions (REWRITE, RESET, CLOSE, UNITREAD,
BLOCKREAD, etc.)

Miscellaneous functions (e.g., EXIT)

Byte-Oriented functions (SIZEOF, SCAN, MOVELEFT, FILLCHAR)

Pascal compiler options
(*$I+*)
(*$I-*)
(*$I filename *)
(*$L+*)
(*$R+*)
(*$S++*)
(*$V-*)

Libraries and UNITs (Regular and Intrinsic)

Chaining programs (SETCHAIN, SETCVAL, GETCVAL, SWAPON, SWAPOFF)

SYSTEM.SWAPDISK

I found "Apple Pascal, Operating System Reference Manual" to be
extremely valuable in understanding not just the Pascal OS, but the P-
Machine:

Appendix A: "Architecture of the P-Machine"
Appendix B: "Operation of the P-Machine"
Appendix C: "File Formats" (including CODE files).

The reference manual is very technically written, and in my opinion
not very well, but it does contain almost 100% of the information
needed to decompile an Apple Pascal program. The appendices clearly
formed the basis for a Pascal P-Code decompiler (disassembler?) that I
wrote using Applesoft Basic. Its current name is WIZ4 that you might
read about later.

Note: "Decompiler" seems like the wrong term, since the output is not
Pascal code!?

If I had dozens of programs the size of Wizardry III that I wanted to
re-engineer, I would consider writing a real Pascal decompiler -- one
that would generate Pascal code directly from the P-code.

A Pascal P-code decompiler is the centerpiece in the decompiling
process.

I will make an assumption here that you understand the difference
between a compiler that generates native machine code (6502 code) and
a Pascal compiler that generates P-code.

Now, how well do you need to know the information in Appendices A, B,
C regarding the P-machine? If I do my job right in these posts, you
might not need to know more than the general concepts. Much of the
work for decompiling uses the text output from the decompiler. You
never need to know exactly what an individual P-code (bytecode) value
does as the decompiler shows it at a higher conceptual level.

The Pascal system on Wizardry III is the 48K runtime version (there
are others) that software developers distribute with their product.

A Pascal program can have:

Multiple files starting with STARTUP.CODE (turnkey system)
Chaining to other ".CODE" files.
Each file with at least one segment (Wizardry.CODE and WizUtil.CODE
contain many).
A segment containing other segments.
Each segment containing one or more procedures (or functions).
Each procedure with at least one Pascal statement.
Each Pascal statement consisting of 1 or more bytes.

Regarding the Apple computer, what do you need to know? Lots(?)

Some of my discussions will likely contain references to the AppleII
"monitor" program (hint: it has nothing to do with a computer screen
or terminal). You should know what "CALL -151", "800G", and "800L"
do. You should be able to read and understand assembly language
code. You should know the general way that the 6502 CPU operates. In
general you should know what a memory map displays.

Apple memory map:
Zero page
Stack page
$3F0-$3F4 BRK and Powerup bytes
Low-res memory map screen
High-res graphics page 1 memory
High-res graphics page 2 memory
Addresses from $C000-$C070 for hardware
PROM space for slot devices ($C100-$C7FF)
Monitor rom

Some of my side discussions (for example: copy protection schemes)
will likely require a lot of knowlege about internal disk formatting.
You might need to know about Track and Sector formatting and RWTS.
There are differences between Pascal "block" numbering and DOS T/S
numbering.

You need lots of TIME and lots of PATIENCE.

To whet your appetite for what is to come, I will show you an example
of decompiled P-code (from Wizardry III, file WizUtil.code, segment
UTILS, procedure 10) using my WIZ4 decompiler. The program solicits
for "Process all?", "FILE NUMBER", "SEGMENT NUMBER", "START ADDRESS",
and "PROCEDURE NUMBER". The rest of this listing is output from the
decompiler (WIZ4).


]PR#1
]REM LEGACY OF LLYLGAMYN BOOT DISK IN S6 D2

]RUN WIZ4
PROCESS ALL?N
FILES: 9

1 WIZARDRY.CODE
2 SYSTEM.CHARSET
3 SYSTEM.STARTUP
4 PICTURE.BITS
5 WIZUTIL.CODE
6 SYSTEM.LIBRARY
7 RTSTRP.APPLE
8 SYSTEM.PASCAL
9 SYSTEM.MISCINFO

TYPE A FILE NUMBER (OR -1) 5

SEGMENT T/S = 24/0 --- PASCAL T/S
SEGMENT T/S = 24/0 --- DOS 3.3 T/S

0 WIZBOOT
1 UTILS
2 MAKESCEN
3 OPTIONS

TYPE A SEGMENT NUMBER (OR -1) 1

T/S FOR SEGMENT 24/2 --- PASCAL
T/S FOR SEGMENT 24/13 --- DOS

SEGMENT'S START ADDR($5000):

THERE ARE 26 PROCEDURES.


ENTER PROCEDURE # (OR -1) 10


JTAB FOR SUBROUTINE 10:
DATA SIZE: 8
PARAM SIZE: 2
EXIT AT: 538C
ENTER AT: 5340
PROC NUMBER: 10
LEXICAL LEVEL: 2
5340 00 SLDC. PUSH #0000
5341 CC 04 STL. MP.04 := (TOS)
5343 AE 04 CIP. CALL INTERMEDIATE PROCEDURE: 04
5345 EA SLDO. PUSH BASE.03
5346 0D SLDC. PUSH #000D
5347 CB NEQI. PUSH ((TOS-1) <> (TOS))
5348 A1 24 UJP. IF NOT (TOS) THEN JUMP TO 536E
534A B6 03 03 LOD. PUSH ACTREC(-03).03
534D 08 SLDC. PUSH #0008
534E 00 SLDC. PUSH #0000
534F CD 00 11 CXP. CALL EXTERNAL PROCEDURE: 11 IN SEGMENT: 00
5352 00 SLDC. PUSH #0000
5353 CC 03 STL. MP.03 := (TOS)
5355 EC SLDO. PUSH BASE.05
5356 02 SLDC. PUSH #0002
5357 8E MODI. PUSH ((TOS-1) MOD (TOS))
5358 CC 05 STL. MP.05 := (TOS)
535A DA SLDL. PUSH MP.03
535B DC SLDL. PUSH MP.05
535C C8 LEQI. PUSH ((TOS-1) <= (TOS))
535D A1 0F UJP. IF NOT (TOS) THEN JUMP TO 536E
535F B6 03 03 LOD. PUSH ACTREC(-03).03
5362 58 SLDC. PUSH #0058
5363 00 SLDC. PUSH #0000
5364 CD 00 11 CXP. CALL EXTERNAL PROCEDURE: 11 IN SEGMENT: 00
5367 DA SLDL. PUSH MP.03
5368 01 SLDC. PUSH #0001
5369 82 ADI. PUSH ((TOS) + (TOS-1))
536A CC 03 STL. MP.03 := (TOS)
536C B9 F6 UJP. JUMP TO 535A
536E DB SLDL. PUSH MP.04
536F 01 SLDC. PUSH #0001
5370 82 ADI. PUSH ((TOS) + (TOS-1))
5371 CC 04 STL. MP.04 := (TOS)
5373 D8 SLDL. PUSH MP.01
5374 DB SLDL. PUSH MP.04
5375 EA SLDO. PUSH BASE.03
5376 BF STB. (TOS-2)^.(TOS-1) := TOS BYTE
5377 EA SLDO. PUSH BASE.03
5378 0D SLDC. PUSH #000D
5379 C3 EQUI. PUSH ((TOS-1) = (TOS))
537A DB SLDL. PUSH MP.04
537B 0F SLDC. PUSH #000F
537C C3 EQUI. PUSH ((TOS-1) = (TOS))
537D 8D LOR. PUSH ((TOS-1) OR (TOS))
537E A1 F4 UJP. IF NOT (TOS) THEN JUMP TO 5343
5380 B6 03 03 LOD. PUSH ACTREC(-03).03
5383 CD 00 16 CXP. CALL EXTERNAL PROCEDURE: 16 IN SEGMENT: 00
5386 D8 SLDL. PUSH MP.01
5387 00 SLDC. PUSH #0000
5388 DB SLDL. PUSH MP.04
5389 01 SLDC. PUSH #0001
538A 95 SBI. PUSH ((TOS-1) - (TOS))
538B BF STB. (TOS-2)^.(TOS-1) := TOS BYTE
538C AD 00 RNP. RETURN FROM NON-BASE PROCEDURE.
538E 4B SLDC. PUSH #004B
538F 00 SLDC. PUSH #0000
5390 36 SLDC. PUSH #0036
5391 00 SLDC. PUSH #0000

ENTER PROCEDURE # (OR -1) -1

============================

Description of lising (e.g., instruction at $534F):

534F CD 00 11 CXP. CALL EXTERNAL PROCEDURE: 11 IN SEGMENT: 00

The first number is an Apple memory address ($534F) determined by
the segment load address.
Next are the P-code bytes (CD 00 11).
Following that is the P-code mnemonic for CD (CXP.).
Following that is the interpretation of the parameters for the CXP
instruction
(CALL EXTERNAL PROCEDURE: 11 IN SEGMENT: 00).

The JTAB header says there are 2 bytes of data as parameters and 8
bytes of data for local variables in this routine. Pascal generally
works on word boundaries with 2 bytes of data representing one piece
of data such as an INTEGER. The JTAB indicates this procedure is at
lexical level 2 (UTILS is lexical level 1, so this is a direct child
to it). The RNP instruction at 538C is the exit point from this
routine, and since the parameter for this instruction (RNP.) is 00,
that means this is a procedure and not a function.

Procedures and functions are inserted into the final CODE file based
on the order in which the "END;" instruction for the procedure is
encountered while compiling. They are given a "number" based on the
number of PROCEDURE (and FUNCTION) headings encountered. (There is an
exception to this rule when FORWARD statements are used.)

The P-code machine makes heavy use of the stack to push results and
pull results. For example, to assign a value to a variable takes 2
steps:

1. PUSH <value>
2. ASSIGN (from TOS) (TOS == Top of Stack).

To add 2 values, and then assign the result to a variable takes 4
steps:

1. PUSH <value1>
2. PUSH <value2>
3. ADD <TOS> + <TOS-1> (Pull 2 values from stack, add them, and
push result)
4. ASSIGN (from TOS)

So already we can start programming:

PROGRAM WIZBOOT; (* <-- Note the name from
decompiler. *)
SEGMENT PROCEDURE UTILS; (* <-- Note "SEGMENT" and name
from decompiler. *)
PROCEDURE P050102;
BEGIN
END;
PROCEDURE P050103;
BEGIN
END;
PROCEDURE P050104;
BEGIN
END;
PROCEDURE P050105;
BEGIN
END;
PROCEDURE P050106;
BEGIN
END;
PROCEDURE P050107;
BEGIN
END;
PROCEDURE P050108;
BEGIN
END;
PROCEDURE P050109;
BEGIN
END;

PROCEDURE P05010A( MP01: INTEGER); (* <-- lexical level 2
(subroutine to UTILS) *)

VAR
MP02: INTEGER;
MP03: INTEGER;
MP04: INTEGER;
MP05: INTEGER;

BEGIN
END;

BEGIN (* UTILS *)
END;

BEGIN (* WIZBOOT *)
END.

I use a simple naming convention for unknown procedures and
functions. The letter "P", followed by the file's cardinal location
in the directory (05), followed by the segment number (01), followed
by the procedure number (I'm inconsistent as I use hexidecimal here,
but use decimal in WIZ4). Note, the values in the procedure name are
the same as the "input" to the decompiler (WIZ4).

I use MPxx to represent parameters and local variables, and BASExx to
represent global variables.

At this point I do not know if the variables are INTEGER or something
else, and I do not know if the parameter is passed by value or passed
by reference (VAR).

If you printed my p-code listing on paper, you should now start to
annotate it with your pencil (of course these days you might want to
just put it into an editor and work on it). Under each instruction
that has ":=" you should draw a line to signify that this completes an
assignment instruction. You can also draw a line under any "CALL"
instruction as that oftentimes indicates the end of a Pascal
instruction.

If you see a jump or branch instruction in a forward direction
(towards the bottom of the page), then draw an arrow from the left
side of the page from there to the address. If you see a jump or
branch to an instruction in a backward direction, draw an arrow from
the right side to the destination.

So now you listing should look like the following:

5340 00 SLDC. PUSH #0000
5341 CC 04 STL. MP.04 := (TOS)
--------------------------------------
5343 AE 04 CIP. CALL INTERMEDIATE PROCEDURE: 04 <-+
-------------------------------------- |
5345 EA SLDO. PUSH BASE.03 |
5346 0D SLDC. PUSH #000D |
5347 CB NEQI. PUSH ((TOS-1) <> (TOS)) |
5348 A1 24 +<-UJP. IF NOT (TOS) THEN JUMP TO 536E |
534A B6 03 03 | LOD. PUSH ACTREC(-03).03 |
534D 08 | SLDC. PUSH #0008 |
534E 00 | SLDC. PUSH #0000 |
534F CD 00 11 | CXP. CALL EXTERNAL PROCEDURE: 11 IN SEGMENT: 00
--------------|----------------------- |
5352 00 | SLDC. PUSH #0000 |
5353 CC 03 | STL. MP.03 := (TOS) |
--------------|----------------------- |
5355 EC | SLDO. PUSH BASE.05 |
5356 02 | SLDC. PUSH #0002 |
5357 8E | MODI. PUSH ((TOS-1) MOD (TOS)) |
5358 CC 05 | STL. MP.05 := (TOS) |
--------------|----------------------- |
535A DA | SLDL. PUSH MP.03 <--+ |
535B DC | SLDL. PUSH MP.05 | |
535C C8 | LEQI. PUSH ((TOS-1) <= (TOS)) |
535D A1 0F +<-UJP. IF NOT (TOS) THEN JUMP TO 536E |
535F B6 03 03 | LOD. PUSH ACTREC(-03).03 |
5362 58 | SLDC. PUSH #0058 | |
5363 00 | SLDC. PUSH #0000 | |
5364 CD 00 11 | CXP. CALL EXTERNAL PROCEDURE: 11 IN SEGMENT: 00
--------------|----------------------- | |
5367 DA | SLDL. PUSH MP.03 | |
5368 01 | SLDC. PUSH #0001 | |
5369 82 | ADI. PUSH ((TOS) + (TOS-1)) |
536A CC 03 | STL. MP.03 := (TOS) | |
--------------|----------------------- | |
536C B9 F6 | UJP. JUMP TO 535A ->+ |
536E DB +->SLDL. PUSH MP.04 |
536F 01 SLDC. PUSH #0001 |
5370 82 ADI. PUSH ((TOS) + (TOS-1)) |
5371 CC 04 STL. MP.04 := (TOS) |
-------------------------------------- |
5373 D8 SLDL. PUSH MP.01 |
5374 DB SLDL. PUSH MP.04 |
5375 EA SLDO. PUSH BASE.03 |
5376 BF STB. (TOS-2)^.(TOS-1) := TOS BYTE |
-------------------------------------- |
5377 EA SLDO. PUSH BASE.03 |
5378 0D SLDC. PUSH #000D |
5379 C3 EQUI. PUSH ((TOS-1) = (TOS)) |
537A DB SLDL. PUSH MP.04 |
537B 0F SLDC. PUSH #000F |
537C C3 EQUI. PUSH ((TOS-1) = (TOS)) |
537D 8D LOR. PUSH ((TOS-1) OR (TOS)) |
537E A1 F4 UJP. IF NOT (TOS) THEN JUMP TO 5343 -->+
5380 B6 03 03 LOD. PUSH ACTREC(-03).03
5383 CD 00 16 CXP. CALL EXTERNAL PROCEDURE: 16 IN SEGMENT: 00
--------------------------------------
5386 D8 SLDL. PUSH MP.01
5387 00 SLDC. PUSH #0000
5388 DB SLDL. PUSH MP.04
5389 01 SLDC. PUSH #0001
538A 95 SBI. PUSH ((TOS-1) - (TOS))
538B BF STB. (TOS-2)^.(TOS-1) := TOS BYTE
538C AD 00 RNP. RETURN FROM NON-BASE PROCEDURE.------>
--------------------------------------
538E 4B SLDC. PUSH #004B
538F 00 SLDC. PUSH #0000
5390 36 SLDC. PUSH #0036
5391 00 SLDC. PUSH #0000


You might start to recognize some Pascal structure now.

This construct is an assignment statement:

PUSH #0000
MP.04 := (TOS)

This construct is a REPEAT UNTIL loop:

A <-----+
B |
C |
test -->+ (jump backward)

This construct is an IF THEN statement:

+<--test (jump forward)
| A
| B
| C
+-->D

This construct is a FOR (or WHILE) loop:

+<---test <---+ (jump forward)
| A |
| B |
| C |
| jump --->+ (unconditional jump backward)
+--->D

Whenever you see "MP.xx" it refers to parameters in the procedure
heading, or to local variables. The "MP" refers to Markstack Pointer
or simply Markstack. Whenever you see "BASE.xx" that refers to GLOBAL
variables.

Whenever you see "ACTREC(-x)" it is referring to a Pascal Activation
Record. The value in the parenthesis after ACTREC indicates the
number of levels backwards to traverse.

With "IF THEN" instructions the code usually sets up the condition and
then tests "IF NOT <condition> JUMP" which means when the condition is
TRUE it falls into the code following the test as we would expect.

There are several calls to EXTERNAL procedures and the ACTREC(-3)
means we are going back several activation records until we are at the
-1 level (+2 - 3). The ".3" refers to the standard Pascal OUTPUT
file. There is always a parameter of 0 pushed before an external
procedure call. This means the code is executing a standard Pascal
procedure. Here is a table I created to identify the external
procedures:

READ() Procedure: 10
EOLN() Procedure: 0B
WRITE() Procedure: 11 WRITE( CHR( 13))
WRITE() Procedure: 13 WRITE( Astr : 20);
WRITELN() Procedure: 13 and 16
WRITELN; Procedure: 16
WRITE() Procedure: 0D WRITE( Anum);
GOTOXY() Procedure: 1D
COPY() Procedure: 19
CONCAT() Procedure: 17

Ok, get out your coding pencil again! It's time to code!

If I don't know how to convert something right away, I'll just leave
the original P-code in the file for now.

PROGRAM WIZBOOT;

VAR
(* First 2 global variables are implicitly defined. INPUT/
OUTPUT (?) *)

BASE03 : INTEGER; (* see P05010A *)
BASE04 : INTEGER;
BASE05 : INTEGER; (* see P05010A *)

SEGMENT PROCEDURE UTILS;

PROCEDURE P05010A( MP01: INTEGER);

VAR
MP02: INTEGER;
MP03: INTEGER;
MP04: INTEGER;
MP05: INTEGER;

BEGIN
MP04 := 0;
REPEAT
P050104;
IF BASE03 <> 13 THEN
BEGIN
WRITE( CHR( 8));
MP03 := 0;
MP05 := BASE05 MOD 2;
WHILE MP03 <= MP05 DO
BEGIN
WRITE( CHR( 88));
MP03 := MP03 + 1
END;
END;
MP04 := MP04 + 1;

5373 D8 SLDL. PUSH MP.01 |
5374 DB SLDL. PUSH MP.04 |
5375 EA SLDO. PUSH BASE.03 |
5376 BF STB. (TOS-2)^.(TOS-1) := TOS BYTE |

UNTIL (BASE03 = 13) OR (MP04 = 15);
WRITELN;
5386 D8 SLDL. PUSH MP.01
5387 00 SLDC. PUSH #0000
5388 DB SLDL. PUSH MP.04
5389 01 SLDC. PUSH #0001
538A 95 SBI. PUSH ((TOS-1) - (TOS))
538B BF STB. (TOS-2)^.(TOS-1) := TOS BYTE

END;


Ok, time to clean up MP01, the parameter to P05010A. The STB
instruction at $5376 indicates we are using MP01 as a pointer that is
indexed by MP04 (MP.04) and storing BASE03. The instruction at $538B
is likewise using MP01 as a pointer.

By looking at the calling procedure we can determine how it uses the
parameter that is passed to P05010A. The variable in the calling
procedure is also called MP.01. Here is some code from the calling
routine:

LLA. PUSH #MP.01
LLA. PUSH #MP.4B
NEQ. PUSH ((TOS-1) <> (TOS))--STRINGS

Therefore we know that the type of parameter passed to P05010A must be
STRING, and since it is altered, it is passed by reference (VAR).

There is one other problem with the code. If you look at BASE03, you
will see in one line that it seems to be used as an INTEGER, yet on
another line it is being used as a BYTE. The proper place to make the
correction here is the definition for BASE03...it must be type CHAR.
And the instruction that is comparing it to "13" as an integer can be
fixed by using the "CHR" function as "CHR( 13)".

So this version of the code now looks like:

PROGRAM WIZBOOT;

VAR
(* First 2 global variables are implicitly defined. INPUT/
OUTPUT (?) *)

BASE03 : CHAR; (* see P05010A *)
BASE04 : INTEGER;
BASE05 : INTEGER; (* see P05010A *)


PROCEDURE P05010A( VAR MP01: STRING);

VAR
MP02: INTEGER;
MP03: INTEGER;
MP04: INTEGER;
MP05: INTEGER;

BEGIN
MP04 := 0;
REPEAT
P050104;
IF BASE03 <> CHR( 13) THEN
BEGIN
WRITE( CHR( 8));
MP03 := 0;
MP05 := BASE05 MOD 2;
WHILE MP03 <= MP05 DO
BEGIN
WRITE( CHR( 88));
MP03 := MP03 + 1
END;
END;
MP04 := MP04 + 1;
MP01[ MP04] := BASE03
UNTIL (BASE03 = CHR( 13)) OR (MP04 = 15);
WRITELN;
MP01[ 0] := MP04 - 1
END;

==============================

There is something I need to mention about this code. There are 2
ways to write the "WHILE" loop. It can also be written as a FOR loop:

1. Remove MP05 definition from the VAR declaration,
2. Rewrite the loop:
FOR MP03 := 0 to BASE05 MOD 2 DO
WRITE( CHR( 88));

The compiler will actually generate a temporary variable that was
previously defined as MP05. The compiler will then generate EXACTLY
the same P-code. Note that I did not say it would produce code that
when executed would produce identical results, the P-code itself is
identical. You might think this is an insignificant topic, but not
knowing this distinction caused me to spend countless HOURS trying to
solve a problem in the code.

Note that this is just one case where writing 2 different looking
sequence of Pascal statements will generate exactly the same P-code.
It really doesn't matter (usually) which way you write it in your re-
engineering because the resultant code will execute exactly as the
original code.

If you want to, you can also change CHR( 88) to 'X' (with the single
quotes).

Ok, so now you compile this code and find that it still does not match
the generated code (even if I haven't included any typos in this
description). You may find that you need to disable the "IO CHECK" (*
$I-*) and turn off "RANGE CHECK" (*$R-*).

I think this was a very good example to start with, as it showed
parameter passing, local variables, global variables, a compiler
generated local variable, a call to a local procedure, calls to
external standard procedures, assignment statements, repeat loop, if-
then, and a while (or for) loop.

Enjoy!

--Tommy

winston...@yahoo.com

unread,
May 30, 2012, 3:40:23 PM5/30/12
to
UCSD P-code!

Can anyone here tell me more about this actual program? Can I assume it uses graphics, like the UNIT TURTLEGRAPHICS or whatever the Apple II name was?

Sorry, I was (am?) a UCSD junky, since I got UCSD 4.0 back in 1987 for my TI-99... it was too hard to obtain before they went out of the computer business...

David Schmenk

unread,
May 30, 2012, 4:10:29 PM5/30/12
to
Tommy-

This is quite an achievement. I'm looking forward to going over all your research when I get time (2013?). One reference that has great insight into the inner workings of the UCSD p-code interpreter and the Apple version in particular is Randy Hyde's excellent p-Source book. Google "hyde p source" and you should be able to find a link to the pdf. It still amazes me what could be pulled off in 64K.

Dave...
Message has been deleted

TommyGoog

unread,
May 31, 2012, 2:42:10 AM5/31/12
to
On May 30, 3:10 pm, David Schmenk <dschm...@gmail.com> wrote:
> Tommy-
>
> One reference that has great insight into the inner workings of the UCSD p-code interpreter and the Apple version in particular is Randy Hyde's excellent p-Source book.  Google "hyde p source" and
> Dave...

Hi David Schmenk,

Interesting that you mention Randy Hyde's book ("Hyde_P-Source - A
Guide to the Apple Pascal System 1983"). A Google search should bring
you to an earlier post in this thread (April 11) where I posted a link
(bullet item 6) to his excellent work :) I wish I'd known about it
before I did my preliminary work in 1991-1992!


Hi Winston,

>Can anyone here tell me more about this actual program? Can I assume it
>uses graphics, like the UNIT TURTLEGRAPHICS or whatever the Apple II name
>was?"

Wizardry III does not use the TurtleGraphics unit. If you are
interested in looking at the 14,000 lines of Pascal code that I re-
engineered, they can be found here:

ftp://ftp.apple.asimov.net/pub/apple_II/images/games/rpg/wizardry/wizardry_III/Wizardry_iii_SourceCode.zip

I will retype that link in pieces, as when I look at it in my browser
on this forum it seems to be truncated:

ftp://ftp.apple.asimov.net/pub/apple_II/images/games
rpg/wizardry/wizardry_III/Wizardry_iii_SourceCode.zip

There are 2 directories in the zip file. One for Wizardry.CODE and
one for WizUtil.CODE. The listings are in these 2 files:

WizardryListing.txt
WizUtilListing.txt

--Tommy

TommyGoog

unread,
May 31, 2012, 2:37:17 AM5/31/12
to
On May 30, 3:10 pm, David Schmenk <dschm...@gmail.com> wrote:
> Tommy-
>
> One reference that has great insight into the inner workings of the UCSD p-code interpreter and the Apple version in particular is Randy Hyde's excellent p-Source book.  Google "hyde p source" and

David Schmenk

unread,
May 31, 2012, 9:33:13 AM5/31/12
to
On Wednesday, May 30, 2012 11:42:10 PM UTC-7, TommyGoog wrote:
> On May 30, 3:10 pm, David Schmenk <dschm...@gmail.com> wrote:
> > Tommy-
> >
> > One reference that has great insight into the inner workings of the UCSD p-code interpreter and the Apple version in particular is Randy Hyde's excellent p-Source book.  Google "hyde p source" and
> > Dave...
>
> Hi David Schmenk,
>
> Interesting that you mention Randy Hyde's book ("Hyde_P-Source - A
> Guide to the Apple Pascal System 1983"). A Google search should bring
> you to an earlier post in this thread (April 11) where I posted a link
> (bullet item 6) to his excellent work :) I wish I'd known about it
> before I did my preliminary work in 1991-1992!
>
>

snip

> --Tommy

And so you did! Sorry to have re-iterated your point (but, yeah, the book is that good). You have a ton of great information here. It will take time to digest all of it, but kudos to your excellent work,

Dave...

winston...@yahoo.com

unread,
May 31, 2012, 10:15:49 AM5/31/12
to
Thank you for that link. I have something to read over the next week!
And the Hyde book is great. He still hung out over at comp.lang.asm last I looked...

A2CPM

unread,
May 31, 2012, 8:59:29 PM5/31/12
to
Hi!

On May 31, 10:15 am, "winston19842...@yahoo.com"
<winston19842...@yahoo.com> wrote:
> Thank you for that link. I have something to read over the next week!
> And the Hyde book is great. He still hung out over at comp.lang.asm last I looked...

The last message I could find from Randy Hyde in comp.lang.asm.x86
is over six years old.

Willi


winston...@yahoo.com

unread,
Jun 1, 2012, 2:07:53 AM6/1/12
to
Oops... alt.lang.asm... but it has been almost 2 years there too, it looks like.

TommyGoog

unread,
Jun 21, 2012, 5:42:41 PM6/21/12
to
In this post, I will give more examples of the output from my pcode
decompiler, Wiz4, and some of the interesting problems you might run
into. The programs at the end of this post were taken from Hyde
chapter 4.

In order to understand how to re-engineer Pascal pcode, you should
probably first study sample Pascal programs and the pcode generated
from them.

There are 2 different de-compilers (de-coders?) used in Hyde, but I
think mine, Wiz4, displays the information a little better, even if it
is a bit inconsistent in places.

The description fields for Wiz4 are a bit of a mish-mash of different
programming languages. If I were to write a new de-compiler for
others to use, I would make the description fields a bit more
consistent with a single known programming language such as Pascal or
C.


Definition of terms used in my de-compiler output
=================================================

BYTE
----
8 bit value


WORD
----
Two consecutive BYTES, usually starting on an even byte boundary and
in little-endian notation.


BASE
----
BASE Procedure. A pointer to the activation record of the most
recently invoked base procedure (lex level 0). Global (lex level 0)
variables are accessed by indexing off BASE. (as described in Apple
Pascal OS)

In Wiz4, BASE.03 represents the first global variable. The value
following "BASE." is the word offset in hexidecimal to the global
variable. If I were to re-write Wiz4, I might instead use a notation
such as "BASE^.G03". I'm not sure why I have chosen 03 to represent
the first global variable instead of using 01 or 00.


MP
--
Markstack Pointer. This is similar to BASE, except that it refers to
the variables in a Procedure (or Function) or to the passed
parameters. MP.01 represents the first declared variable in a
procedure with no passed parameters.


TOS
---
Top of Stack. (TOS) is my way of referring to the value stored on
TOS.

Once again, I am a bit inconsistent in Wiz4. I use "(TOS)" to
represent the value that is found at the top of the stack. It would
probably make more sense to have instead used "TOS^.0", and perhaps
"TOS^.1" or "TOS^.-1" to represent the previous TOS value.


(TOS) or (TOS-1)
----------------
If the 2 values on the top of the stack represent integers, then (TOS)
is the value of the integer at the top of the stack, and (TOS-1) is
the value of the next integer in the stack.

If the 2 values on the top of the stack represent other sized values,
then (TOS) refers to the top value, and (TOS-1) refers to the next.


(TOS-1)^ := (TOS)^ 02 WORDS
---------------------------
A construct like this says to use (TOS) and (TOS-1) as pointers and to
transfer 2 words pointed to by (TOS) to the address (TOS-1). Some of
my descriptions use "BT" which stands for "Block Transfer".


PUSH #0004
----------
Push the value 04 on to the stack (1 word).


PUSH BASE.03
------------
Push the value of the variable BASE.03 on to the stack.


PUSH #BASE.03
-------------
Push the address of BASE.03 on to the stack.


STRING instructions in code
---------------------------

5005 A6 0B 48 45 4C 4C 4F 20 54 48 45 52 45
HELLO THERE
LSA. PUSH #(PC+1) POINTER TO THE STRING

First the hex values of the characters are displayed,then the next
line displays the ASCII representation,and the final line has the
instruction. In this case, the pointer to the string is pushed onto
the stack (using the program counter). Note the first byte is the
length of the string, and that the string is not "null terminated".


SAS instruction
---------------

5012 AA 50 SAS. TOS=CHAR::(TOS-1)^ := TOS CHAR
TOS=PTR ::(TOS-1)^ := (TOS)^ 50 CHARACTERS MAX

This instruction is a bit strange, since it operates differently based
upon the value that is currently on the top of stack. If it is a
character, then use (TOS-1) as a pointer, and transfer the character.

If it is a pointer to a string, then use the pointer to transfer the
string (TOS-1).


First 2 instructions NOP
------------------------
You might notice that the first 2 instructions for most procedures are
simply NOPs. The compiler was written as a single pass compiler, but
they needed to account for the possible loading of intrinsic
segments. Intrinsic segments must be loaded before any of the rest of
the code in the procedure is executed, but the compiler doesn't know
that until it gets to an instruction that requires the intrinsic
segment. When an intrinsic segment needs to be loaded, the NOPs are
changed to a GOTO instruction. See for example, HYDE4-19.

[Side note: In a bit of irony, the first instruction executed by the
pcode "machine" during the boot process is a GOTO instruction to load
an intrinsic segment.]


Instructions following RBP
--------------------------
You might also notice that following the exit (RBP) statement in a
procedure that Wiz4 usually displays a few more "instructions". These
are not instructions, but are the "jump table" values for the
procedure. During my re-engineering of Legacy of Llylgamyn I realized
that I could have re-constructed the pcode correctly from the
beginning of the procedure through the RBP instruction, but still not
have correctly written the Pascal code, with the only difference being
in these bytes that follow the RBP instruction. Consider the
following 2 programs:


PROGRAM BIGJUMP1;

VAR

A : INTEGER;

BEGIN

IF TRUE THEN
BEGIN
A := 1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20;
A := 1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20;
A := 1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20;
A := 1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20;
A := 1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20;
A := 2
END;

END.


PROGRAM BIGJUMP2;

VAR

A : INTEGER;

BEGIN

IF TRUE THEN
BEGIN
A := 1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20;
A := 1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20;
A := 1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20;
A := 1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20;
A := 1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20
END;
A := 2
END.

The pcodes generated from BIGJUMP1 and BIGJUMP2 are identical from the
first instruction down through the RBP instruction. They only differ
in the "jump table" values that follow the RBP instruction.


LDC instruction
---------------
Earlier today I noticed the LDC pcode instruction seems to display
differently at times. Then I realized that it is operating with WORD
values and needs to have them aligned on an even BYTE boundary. For
example:

5004 B3 02 8C 3F CD CC
LDC. PUSH 02 WORDS

5017 B3 02 (02)8C 3F CD CC
LDC. PUSH 02 WORDS



Record structures and pcode
---------------------------
Re-engineering pcode that was created from Pascal code written with
user defined structures (RECORDs)is a very error prone task and will
often require re-working of re-engineered Pascal code that you thought
you had properly re-engineered. Take for example the following pcode:

JTAB FOR SUBROUTINE 1:
DATA SIZE: 16
PARAM SIZE: 4
EXIT AT: 5022
ENTER AT: 5000
PROC NUMBER: 1
LEXICAL LEVEL: 0
5000 D7 NOP. NOP
5001 D7 NOP. NOP
5002 01 SLDC. PUSH #0001
5003 AB 03 SRO. BASE.03 := (TOS)
5005 02 SLDC. PUSH #0002
5006 AB 04 SRO. BASE.04 := (TOS)
5008 01 SLDC. PUSH #0001
5009 AB 05 SRO. BASE.05 := (TOS)
500B 00 SLDC. PUSH #0000
500C AB 06 SRO. BASE.06 := (TOS)
500E A5 07 LA0. PUSH #BASE.07
5010 B3 02 8C 3F CD CC
LDC. PUSH 02 WORDS
5016 BD 02 STM. (TOS-1)^ := (TOS)^ 02 WORDS
5018 A5 09 LA0. PUSH #BASE.09
501A B3 02 0C 40 CD CC
LDC. PUSH 02 WORDS
5020 BD 02 STM. (TOS-1)^ := (TOS)^ 02 WORDS
5022 C1 00 RBP. RETURN FROM BASE PROCEDURE.


The following 2 programs (TEST3 and TEST2) BOTH generate the pcode
above:

PROGRAM TEST3;


VAR

A1 : INTEGER;
A2 : INTEGER;
B1 : BOOLEAN;
B2 : BOOLEAN;
R1 : REAL;
R2 : REAL;

BEGIN

A1 := 1;
A2 := 2;
B1 := TRUE;
B2 := FALSE;
R1 := 1.1;
R2 := 2.2

END.

-------------------------

PROGRAM TEST2;

TYPE
ATYPE = RECORD
A1 : INTEGER;
A2 : INTEGER;
B1 : BOOLEAN;
B2 : BOOLEAN;
R1 : REAL;
R2 : REAL;
END;

VAR
A : ATYPE;

BEGIN

A.A1 := 1;
A.A2 := 2;
A.B1 := TRUE;
A.B2 := FALSE;
A.R1 := 1.1;
A.R2 := 2.2

END.


Let's continue with this example a step further. The following pcode
is identical to the above, but 1 more instruction was added at the end
of the Pascal program. Now it is clear that the RECORD structure MUST
be used:

JTAB FOR SUBROUTINE 1:
DATA SIZE: 32
PARAM SIZE: 4
EXIT AT: 5028
ENTER AT: 5000
PROC NUMBER: 1
LEXICAL LEVEL: 0
5000 D7 NOP. NOP
5001 D7 NOP. NOP
5002 01 SLDC. PUSH #0001
5003 AB 03 SRO. BASE.03 := (TOS)
5005 02 SLDC. PUSH #0002
5006 AB 04 SRO. BASE.04 := (TOS)
5008 01 SLDC. PUSH #0001
5009 AB 05 SRO. BASE.05 := (TOS)
500B 00 SLDC. PUSH #0000
500C AB 06 SRO. BASE.06 := (TOS)
500E A5 07 LA0. PUSH #BASE.07
5010 B3 02 8C 3F CD CC
LDC. PUSH 02 WORDS
5016 BD 02 STM. (TOS-1)^ := (TOS)^ 02 WORDS
5018 A5 09 LA0. PUSH #BASE.09
501A B3 02 0C 40 CD CC
LDC. PUSH 02 WORDS
5020 BD 02 STM. (TOS-1)^ := (TOS)^ 02 WORDS
5022 A5 0B LA0. PUSH #BASE.0B
5024 A5 03 LA0. PUSH #BASE.03
5026 A8 08 MOV. (TOS-1)^ := (TOS)^ BT 08 WORDS
5028 C1 00 RBP. RETURN FROM BASE PROCEDURE.



Wiz4 Examples using Hyde chapter 4
==================================

PROGRAM ALLOCATION_EXAMPLES; (* HYDE41 *)

VAR
I : INTEGER;
A : ARRAY [0..15] OF INTEGER;
J : INTEGER;
B : ARRAY [0..127] OF INTEGER;
K : INTEGER;

BEGIN
I := I; (* IN THESE EXAMPLES, NOTE THE *)
J := J; (* NUMBER OF BYTES REQUIRED FOR *)
K := K (* LOAD AND STORING EACH VARIABLE *)
END.

FILE: 24 SEG: 0 PROC: 1

JTAB FOR SUBROUTINE 1:
DATA SIZE: 294
PARAM SIZE: 4
EXIT AT: 500F
ENTER AT: 5000
PROC NUMBER: 1
LEXICAL LEVEL: 0
5000 D7 NOP. NOP
5001 D7 NOP. NOP
5002 EA SLDO. PUSH BASE.03
5003 AB 03 SRO. BASE.03 := (TOS)
5005 A9 14 LDO. PUSH BASE.14
5007 AB 14 SRO. BASE.14 := (TOS)
5009 A9 80 95 LDO. PUSH BASE.95
500C AB 80 95 SRO. BASE.95 := (TOS)
500F C1 00 RBP. RETURN FROM BASE PROCEDURE.
5011 00 SLDC. PUSH #0000





PROGRAM ALLOCATION_EXAMPLES_REAL; (* HYDE42 *)

VAR
R : REAL;
S : REAL;

BEGIN
R := 1.1; (* ASSIGNING A REAL CONSTANT *)
R := 4; (* ASSIGNING AN INTEGER CONSTANT. *)
R := S; (* ASSIGNING A REAL VARIABLE. *)
R := -1.1; (* ASSIGNING A NEGATIVE REAL CONSTANT. *)
END.

FILE: 25 SEG: 0 PROC: 1

JTAB FOR SUBROUTINE 1:
DATA SIZE: 8
PARAM SIZE: 4
EXIT AT: 5025
ENTER AT: 5000
PROC NUMBER: 1
LEXICAL LEVEL: 0
5000 D7 NOP. NOP
5001 D7 NOP. NOP
5002 A5 03 LA0. PUSH #BASE.03
5004 B3 02 8C 3F CD CC
LDC. PUSH 02 WORDS
500A BD 02 STM. (TOS-1)^ := (TOS)^ 02 WORDS
500C A5 03 LA0. PUSH #BASE.03
500E 04 SLDC. PUSH #0004
500F 8A FLT. PUSH ( REAL(TOS) )
5010 BD 02 STM. (TOS-1)^ := (TOS)^ 02 WORDS
5012 A5 03 LA0. PUSH #BASE.03
5014 A5 05 LA0. PUSH #BASE.05
5016 BC 02 LDM. PUSH 02 WORDS USING (TOS)^
5018 BD 02 STM. (TOS-1)^ := (TOS)^ 02 WORDS
501A A5 03 LA0. PUSH #BASE.03
501C B3 02 8C 3F CD CC
LDC. PUSH 02 WORDS
5022 92 NGR. PUSH -(TOS)
5023 BD 02 STM. (TOS-1)^ := (TOS)^ 02 WORDS
5025 C1 00 RBP. RETURN FROM BASE PROCEDURE.
5027 00 SLDC. PUSH #0000



PROGRAM ALLOCATION_EXAMPLES_ARRAYS; (* HYDE43 *)

VAR
I : ARRAY [0..10] OF INTEGER;
R : ARRAY[ 0..10] OF REAL;
J : INTEGER;

BEGIN
J := 1;
I [0] := 0;
R [0] := 1.1;
R [J] := 1.1;
I [J] := J;
END.
FILE: 26 SEG: 0 PROC: 1

JTAB FOR SUBROUTINE 1:
DATA SIZE: 68
PARAM SIZE: 4
EXIT AT: 503E
ENTER AT: 5000
PROC NUMBER: 1
LEXICAL LEVEL: 0
5000 D7 NOP. NOP
5001 D7 NOP. NOP
5002 01 SLDC. PUSH #0001
5003 AB 24 SRO. BASE.24 := (TOS)
5005 A5 03 LA0. PUSH #BASE.03
5007 00 SLDC. PUSH #0000
5008 00 SLDC. PUSH #0000
5009 0A SLDC. PUSH #000A
500A 88 CHK. IF (TOS-1) <= (TOS-2) <= (TOS) THEN PUSH TOS-2
ELSE ERROR
500B A4 01 IXA. PUSH #((TOS-1)[TOS]). ARRAY SIZE=01
500D 00 SLDC. PUSH #0000
500E 9A STO. (TOS-1)^ := (TOS)
500F A5 0E LA0. PUSH #BASE.0E
5011 00 SLDC. PUSH #0000
5012 00 SLDC. PUSH #0000
5013 0A SLDC. PUSH #000A
5014 88 CHK. IF (TOS-1) <= (TOS-2) <= (TOS) THEN PUSH TOS-2
ELSE ERROR
5015 A4 02 IXA. PUSH #((TOS-1)[TOS]). ARRAY SIZE=02
5017 B3 02 (02)8C 3F CD CC
LDC. PUSH 02 WORDS
501E BD 02 STM. (TOS-1)^ := (TOS)^ 02 WORDS
5020 A5 0E LA0. PUSH #BASE.0E
5022 A9 24 LDO. PUSH BASE.24
5024 00 SLDC. PUSH #0000
5025 0A SLDC. PUSH #000A
5026 88 CHK. IF (TOS-1) <= (TOS-2) <= (TOS) THEN PUSH TOS-2
ELSE ERROR
5027 A4 02 IXA. PUSH #((TOS-1)[TOS]). ARRAY SIZE=02
5029 B3 02 (02)8C 3F CD CC
LDC. PUSH 02 WORDS
5030 BD 02 STM. (TOS-1)^ := (TOS)^ 02 WORDS
5032 A5 03 LA0. PUSH #BASE.03
5034 A9 24 LDO. PUSH BASE.24
5036 00 SLDC. PUSH #0000
5037 0A SLDC. PUSH #000A
5038 88 CHK. IF (TOS-1) <= (TOS-2) <= (TOS) THEN PUSH TOS-2
ELSE ERROR
5039 A4 01 IXA. PUSH #((TOS-1)[TOS]). ARRAY SIZE=01
503B A9 24 LDO. PUSH BASE.24
503D 9A STO. (TOS-1)^ := (TOS)
503E C1 00 RBP. RETURN FROM BASE PROCEDURE.


(*$R-*)
PROGRAM ALLOCATION_EXAMPLES_ARRAYS; (* HYDE44 *)

VAR
I : ARRAY [0..10] OF INTEGER;
R : ARRAY [0..10] OF REAL;
J : INTEGER;

BEGIN
J := 1;
I [0] := 0;
R [0] := 1.1;
R [J] := 1.1;
I [J] := J;
END.

FILE: 27 SEG: 0 PROC: 1

JTAB FOR SUBROUTINE 1:
DATA SIZE: 68
PARAM SIZE: 4
EXIT AT: 5031
ENTER AT: 5000
PROC NUMBER: 1
LEXICAL LEVEL: 0
5000 D7 NOP. NOP
5001 D7 NOP. NOP
5002 01 SLDC. PUSH #0001
5003 AB 24 SRO. BASE.24 := (TOS)
5005 A5 03 LA0. PUSH #BASE.03
5007 00 SLDC. PUSH #0000
5008 A4 01 IXA. PUSH #((TOS-1)[TOS]). ARRAY SIZE=01
500A 00 SLDC. PUSH #0000
500B 9A STO. (TOS-1)^ := (TOS)
500C A5 0E LA0. PUSH #BASE.0E
500E 00 SLDC. PUSH #0000
500F A4 02 IXA. PUSH #((TOS-1)[TOS]). ARRAY SIZE=02
5011 B3 02 (02)8C 3F CD CC
LDC. PUSH 02 WORDS
5018 BD 02 STM. (TOS-1)^ := (TOS)^ 02 WORDS
501A A5 0E LA0. PUSH #BASE.0E
501C A9 24 LDO. PUSH BASE.24
501E A4 02 IXA. PUSH #((TOS-1)[TOS]). ARRAY SIZE=02
5020 B3 02 8C 3F CD CC
LDC. PUSH 02 WORDS
5026 BD 02 STM. (TOS-1)^ := (TOS)^ 02 WORDS
5028 A5 03 LA0. PUSH #BASE.03
502A A9 24 LDO. PUSH BASE.24
502C A4 01 IXA. PUSH #((TOS-1)[TOS]). ARRAY SIZE=01
502E A9 24 LDO. PUSH BASE.24
5030 9A STO. (TOS-1)^ := (TOS)
5031 C1 00 RBP. RETURN FROM BASE PROCEDURE.
5033 00 SLDC. PUSH #0000




PROGRAM ALLOCATION_EXAMPLES_SETS; (* HYDE45 *)

TYPE
SMALLI = 0..11;

VAR
S : SET OF SMALLI;
R : SET OF SMALLI;
Q : SET OF SMALLI;
B : BOOLEAN;
I : INTEGER;

BEGIN
S := []; (* EMPTY SET *)
S := [0];
S := [0,1];
S := [0,1,2];
S := [0,1,2,3];
S := [4,5,6,7];
S := [8,9,10,11];
R := [0];
Q := R + S;
Q := R - S;
Q := R * S;
I := 2;
B := I IN Q;
END.

FILE: 28 SEG: 0 PROC: 1

JTAB FOR SUBROUTINE 1:
DATA SIZE: 10
PARAM SIZE: 4
EXIT AT: 5059
ENTER AT: 5000
PROC NUMBER: 1
LEXICAL LEVEL: 0
5000 D7 NOP. NOP
5001 D7 NOP. NOP
5002 00 SLDC. PUSH #0000
5003 A0 01 ADJ. ADJ SET TO 01 WORDS
5005 AB 03 SRO. BASE.03 := (TOS)
5007 01 SLDC. PUSH #0001
5008 01 SLDC. PUSH #0001
5009 A0 01 ADJ. ADJ SET TO 01 WORDS
500B AB 03 SRO. BASE.03 := (TOS)
500D 03 SLDC. PUSH #0003
500E 01 SLDC. PUSH #0001
500F A0 01 ADJ. ADJ SET TO 01 WORDS
5011 AB 03 SRO. BASE.03 := (TOS)
5013 07 SLDC. PUSH #0007
5014 01 SLDC. PUSH #0001
5015 A0 01 ADJ. ADJ SET TO 01 WORDS
5017 AB 03 SRO. BASE.03 := (TOS)
5019 0F SLDC. PUSH #000F
501A 01 SLDC. PUSH #0001
501B A0 01 ADJ. ADJ SET TO 01 WORDS
501D AB 03 SRO. BASE.03 := (TOS)
501F C7 F0 00 LDCI. PUSH #00F0
5022 01 SLDC. PUSH #0001
5023 A0 01 ADJ. ADJ SET TO 01 WORDS
5025 AB 03 SRO. BASE.03 := (TOS)
5027 C7 00 0F LDCI. PUSH #0F00
502A 01 SLDC. PUSH #0001
502B A0 01 ADJ. ADJ SET TO 01 WORDS
502D AB 03 SRO. BASE.03 := (TOS)
502F 01 SLDC. PUSH #0001
5030 01 SLDC. PUSH #0001
5031 A0