The Commodore c64 is a computer where the Kernal (the OS if you will) supports load and store on tape and disk. The tape is a directly connected device where the Kernal provides all the code to steer the units mechanics, and parse the pulses to and from it. The disk drive is a lot more complex, and the connection here is via a serial bus. This is like communicating with another computer, and basically the 1541 disk drive IS like another computer. It has it’s own CPU (a 6502), two 6522 VIA circuits (like the 6526 CIAs in the C64 itself – here accessible on $1800 and $1c00), 2KB or RAM ($0000 to $07ff), and 16KB of ROM ($c000 to $ffff).

So thinking of the disk drive as a sparate computer, you can communicate with it and you can program it pretty much like the c64 itself.

My own master reference to the disk drive is the book “Inside Commodore DOS” (LINK) by Richard Immers and Gerald G Neufeld. I will take the liberty of extracting a few pieces from it in the following.

Key understandings

Disk organisation – the physical level

When you format the disks, you establish the track and sector structure on it so that the DOS in the drive has space where it can store data. The normal DOS uses 35 tracks where the outer (1 to 17) have 21 sectors and the inner ones gradually fewer. This is needed as the distance round trip and the associated of magnetic area is getting shorter the closer to the centre of the disk you get. It coiuld also be mentioned that the 1541 doesn’t use the sync hole of the disk (as the PC does). This means that the 1541 doesn’t know where on the track it it. One one track, sector 0 can be 12 o clock, but on the next it could be at 3 o clock.

On the physical layer, the disk block has a header block preceding the data block.

Going to the details of the two types, the header contains sync marks and then header data.

The data block structure is as follows:

Disk organisation – the logical level

As per the precious section, formatting a disk, the DOS establishes 683 block to be used. Track 18 is special; this is the directory track, and this is why we see 664 blocks free on a new disk after track 18 has been allocated for disk management.

Filestructure

A file (PRG, SEQ and USR) is stored in the 256 byte blocks of a sector in the form of two bytes that point to the next track/sector of the file and then 254 bytes of data. The last sector of a file uses $ff as track, to mark the end, and then the sector value is denoting the number of bytes used in the sector. Anything after the last byte in use if not part of the file – it’s wasted storage space.

In the first sector of a file (PRG), the first two data bytes define the load address of the file. So here we have track, sector, load low, load high and then 252 bytes of real file data. This is rather the file format than the disk format, but might be relevant to mention anyway.

One could also mention that files are not placed on the adjacent sectors on the track (1,2,3,4 etcetera). It’s rather 1,11,21. The number of sectors skipped is called interleaving and the reason for this is to optimise the process. The drive gets time between fetching the first sector before it is going to fetch the next, and you typically eliminate the risk for the drive to make a full new lap to find the next sector of the block chain. A hundred times better way would have been if Commodore would have populated the drives with enough RAM for reading a full track to RAM, as some of the parallel speeders have it. But they didn’t and it’s somewhat too late to fix that now …

Block Allocation Map (BAM)

Track 18 ($12), sector 0 is the Block Allocation Map (BAM). That is the systems storage space for telling what blocks are available for storage, and what has been occupied. This is also where the disk name and the disk id is stored.

The directory

The rest of the sectors on Track 18 ($12) – starting with sector 1 is the Directory.

Here each entry has 30 bytes, stored in eight 32 byte groups in a sector.

Byte 0 and 1: So if we assume that each entry is 32 bytes, the first two bytes of the first entry is a pointer to the next track/sector of the directory chain, as for files. In the other seven entries of that sector, these pad just two bytes that hold zero. They are unused and can be seen as padding.

Byte 2 is the type and here we must enter bit level to understand the value. Bit 7 indicates a closed file, and if this is zero then it’s unclosed. On the directory you see this marked with an asterisk (“*”)
Bit 6 indicated replacement which is a state the file has during a save and replace operation (which is a serious flawed function that you really shouldn’t use)
Bit 5 indicate a Locked file. On the directory you see this marked with a bracket(“<“)

Byte 3 and 4: This is the track/sector pointer to the first sector in the file.

Byte 5 to 20: The sixteen chars that forms the filename. There is no marker of the length of the file name. Instead it used inverted spaces for padding ($a0).

Byte 30 and 31: This is the marker for the size of the file. Please mind that this is merely cosmetic – what you see when listing the directory. In the normal case, this should correspond to the number of sectors allocated by the file, but you can change this to anything and there will be impact. File will still occupy the same number of blocks on the disk.

Byte 21 to 29 are not really important in most cases. First three are only used for REL files, next four are unused and then you have two used in the context of save and replace.

Talking to the drive from the computer

There are a number of commands you can issue over the command channel on the serial bus.

So here you can read and write to sectors, read and write to the disk drive ram, you can manage the BAM. A normal use case is that the computer program writes data to the drive RAM (“M-W”) and then run the program (“M-E”), to launch a proprietary communication protocol over the serial bus (like a speed loader). But a fully possible alternative is to use B-E to execute a program that is stored on disk.

You also have the B-R and B-W but these behave very strange and you do best using U1 and U2 instead.

Drive coding – the system way

The internal operation of the disk drive is that is stores jobcodes and is polled for jobs, and then the drive reports back on the status of the result. By doing this, you are exposing the full potential but with the full liability. It’s here fully possible that you request the stepper motor to go places where it shouldn’t and that this can cause problems.

So these are the job commands:

The are the possible results:

Working with the jobcodes, this is a handy table:

So reading the data on track 17, sector 0 to $0400 in the drive you place $80 in address $01, $11 (=17) on $06 and $00 on $07, and then scan $01 for the result.

Drive coding – even closer to the metal

It’s naturally fully possible to skip the overhead of working with job queues and just either call the Kernal routines yourself, or write your own DOS. In general memory is really scarce.

Page 0 is uses for storing Kernal values and the job code. If the drive has any sort of access to anything, then it will read and write to portions of this area. There are parts you can use, but this needs to be carefully managed.

Page 1 is allocated by the CPU as it’s stack. If you manage your code, you could ensure that stack usage is kept so that portions of the stack is available for your own code.

Page 2 is the command buffer.

Page 7 is allocated for the BAM. You can naturally use this area for storage but be aware that given control to the drive, then this area could be wiped. And there are times when the drive actually looks for stuff here.

So in practice, you are left with page 3,4,5 and 6. For reading and writing to the disk, you need one page for the data that goes in and out. Let’s assume you use page 6 for that. This means that your code needs to fit in 3*256 bytes. You need to code efficiently to get something proper done using only this restricted space.

Protections

The whole point with protections is that you bind data to a specific disk. If copied to another disk, the protection check would detect that it’s not sitting on the right disk and would hence refuse to work. On the computer side, the error checking routine could then be guarded by obfuscation, encryption and checksum to ensure it isn’t disabled or manipulated.

The simple protections are implemented by placing data outside of the general DOS area. Track 36-42 are typically fully accessible by the drive mechanics of the 1541, but it’s not supported by the DOS.

Next step was deliberate format error on the disk. In the early copy program, these error weren’t copied and would render the copy useless. We saw the advent of more and more complex copy programs (nibblers) and were able to copy more and more complex errors correctly.

But given the hardware restrictions of the c64, not everything can be reproduced. One trick was to place the sync header on one track, and then move the stepper to the adjacent track to read the data. Given that the 1541 doesn’t have any hardware for reading the synk hole, it is technically impossible to implement a general copier for this scenario.

The counter measure was then rather to implement “paramenters”. These are patches to the program in order to eliminate the protection, producing a standard DOS copy where the protection is disable. This is basically the same as cracking the game but in the context of a copy program where you need the original to generate the deprotected copy.

Thanks for rating this! Now tell the world how you feel - .
How does this post make you feel?
  • Excited
  • Fascinated
  • Amused
  • Bored
  • Sad
  • Angry