Realms of Darkness - Progress Diary

Sat, 5. January 2002

Got new backup copies from Matt Larsen. According to Matt the game seems to be very much in its original (unplayed from) state. The disk images have been nibbled using mnib and converted to G64 using n2g.

The game consists of three disk sides:


Converting to D64 (using n2d) reveals that tracks 1-16 of the boot disk are custom format. Track 19 of the boot disk also seems to be either custom format or copy protection (more likely). The other tracks are 1541 DOS format.

For the adventure disks, tracks 1-14 are custom format, tracks 15-35 are 1541 DOS format.

The game is being booted by loading the 'HELLO' file from the 'boot.g64' disk. This is the only file on disk. The 'HELLO' files are identical on the ML and MA versions (2107 bytes), and also resides on identical blocks on the disks. It also matches with the file on three previously dumped boot disks owned by Scott Halverson.


The game is loaded by typing


HELLO default-loads to BASIC start $0801. 'RUN' hits a one-line BASIC header that jumps to $080d (10 SYS 2061).

Next, the actual loader code is moved in memory to $1000-$17ff.

Sun, 6. January 2002

Analyzed some more disk and utility routines at $1000-$17ff:

$16a9 - print disk error message

Sat, 20. April 2002

Analyzed the custom loader, used for tracks 1-14, sectors 1-14 on all three disks. Basically, sectors are recognized by a four byte GCR header giving a start byte, track, sector and disk ID. GCR bytes use a special encoding. After the header, the actual sector data is encoded by combining two consecutive GCR bytes ((byte1 AND 0x55) OR (byte 2 AND 0xaa)). The bits need to further be shifted around to get plain format bytes (shifted format probably speeds up fast byte transfer between 1541 and C64). Finally, a special GCR byte indicates the end of sector. Unfortunately, no checksum is available.

Wrote a tracer tool that converts a standard mnib file to D64, decoding custom sectors into standard D64 format. This should form the basis for comapring different versions of the game and also for producing a good remaster version.

Sun, 21. December 2003

Finally decided to have another go at Realms of Darkness. Let's hope for the best...

Mon, 29. December 2003

Analyzed quite a bit of the 'HELLO' loader. As the Kernel Area seems to be used for RAM, some crucial routines have been reprogrammed to provide standard serial communication with the 1541:

$1383: send listen (device 8)
$140f: send second after listen (secondary address 15)
$141f: send unlisten on serial bus
$142f: set serial data line low
$1438: set serial clock line high
$1441: set serial clock line low
$144a: set serial data line high
$1453: read serial line

A minimal command interpreter ($12ec / $12ef) is used for sending a sequence of commands to the floppy. By using "I", "M-W", and "M-E" some initial boot code is uploaded and executed on the 1541 ($0300). This boot routine loads a block (track 18, sector 2) into buffer 0 at $0300-$03ff, thus overwriting the actual boot code! This only works, because the first $27 bytes of the block are filled with $ea (NOP), so the old code can be safely overwritten and the RTI after the block-read will simply flow into the NOP-chain.

In custom DOS, the C64 sends of command codes to the 1541 by using a routine at $11a4: (A) contains the command code, the parameters (usually track and sector) are stored at $10fe (track) and $10ff (sector). The command routine save the actual command code 5o $10fd and writes all three bytes ($10fd-$10ff) to the 1541 by using a fast-write routine.

On the other side, the 1541 enters a command loop at $0403, waiting for the inital handshake followed by the command from the C64. The command is read into $04fd-$04ff, and the command at $04fd is acted upon.

job code    operation

  $00       read in 1541 DOS block
  $01       reset (kill fast loader)
  $02       turn off motor
  $04       retrieve disk number (0-3), or 0x20 for error
  $40       read in custom sector(s) or file (fileno)
  $80       write block of data to disk

Disks are distinguished by their disk IDs:

  A0   BOOT

Fri, 02. January 2004

Disk Layout for the Realm of Darkness disks:

Disk 1
Track Format Sectors
1-16 Custom 0-13
17 Standard 0-20
18 Standard 0-18
19 Copy protection N/A
20-24 Standard 0-18
25-30 Standard 0-17
31-35 Standard 0-16

Disk 2
Track Format Sectors
1-14 Custom 0-13
15-17 Standard 0-20
18-24 Standard 0-18
25-30 Standard 0-17
31-35 Standard 0-16

Disk 3
Track Format Sectors
1-14 Custom 0-13
15-17 Standard 0-20
18-24 Standard 0-18
25-30 Standard 0-17
31-35 Standard 0-16

Layout of a custom sector (Header)
Byte Contains GCR code
0 header begin 0x77
1 sector RGCR(0-32)
2 track RGCR(0-32)
3 disk number RGCR(0-3)

Layout of a custom sector (Data)
Byte Contains
0 data & 0x55
1 data & 0xaa
2 data & 0x55
3 data & 0xaa
... ...
510 data & 0x55
511 data & 0xaa

$1000: read file (A) to page (X)
$1003: read Track (X) Sector (Y) into page (A)
$1006: write page (A) to Track (X) Sector (Y)
$100f: turn off motor (?)
$1012: prompt for disk (A)
Adventure Disk 1: realms2.nib, disk2-5b.nib both have no read errors, but differ
Adventure Disk 2: realms3.nib

Sun, 04. January 2004

Improved the r2d tool which converts Realms NIB files to D64 format. Also, I adapted g2d into a new tool (rg2d) which lets me convert images in G64 format into D64. This way I can compare some more images from Matt Larsen, which I only have in G64 format.

State of Realms of Darkness images
Disk Version Images State Notes
BOOT 1.0 MA ? track 19 blank in G64
1.1 ML(disk1-1c) custom OK
1.2 ML(disk1-3b),ML(boot) custom match
1.1 ?
1.2 ML ?
1.? ML(disk2-5c) Good looks virgin
ADVENTURE 2 1.0 MA(adv2),ML(disk3-5c) Perfect virgin
1.1 ?
1.2 ML ? Cleaning restores bytes 162344-173824

Mon, 05. January 2004

Matt sent me a couple of zip archives with all of his converted Realms of Darkness disks. Some of these have converted very well with mnib, and have shown matches on all tracks.

I wrote a program (r1541) that lets me extract the program files from the custom loader area on the boot and adventure disks. This should be the first step on doing a proper version that may run from D64 images.
Note: When the load custom file routine ($1000) is used, the last block is fully read, regardless of the stop offset byte. This is most probably a bug, but may present a problem when remastering the files - either append the extra bytes to the files, or run the game with the shortened file.

Classification of boot disks:

disk1-1    V 1.1    all dumps match in custom area, dump c overall good
disk1-2    V 1.2    dump a bad, dump b boots, all dumps differ in custom area
disk1-3    V 1.2    all dumps match in custom area, dump b overall good
File BOOT 1.0 BOOT 1.1 BOOT 1.2 filesize
00 1.0 1306
01 1.0 3991
02 1.0 1.1 1.2 870, 867, 867
03 1.0 3991
04 1.0 1432
05 1.0 8193
06 1.0 4088
07 1.0 1.2 5825,5828
08 1.0 927
09 1.0 5972
10 1.0 4082
11 1.0 1.1 1010
12 1.0 998
13 1.0 1628
14 1.0 1433
15 1.0 1195
16 1.0 7168
17 1.0 928
18 1.0 1003
19 1.0 771

It seems the custom data area on Adventure disk 2 is identical throughout all versions (1.0, 1.1, 1.2). I compared the custom data from the 1.0 and 1.2 versions, and they matched 100%.

The same holds for Adventure disk 1. Both 1.0 and 1.2 versions contain the same information on the custom tracks. Also, the unknown version on disk2-5c matches with the other disks.

Tue, 06. January 2004

Continued to analyze the main menu program part. If Continue A Saved Game is selected, either Adventure 1 or Adventure 2 may be inserted. This means, games can be saved and continued from both adventure disks. Problaby this means, that in earlier game stages the game typically saves to Adventure disk 1, and in later stages the save slot on Adventure 2 is used.

In both cases, the actual save game data is located at Track 18, Sectors 4 - 11 and loaded to $0800-$0fff in memory.

Wed, 07. January 2004

Christmas is over for good, I had to go to work today ;-) Still, some progess:

When backing up the game, data is read both from Adventure disks 1 and 2. The data being copied consists of:

  Track 31, sectors 0-15
  Track 32, sectors 0-15
  Track 33, sectors 0-15
  Track 34, sectors 0-15
  Track 35, sectors 0-15

  Track 18, sectors 3-11
  Track 33, sectors 8-14
  Track 34, sectors 8-14
  Track 35, sectors 8-14
  (two blank pages of memory)

  Track 18, sectors 3-11
  Track 33, sectors 8-14
  Track 34, sectors 8-14
  Track 35, sectors 8-14
  (two blank pages of memory)
It is written to:
  Track 31, sectors 0-15  (data from BOOT)
  Track 32, sectors 0-15
  Track 33, sectors 0-15
  Track 34, sectors 0-15
  Track 35, sectors 0-15
  Track 20, sectors 0-15  (data from ADVENTURE 1)
  Track 21, sectors 0-15
  Track 22, sectors 0-15  (data from ADVENTURE 2)
  Track 23, sectors 0-15

'Reinitialize The Game' reads a file from the BOOT disk and uses the data contained in that file to re-write Tracks 33-35 on the two Adventure disks. However, it doesn't re-write the data on Track 18 contining the information, weather a game is currently in progress. Thus reinitializing still doesn't let you start over from scratch!

Thu, 08. January 2004

Found out some more information on the boot disk's layout: There's a second directory located at track 20, sector 0. This directory looks pretty much the same as the one from the custom area, except that it isn't encoded in the custom GCR format. The first file (0) in this area is the reinitialization data, used for reinitializing Adventure disks 1 and 2. Files reside from Track 20, Sector 1 up to Track 29, Sector 14.

Track 17 of the boot disk is mainly taken up with the "HELLO" program used for booting the game.

Similarly, the ADVENTURE 1 disk has a directory on Track 15, Sector 0. This directory references files in the disk's area Track 15, Sector 1 up to Track 21, Sector 10. ADVENUTRE 2 also has the directory on Track 15, Sector 0 with files ranging from Track 15, Sector 1 up to Track 24, Sector 12.

Sat, 10. January 2004

Found the actual copy protection which is called from the main menu file at $a063:

The copy protection code is located at $a4c0: By calling the $1003 (load block) routine with the Track parameter set to Track 19, the floppy code branches into a special subroutine at $0288.

The actual protection code routine is loaded from Track 18, Sector 17 from the boot disk into $0300-$03ff. It is started by jumping into memory location $0380, which decrypts the actual copy protection read routine.

The copy protection technique used consists of a combination of a Killer track (a large number of $ff bytes in a row) and a string of $00 bytes. The code used waits for the end of the (huge) Sync signal, then takes the time until the next Sync signal starts. This time is written into $0300 (high byte) and $0301 (low byte).

The whole $0300 block is transferred over to the C64 (encrypted), and the scrambled timing values are stored at $fe and $ff in the zero page.

My 'boot_p.g64' disk image (pulled from Mathew Allen's set) measures $02b6 cycles, this results in $fe=#$20, $ff=#$6b.

Matt Larsen's boot disks measures $02be cycles, resulting in $fe=#$20, $ff=#$7b.

Thu, 15. January 2004

Found the copy protection check which accesses the measured values at $fe/$ff. The routine is located at $62fe, and called at certain (random?) locations within the game. The measured data length is decrypted and is assumed to be valid if the value lies between $0287 < value <= $02b6. Comparing the boundary values with the ones measured on the disks above, Matt Larsen's boot disk exceeds the upper limit and thus the game is aborted (resulting in a flashing screen). Mathew Allen's boot disk lies exactly on the upper boundary which may be a lucky accident.

Now, it's interesting to analyze why Matt Larsen's disk images consistently show too many bytes between the syncs. My first guess was to blame mnib's bit rate detection, thus measuring the GCR hole with a wrong bit rate and ending up with too many bytes. However, I have checked that the bit rate in the nib images has correctly been to 0x10 (2), the correct value for track 19. So the only explanation is that Matt's floppy somehow ended up with the wrong value, probably because he used a 1571 which tends to read a very exact data stream (containing lots of true '00' bytes) rather than the 1541 which does a more sloppy job and drops in the occasional '1' bit more often. For some reason this leads to the 1541 actually reading fewer bytes than the 1571 for the whole '00'-passage.

Sun, 09. May 2004

To come up with a patch to remove the copy protection, one needs to regard the copy protection routine in Block 18/17: Entry is at 0x0380; memory is decoded from 0x0300-0x037f. Later, 0x0313 (decoded) is called, where the actual protection runs. To patch, one needs to change the code in 0x0313 to return the copy protection values of a correct run ($02b6) into X and Y and the jump over the old protection code right to 0x0356.

As the machine program is stored in encoded form (Exclusive-Or with 0x8f), we also need to do our patch XOR-ed with 0x8f. This leads to:

Removing the copy protection:
  1. Attach the boot disk (G64 format) in VICE
  2. read in Track 18, Sector 17: br 12 11 c000
  3. change data at $c014: > c014 8d 2f 39 c3 d9 8c
  4. write back block to disk: bw 12 11 c000

Thu, 20. May 2004

Preparing a cleaned-up version of the 1.2 disks for the upcoming Gamebase release.

As a basis, I use a set of disk images from Matt Larsen, copied with mnib on 04-January-2002. The disk images are boot.g64, adv1.g64, adv2.g64.

rg2d indicates that all three disk images are error free, as far as I can judge.

I copy boot.g64 to boot_p.g64. This will become my patched boot disk. Next I start up x64 and attach boot_p.g64 to drive 8. I do the patch described in the 09-May-2004 entry. I do a copy of boot_p.g64 (b.g64) as well as adv1.g64 and adv2.g64 (a1.g64 and a2.g64)

I put VICE into NTSC-M mode and boot up b.g64. From the Main Menu, I select 'Continue A Saved Game'. Adventure disk a1.g64 doesn't contain a saved game, but a2.g64 does. I let the game enter the adventure by loading the saved game from a2.g64.

In order to return the game to an initial state, I walk around in the game world, and let monsters attack me. Eventually the party gets killed and I am transferred back to the boot disk (b.g64). From the Main Menu, I enter the Character Utilities. I delete all characters currently in the Character Directory (they are dead, anyway).

Back in the Main Menu, I choose Disk Utilities and Reinitialize The Game. This reinitializes disks a1.g64 and a2.g64. I rename b.g64, a1.g64 and a2.g64 to meaningful disk image names (RoD_Boot.g64, RoD_Adv1.g64, RoD_Adv2.g64) and ZIP them up to

I perform some tests on the game:

Everything seems to work fine, so I send the version to Matt :-)