Atari Protected Disk Image Format







ATP Working Group


1. Introduction. 3

Tracks. 3

Sectors. 4

2. Sector Status Errors. 4

Sector Ok. 5

Data CRC Error. 5

Record Type "Deleted"5

Record Type "Deleted" + CRC error. 5

Lost Data. 5

Lost Data + CRC Error. 6

Lost Data + Record Type "Deleted"6

Lost Data + Record Type "Deleted" + CRC Error. 6

Record Not Found. 7

Record Not Found + Header CRC Error. 7

3. Double sectors. 7

4. Sector timing. 8

Sector skewing. 8

Track skewing. 8

Sector interleaving. 8

Reference Sector. 8

5. Weak data. 9

Weak Header. 9

Weak Sector Data. 9

Weak data in ATP. 10

6. Track density change. 11

7. ATP File Format. 12

ATP File. 12

ATP Disk Data. 12

ATP Header. 12

ATP Track. 12

ATP Sector. 13


ATP Timing Data. 15

8. References. 16

9. History. 16

Version 1.6. 16



1. Introduction

Why do we need the ATP format? The current format for Atari 8-bit disk images is ATR. With this format it is not possible to store copy protected disks. The new ATP format should make this possible.


So, what about the APE PRO format? Thus far it seems that the author of APE will not release any information about his 'PRO' format. Therefore we're simply forced to develop our own format. We probably need more supported protection features and since we propose an 'open' format this will be possible.



Some disks are around 20 years old and who knows how many original copies or backups (original backups!) there are? It's time to make good copies where possible. With the ATP we will be able to store these copies on modern media. This is not an easy task, since many different copy protections are invented during the years. Not to mention the copy-programs which can make copies of protected disks, but not of the copy-program itself!


Each disk contains a number of tracks. On the Atari drives the track number ranges from 0 to 39. The disk drives use the so called soft sectored disks. This means that a track can be seen as just one long data stream. With help of markers the FDC knows where to find the right sector data in this stream. In general a track looks like this:


Start of track:

 Marker | Index mark

 Gap #1 | Post index mark


NOTE: The XF551 & 1050 both ignore the index (if written to disk)


First sector:

 Gap #2 | Pre address mark

 Marker | Address mark

 Data   | Sector header + CRC

 Gap #3 | Post address mark

 Marker | Data address mark

 Data   | Sector data + CRC

 Gap #4 | Post data mark


The first sector is followed by the rest of the sectors which all have the same structure as the first sector. Note that the first sector doesn't have to be numbered as sector #1.


A disk sector consists of the following three parts:


- Sector header field (also called 'address field')

  . Gap bytes (should be at least 4 Bytes in SD, 8 Bytes in MD/DD)

  . Header mark (also called 'address mark')

  . Track number (0 - 39)

  . Side (0 or 1) the sector belongs to (the FDC ignores this byte)

  . Sector number (also called 'sector id')

  . Sector size (0=128, 1=256, 2=512, 3=1024 Byte)

  . Two CRC checksum bytes (used by the FDC to check the last 4 Bytes)

- Sector data field (also called 'data field')

  . Gap bytes

  . Data mark

  . Sector data (SD/ED: 128 Bytes, DD: 256 Byte)

  . Two CRC-Bytes (used by the FDC to check the sector data)

- Gap

  . Gap bytes until the next sector

2. Sector Status Errors

After reading a protected sector, the status command returns the Floppy Disk Controller (FDC) status byte for the last read/written sector. A perfectly good sector has a status of $FF. The status byte can then be checked for error bits to identify the protection. Sometimes the sector data is also verified.


Each bit of the FDC-status byte is low active and has the following meaning:


Bit 7: /DOOR   No Disk in drive (NOT valid on XF551 drives)

Bit 6: /WP     Write protected (valid only after a write command)

Bit 5: /RT     Record Type "Deleted" in the sector header field (address field)

Bit 4: /RNF    Record not found (sector not found)

Bit 3: /CRC    Checksum-Error (in the sector header or sector data field)

Bit 2: /LD     Lost data: the drive-CPU didn't send/receive all data to/from the FDC

Bit 1: /DRQ    Data request: the FDC asks for a byte read/written to its data register

Bit 0: /BUSY   The FDC is busy, the last FDC-Command hasn't completed yet


For copy protection methods only the bits 5,4,3,2 are useful. The other bits are 1 after a read sector command. Bit 1 should always be 1, in combination with LD (Bit 2 = 0), Bit 1 is undefined (depends on the ROM revision of the disk drive).


The possible FDC-status bytes are described below.

Sector Ok

This is a perfectly normal sector without error.

Status: $FF (255)

Data? : yes

Data CRC Error

The two CRC bytes of the sector data field aren't correct. This error is created by issueing a write command to the FDC and abort it before the complete sector is written. The sector data may be correct, and some programs verify the complete sector data since it is VERY difficult to abort the write command exactly when the CRC bytes are written by the FDC.

Status : $F7 (247)

Data?  : yes


Great American Road Race, track 32-39

MS Copy, sector 68

Record Type "Deleted"

Also called "Deleted Data Mark".  The data mark of the sector is set to "Deleted". The error can be created by issueing a write sector command to the FDC with Bit 0 set. The FDC treats a "Deleted Data Mark" not as an error, it's simply a possibility to mark sectors, so a DOS can detect if a sector is free or in use by a file. This is not used by the ATARI-XL/XE system nor PCs.


Status: 223 ($DF)

Data? : yes

Examples: ?

Record Type "Deleted" + CRC error

This is a combination of a deleted data mark and CRC error. This type of error is often caused by a so called 'weak' or 'fuzzy' sector. According to the manual of the Super Archiver a 'Fuzzy' or 'Phantom' sector is "a sector in which the data does not regain constant. It will almost always carry with it a CRC error, because of the unstable data."


Status: 215 ($D7)

Data? : yes


MS Copy, sector 67, 71

Lost Data

Also called "Long Sector". The size byte in the sector header field is set to 1 or greater (meaning at least 256 Byte) on a SD/ED disk (resp. at least 512 byte on a DD disk). So the FDC will read 256 bytes or more data bytes, but the drive ROM will read only 128 data bytes, which results in lost data. In some ROM revisions the ROM doesn't reset the FDC after an error, this will result in Bit 1 (DRQ-Bit) set to zero. Therefore a program must allow $FB or $F9 or it won't work on all ATARI drives. This error must be created at format time since the

size byte is in the sector header field (the sector header field cannot be modified when writing a sector). To create the sector data field without a CRC error the complete "long" sector (256 Byte) must be written by the drive ROM to the FDC. Or the drive ROM has to calculate the correct CRC.


Status: $F9 (249) or $FB (251)

Data? : yes


MS Copy, sector 56-59, 64


Not displayed by 'disk mapper' (a program used to analyse disks).

Lost Data + CRC Error

Same as "Lost Data" but the sector also contains a CRC error. The CRC part of

this error is created the same way as in a simple CRC ($F7).


Status: $F1 (241) or $F3 (243)

Data? : yes


MS Copy, sector 72

Lost Data + Record Type "Deleted"

Same as "Lost Data" but the sector also contains a "Record Type" error. The "Record Type" part of this error is created the same way as in a simple "Record Type Deleted" ($DF).


Status: $D9 (217) or $DB (219)

Data? : yes

Examples: ?

Lost Data + Record Type "Deleted" + CRC Error

Same as "Lost Data" but the sector also contains a "Record Type" error and a CRC error. The "Record Type" + CRC part of this error is created the same way as in a simple "Record Type" + CRC ($D7).


Status: $D1 (209) or $D3 (211)

Data? : yes


MS Copy, sector 37

Record Not Found

Also called "Missing Sector". The drive doesn't find the sector at all. There are three possible causes:


-        Sector not found

There is no header field with the correct sector number.


-        Track not found

A header field with the correct sector-nr but a wrong track-nr was found.


-        Data not found

A correct header field was found but the data mark is missing.


This error is created at format time by not writing the sector, the data mark or writing a wrong track number in the header field.


Status: $EF (239)

Data? : no


Printshop, track 5

Ultra Copier, track 7-39

MS Copy, sector 73-90

Record Not Found + Header CRC Error

Also called "Address CRC error" or "Sector ID CRC error". The sector header field has a bad CRC. This error is created by writing two zero bytes instead of the CRC bytes at format time.


Status: $E7

Data? : no

Examples: ?

3. Double sectors

The sector ID can appear more than once on the same track. Often double sectors are placed 180 degrees away from eachother. That way you can read the same sector twice and get two sets of data. With the right timing it's possible to read data from multiple sectors with the same sector ID on one track. Short sectors can be used to get more sectors on a track to create complex protection schemes.



Printshop, track 5

4. Sector timing

Sector skewing

The time between two consequetive sectors on a track is called skew-alignment. This space between sectors is infuenced by the placement of sectors on the track and the amount of gap bytes.

Track skewing

Since the Atari drives don't use the index hole to indicate the start of the first sector in a track, the alignment of two tracks can be different. A track skew of 0 means that the tracks are aligned, e.g. the first sector from the first track starts at the same position as the first sector on the second track. With a track skew of 180 degrees, the first sector on the second track start 180 degrees later on that track.


According to the Super Archiver manual: "Skewing is the measurement of time between two different tracks. For example, if sector 1 on track 0 is located 180 degrees away from sector 1 on track 1, a program could read each sector 1, time it, and compare it to a given value. This sort of protection is used, and is quite effective against sector copiers, but not some enhancements (such as the Super Archiver 1050!). Normally what is done is to line up the first sector of each track (assuming all tracks in the skew section are the same format).

The software will read that first sector of each track in a certain order, and compare it with a certain timing. For example, the same time it takes to read sector 1 on track 0 twice should be the same time it takes to read sector 1 on track 0, and sector 1 on track 1. When stepping more than 2 tracks, allowances need to be made for step rates, because certain drives step slower than others. Usually measuring the time between one track the very next one is effective

enough. ... "

Sector interleaving

By sector interleaving sectors are placed on the disk in the most efficient order. When the sectors are positioned in a normal sequence (0, 1, 2 etc) the interleave factor is 1. With a sector interleave of 2, the next sector is placed two positions behind the first sector. Protection schemes can use custom interleaving or completely different sector layouts. With help of timing information, the original can be detected.

Reference Sector

Since the index hole is not used, a track has no clear beginning or end. Some protections measure the time between two sectors, but we don't know which sectors the program will check. Therefore we will use a specific sector to measure the time between this sector and all other sectors on the disk.


First definition:

The start of sector "1" on track "0" denotes the "virtual index hole" and serves as a physical reference position for all other sectors on the disk.


There can be double sectors on the disk, but it's not likely that sector 1 - the bootsector - is a double sector (contact us if you have an example!). Just to be on the save side we will use the following definition:


Final definition:

The first, unique (i.e. non-double) sector on the disk (starting with track 0) is called the "reference sector". This "reference sector" serves as an absolute reference point (for all timing measurements). The start of this "reference sector" denotes the "virtual index hole".


If there is no unique sector on the disk (i.e. each sector number occurs at least twice within a track), the user may choose any sector of the disk as a reference sector.

5. Weak data

Some protections are based on weak data bits. These unstable bits can produce several errors when they appear in the sector header and/or corrupt data bits in the sector data part.

Weak Header

If the FDC cannot read the sector header correctly, the header-CRC checksum fails and the FDC will report 'sector not found'. If the header data is corrupted all the time, this will look like a completely missing sector. But if the sector header could be read correctly, the FDC will also read the sector data (without any weak bits in this case) and return 'sector read successful'.


STEFAN: This only happens if the data CRC is correct. This very unlikely.

A floppy emulator could simulate this case by randomly returning:

1) sector OK (plus valid data)

2) sector not found

STEFAN: No, this error may be combined with Header-CRC or Record-Type status


FREDDY: This is not clear. Is the above simply wrong?


Examples: ?

Weak Sector Data

In case of a broken bit in the data part, the data CRC-check will fail and the FDC will return partially correct data.


This can be simulated by returning:

1) sector OK (plus valid data)

2) data CRC error (plus some partially garbled data)


In the combination of weak bits in the data part and in the header part, we would end up with three possible cases that a floppy emulator has to return:


1) sector OK (plus valid data)

Header and data read in OK.


2) data CRC error (plus some partially garbled data)

Header was OK, but broken bit(s) in data part.


3) sector not found

Broken bit in header and the FDC didn't have any chance to look into the data part - so it doesn't matter if there are any further broken/weak bits.


Examples: ?

Weak data in ATP

Weak data is still unresolved for ATP. However, with the chunk-based format it is possible to add this information later.


Here are the discussions regarding weak data:


DISCUSSION: Multiple corrupted parts in one sector

MATTHIAS: I'm still not sure how we would have to store the information about the corrupted part. Theoretically it could be possible to have multiple weak parts (or holes) within a single sector. Eg: first 16 bytes ok, then 16 weak bytes, then 16 bytes ok, then 16 weak bytes... I'm not sure if this would make sense, but if the FDC is actually able to re-synchronize and read in some correct data between garbled parts, we would also have to reflect this in our

data structures.

STEFAN: Theoretically, it is possible:

A bit on the disk is represented by one (0-Bit) or two (1-Bit) low-to-high or high-to-low transitions (in SD). So the FDC can synchronize at least on every 0-bit. But if you write no transitions for some time on the disk (this can't be done by the FDC), the FDC won't find the transitions to synchronize and may deliver random bytes, the behaviour of the FDC isn't defined in that case, different revisions of the FDC may behave different. So I would define the ATP-Format to have this option but not include it in the first revision.

MATTHIAS: ACK. IMO (for the first version) we should just store the starting position of the weak data, and assume that the FDC won't be able to re-sync and treat the following data as random data.

This means: we simply store a single byte (denoting the number of stable bytes)

and then either deliver:

* the correct sector data, or

* the first N "stable" bytes plus (128-N) random bytes and indicate a checksum-error


DISCUSSION: Weak bits in FM and MFM


In FM mode both clock and data bits are written to disk. So if the FDC misses a bit, it will probably read the following clock bits as data bits and vice versa. OTOH if the FDC won't get reliable clock bits (since it is actually reading data bits) it _could_ possibly re-sync to the clock bits. [BTW: since the clock bits are "1", the FDC will report $FF data 'til the end of the

sector or 'til it re-syncs].


In MFM mode only data bits are written to disk, but with a special encoding so that there will not be long runs of zero-bits. Using this encoding clock bits aren't necessary any more and it can reliably re-generate the clock (and sync to it). A missed bit will impact the decoding in two ways: First, the current bit is lost (obviously) and second, due to the MFM encoding scheme, it will also affect decoding of the following data bits. IMHO this could show up a some garbeled bits and then (when the FDC re-synced again) correct data that is shifted by several bits (not necessarily bytes!).

6. Track density change

Some tracks of the disk are formatted in SD, some in ED. This copy protection is effective, since there is no backup program that checks the density of a track. And what is more, this protection method can be created by standard ATARI disk drives: Simply format a disk in SD, than reformat it in ED but switch off the disk drive after 30 formatted tracks. Now the disk seems to be in ED but the last 10 tracks are in SD. To access these tracks write protect

the disk, then execute a format command, which will switch the drive in SD read mode. And maybe the density can be changed in the middle of a track.


Examples: ?

7. ATP File Format

The ATP file format is a so-called chunk-based format. A chunk is simply a 4 bytes identifier and a 4 bytes length field followed by the amount of data bytes indicated by the length.


<UINT32> : 32-bit 'big endian' unsigned integer

ATP File

<ATP_File> := <FORM_ID> <FORM_Length> <ATP_Disk_Data> <ATP_CRC> <ATP_Timing_Data>




<FORM_Length> := <UINT32>

The length of the ATP Disk-Data chunk (file length - 8), rest of file.

ATP Disk Data

<ATP_Disk_Data> := <ATP_Disk_Data_ID> <ATP_Disk_Data_Length> <ATP_Header> <ATP_Track>*


<ATP_Disk_Data_ID> := "ATP1"

The magic code to identify an ATP version 1 disk image.


<ATP_Disk_Data_Length> := <UINT32>

Length of ATP Disk Data chunk in bytes.

ATP Header

<ATP_Header> := <ATP_Header_ID> <ATP_Header_Length> <Nr_Of_Tracks> <Disk_Info>


<ATP_Header_ID> := "INFO"


<ATP_Header_Length> := <UINT32>

Length of the ATP header info chunk (2 bytes).


<Nr_Of_Tracks> := <UINT32>

Number of tracks (chunks) in this ATP Disk Data chunk.


<Disk_Info> := <UINT32>

    { #$00 | #$01 }

bit 0, Disk write protection (0=off, 1=on)

ATP Track

The order of ATP sectors from each ATP track is important. It should be used to store different interleaves and place double sectors on a track.

When a certain track number is not available in the ATP file, it means that it's an unformatted track (drive emulators should produce error 239 ($EF) - missing sector).


Note: The tracks have to be stored in strictly ascending order! Sectors within a track also have to be stored in strictly ascending order, sorted by their sector starting time (= sector position on disk)!


Each block of track data contains a track header followed by a sequence of sector blocks:


<ATP_Track> := <ATP_Track_ID> <ATP_Track_Length> <ATP_Track_Data>


<ATP_Track_ID> := "TRAK"


<ATP_Track_Length> := <UINT32>

Length of this ATP_Track chunk in bytes.


<ATP_Track_Data> := <Track_Number> <Nr_Of_Sectors> <Density> <ATP_Sector>*


<Track_Number> := <UINT32>

Track number.


<Nr_Of_Sectors> := <UINT32>

Number of sectors on the track.


<Density> := <UINT32>

    { #$00 | #$01 }

bit 0, Density - 0=FM (SD,DD), 1=MFM (ED)

ATP Sector

Sectors which are not listed are simply 'missing' sectors and thus produce an error 239 ($EF).


Each sector block has a sector header followed by an amount of data bytes:


<ATP_Sector> := <ATP_Sector_ID> <ATP_Sector_Length> <Sector>


<ATP_Sector_ID> := "SECT"


<ATP_Sector_Length> := <UINT32>

Length of ATP Sector chunk in bytes.


<Sector> := <Sector_Nr> <Sector_Size> <Sector_Status> <Sector_Data>*


<Sector_Nr> := <UINT32>

Sector ID Number.


<Sector_Size> := <UINT32>

Sector Size (in bytes).


<Sector_Status> :=

    { #$D3 | #$D7 | #$DB | #$DF | #$E7 | #$F3 | #$F7 | #$FB | #$FF }

Sector Status

    $D3               FDC error $D3 (%11010011) Lost Data + Record Type "Deleted" + CRC Error

    $D7               FDC error $D7 (%11010111) Record Type "Deleted" + CRC error

    $DB               FDC error $DB (%11011011) Lost Data + Record Type "Deleted"

    $DF                FDC error $DF (%11011111) Record Type "Deleted"

    $E7                FDC error $E7 (%11100111) Record Not Found + Header CRC Error

    $F3                FDC error $F3 (%11110011) Lost Data + CRC Error

    $F7                FDC error $F7 (%11110111) Data CRC Error

    $FB                FDC error $FB (%11111011) Lost Data

    $FF                FDC status $FF (%11111111) Sector Ok


Note that status $EF (Record Not Found) is not allowed here, because these missing sectors should not be listed in the track data at all.


<Sector_Data> := <BYTE>*

Sector data bytes indicated by Sector Size.


A CRC32 checksum of the data part of the ATP_Data chunk (excluding the 4-byte chunk id and 4-byte chunk length) is stored for quick image comparison.


<ATP_CRC> := <CRC_ID> <CRC_Length> <32_bit_CRC>


CRC_ID := "CRC1"


CRC_Length := <UINT32>

Length of ATP CRC chunk. Currently fixed at 4 (bytes).


<32_bit_CRC> := <UINT32>

A checksum calculated over the data part of the ATP_Data chunk.

ATP Timing Data

Starting with ATP version 1.6 the timing information has been separated from the sector data/status information and is stored in an own chunk. This is mainly to ease quick image comparison by using the CRC checksum. Since the timing information might be a little bit "fuzzy", it would break quick CRC-checks. On the other hand, there is a high possibility that

two images are identical if they contain exactly the same image data.


Note: the timing data chunk must contain exactly the same number of tracks as the ATP Disk Data chunk and each track must contain exactly the same number of sectors as the corresponding tracks in the ATP disk data chunk!


<ATP_Timing_Data> := <ATP_Timing_ID> <ATP_Timing_Length> <Timing_Nr_Of_Tracks> <ATP_Track_Timing>*


<ATP_Timing_ID> := "TIM1"

Magic code to identify an ATP version 1 timing data chunk.


<ATP_Timing_Lenght> := <UINT32>

Length of the ATP Timing Data chunk in bytes.


<Timing_Nr_Of_Tracks> := <UINT32>

Number of tracks (chunks) in the ATP Timing Data (MUST be the same as <Nr_Of_Tracks> in Disk Data chunk.


<ATP_Track_Timing> := <ATP_Track_Timing_ID> <ATP_Track_Timing_Length> <Track_Timing_Data>


<ATP_Track_Timing_ID> := "TTI1"


<ATP_Track_Timing_Length> := <UINT32>

Length of ATP track timing chunk.


<Track_Timing_Data> := <Timing_Track_Number> <Timing_Nr_Of_Sectors> <Sector_Timing>*


<Timing_Track_Number> := <UINT32>

Track number.


<Timing_Nr_Of_Sectors> := <UINT32>

Number of sectors in the current track.


<Sector_Timing> := <Sector_Start_Time> <Sector_Time_Length>

Timing information for each sector.


<Sector_Start_Time> := <UINT32>

The time distance in microseconds between the start-time of the reference sector and the start-time of the current sector.


<Sector_Time_Length> := <UINT32>

Length of the sector (from the start of the header til the end of the data block) in microseconds.

8. References

- Atari 8-bit floppy disk formats, Mega Magazine #6, F.Offenga

- The Super Archiver manual, CSS

- The Happy Enhancement article series, Atari Magazin

- The Hyper-XF manual, Stefan Dorndorf

9. History

Version 1.6


- Adapted the ATP file structure for separate timing information


- Changed all informational values from <BYTE> into <UINT32>


- Removed FDC error $EF (239, Record Not Found) from <Sector_Status>.

Sectors not listed in <Track_Data> should produce this error.


- Removed FDC error values with the same meaning:

  + $D1 : FDC error $D1 Same as $D3

  + $D9 : FDC error $D9 Same as $DB

  + $F1 : FDC error $F1 Same as $F3

  + $F9 : FDC error $F9 Same as $FB