A program saved with SAVE to the cassette is written by the computer’s Kernal routines in one
fixed format, which every Commodore 8-bit machine with a Datasette shares in idea (the pulse lengths of the C16
and PET differ; this page and the explorer use the C64’s). It is built up in five steps from the
short, medium and long pulses.
Bits and bytes
A bit is two pulses: short then medium is a 0, medium then short is a 1.
A byte is 20 pulses: a byte marker of long then medium, then 8 data bits, least significant bit first, then a parity bit chosen so that the nine bits together contain an odd number of 1s. A byte whose parity is wrong, or one of whose pairs is not short-medium or medium-short, is a read error.
| Pulses | Meaning |
|---|---|
| long, medium | Byte marker: a byte follows |
| short, medium | Bit 0 |
| medium, short | Bit 1 |
| long, short | End-of-data marker |
Block
A block is a leader, the bytes below, and the end-of-data marker:
0Leader: short pulsesvaries- Countdown:
89 88 … 81(first copy) or09 08 … 01(second copy)9 bytes - Block bytes: a header or a programvaries
- Checksum: the XOR of the block bytes1 byte
- End-of-data marker: long, short2 pulses
Leader
A long run of short pulses, which gives the loader time to settle and tells it a block is coming. The explorer takes a run of at least 16 short pulses followed by a byte marker as a leader and shows its length.
Countdown
The first nine bytes of a block count down: $89 $88 $87 $86 $85 $84 $83 $82 $81 in the first
copy and $09 … $01 in the second. They synchronise the loader on byte boundaries and say which copy of
the block this is. The explorer reads a block as the Kernal’s only when its first byte is $89 or
$09.
Checksum
The last byte before the end marker is the exclusive OR of all the block bytes after the countdown and before the checksum. It is not computed over the countdown. A wrong checksum makes the loader report a read error, and the explorer flags the block.
End-of-data marker
A long pulse then a short one, in the place where the next byte marker would be. It ends the block. After it the next block’s leader begins.
Header blocks and data blocks
A file is a header block, then its data, and every block is written twice, so a program makes four blocks on the tape: header, header again, program, program again. The second copy is there so the loader can use it when the first is damaged; the explorer uses the first copy whose checksum is right and no byte had a read error, and says when the copies differ.
| Offset | Size | Description |
|---|---|---|
0 | 1 | Type: 1 BASIC program that loads at the start of BASIC (relocatable), 3 program that loads at its own address, 4 header of a data file, 5 end of tape. |
1 | 2 | Start address, little-endian. |
3 | 2 | End address, little-endian: the address after the last byte. |
5 | 16 | File name, padded with spaces. |
21 | 171 | The rest of the 192 bytes: not used; it is not interpreted here. |
A header block is 192 bytes, so on the tape it is a 9-byte countdown, 192 bytes and a checksum: 202 bytes, each
20 pulses. The block after the header of a type 1 or 3 program holds the program bytes, as many as the end address
minus the start address, and nothing else: it is the program with no load address in front. The explorer builds
a .prg from the start address and these bytes and shows it with the BASIC listing and the
disassembly that file gets, following the SYS in a BASIC stub.
A data file (type 4) is followed by blocks of 192 bytes, each starting with the byte 2 and then 191
bytes of data. The explorer lists the file and its size; it does not show the data as text. A header of type 5 marks
the end of the tape.
Turbo loaders: not decoded
Many commercial tapes start with a short program in this format, and then load the rest with a faster routine of their own, which uses other pulse lengths and its own byte coding. All four real C64 tapes checked here are like that: four Kernal blocks (header and program, each twice), then more than half of the tape in the other kind of pulses. The explorer counts those pulses as outside any standard block, shows their lengths in the histogram, and does not decode them: each loader would need its own description.