Most current TRS-80 Model I/III/4 emulators use one of three common file formats for emulated floppy disk media. The extension .DSK is usually used for all three formats, and some emulators transparently support two or three of them, while others support only one. The two most common formats both originated with emulators written by Jeff Vavasour, while the third originated with an emulator written by David Keil, so I'll take the liberty of giving the formats names that use their initials.
This document describes the JV1 and JV3 formats in complete detail and indicates (where known) which emulators support which. DMK is quite different from JV1 and JV3 and is not described in this document, but there is a description on David Keil's Web page. You probably don't need any of this information unless you are writing an emulator or working with unusual disks.
Note: In emulations of the WD1791/3 floppy disk controller used in the Model III and 4, it is best to present track 17 as being formatted with the standard deleted data address mark 0xF8. The WD1791/3 cannot write 0xFA, and upon reading, returns it as 0xFB. So Model III/4 operating systems use 0xF8 on the directory track instead.
typedef struct { SectorHeader headers1[2901]; unsigned char writeprot; unsigned char data1[]; SectorHeader headers2[2901]; unsigned char padding; unsigned char data2[]; } JV3;
The field padding is currently unused. It should contain 0xFF.
typedef struct { unsigned char track; unsigned char sector; unsigned char flags; } SectorHeader; #define JV3_DENSITY 0x80 /* 1=dden, 0=sden */ #define JV3_DAM 0x60 /* data address mark code; see below */ #define JV3_SIDE 0x10 /* 0=side 0, 1=side 1 */ #define JV3_ERROR 0x08 /* 0=ok, 1=CRC error */ #define JV3_NONIBM 0x04 /* 0=normal, 1=short */ #define JV3_SIZE 0x03 /* in used sectors: 0=256,1=128,2=1024,3=512 in free sectors: 0=512,1=1024,2=128,3=256 */ #define JV3_FREE 0xFF /* in track and sector fields of free sectors */ #define JV3_FREEF 0xFC /* in flags field, or'd with size code */
The track field gives both the physical track number on which the sector lies and the logical track number that is formatted in the sector's ID. Numbering starts from 0. Thus it is not possible to represent a copy-protected disk where sectors were deliberately formatted with incorrect track numbers. The format allows for 255 tracks (numbered 0 through 0xFE), but emulators may impose lower limits. You can expect at least 80 tracks to be supported; xtrs currently allows up to 96.
The sector field gives the logical sector number that is formatted into the sector's ID field. The physical order of sectors on the track is not explicitly represented, but xtrs (and perhaps other emulators) present them in the order they are recorded in the JV3 file; thus when a TRS-80 program formats a track with interleave and reads back the sector ids, they come back in the same interleaved order.
The flags field packs in a lot of information:
JV3_DAM value | Single density | Double density |
0x00 | 0xFB (Normal) | 0xFB (Normal) |
0x20 | 0xFA (User-defined) | 0xF8 (Deleted) |
0x40 | 0xF9 (User-defined) | Invalid; unused |
0x60 | 0xF8 (Deleted) | Invalid; unused |
The treatment of single density data address marks by the Western Digital 1771 and 179x controllers used in original TRS-80 hardware is rather inconvenient. The WD1771 (used in the TRS-80 Model I) is capable of writing any of the four DAMs shown above, and can distinguish amongst all four on reading. The WD179x (used in the Model III and 4), however, can write only 0xFB or 0xF8, and on reading, it cannot distinguish between 0xFB and 0xFA, or between 0xF8 and 0xF9.
TRS-80 operating systems differentiate directory sectors from ordinary data sectors by giving them different data address marks. Unfortunately, Model I TRSDOS uses 0xFA on directory sectors, which a WD179x cannot write and cannot distinguish from the 0xFB used on data sectors. Other Model I operating systems (such as LDOS) typically write 0xF8 on directory sectors but accept either 0xFA or 0xF8 when reading them, allowing them to read TRSDOS disks. Compatibility problems remain when attempting to use Model I TRSDOS disks on a Model III or 4, and when attempting to use LDOS disks with Model I TRSDOS.
An emulator can easily paper over these compatibility problems by behaving differently from the real hardware. All currently known emulators do so, making (at least) the following changes to strictly correct behavior: (1) If TRS-80 software attempts to write or format a 0xF8 DAM in single density, it is instead recorded in the JV3 header as 0xFA. (2) If TRS-80 software reads a single density sector that has the 0xFA DAM using an emulated WD179x, it is returned as 0xF8 instead. These changes hide all the DOS compatibility problems described in the previous paragraph.
It might be worthwhile to make precisely correct behavior available as a run-time emulator option, as this might allow more types of "protected" self-booting TRS-80 disks to work correctly. This option is available in xtrs version 3.2 and later, but not in any other known emulators.
The following tables summarize the DAM behavior of both the WD1771 and the WD179x. On the WD1771, read status bits 6,5 are used to report the DAM of the sector just read, while write command bits 1,0 select the DAM to write; on the WD179x, only read status bit 5 and write command bit 0 are significant: read status bit 6 is always 0, while write command bit 1 is used for a function unrelated to DAM selection. The behavior of these bits on the actual hardware is shown first in each cell; the modifications typically made by emulators follow in parentheses. It should be emphasized that WD1771 behavior shown here correctly describes the actual hardware, even though it contradicts the Western Digital data sheet; the data sheet erroneously transposes the column headings for status bits 6 and 5. As an aside, it seems likely that the engineers who designed the WD179x were misled by this error, as the value that the WD179x returns in bit 5 is the value that the WD1771 was documented as returning in bit 5, but that it actually returns in bit 6.
Read status bits 6,5:
DAM | WD1771 | WD179x single | WD179x double |
0xFB | 0,0 | 0,0 | 0,0 |
0xFA | 0,1 | 0,0 (0,1) | impossible |
0xF9 | 1,0 | 0,1 | impossible |
0xF8 | 1,1 | 0,1 | 0,1 |
Write command bits 1,0:
Bits | WD1771 | WD179x single | WD179x double |
0,0 | 0xFB | 0xFB | 0xFB |
0,1 | 0xFA | 0xF8 (0xFA) | 0xF8 |
1,0 | 0xF9 | 0xFB | 0xFB |
1,1 | 0xF8 (0xFA) | 0xF8 (0xFA) | 0xF8 |
Size | IBM size code in sector ID | JV3_SIZE field value if in use | JV3_SIZE field value if free |
128 | 00 | 1 | 2 |
256 | 01 | 0 | 3 |
512 | 02 | 3 | 0 |
1024 | 03 | 2 | 1 |
Thus, if a sector is in use, xor'ing its JV3_SIZE field with 1 gives the IBM size code that appears in its sector ID size field. If a sector is free, xor'ing its JV3_SIZE field with 2 gives its IBM size code. This representation was chosen because the original JV3 format supports only 256-byte sectors, always places zeros in the JV3_SIZE field for sectors that are in use, and always places ones in the field for sectors that are free.
If a sector header is not in use (free), its track and sector fields must contain 0xFF. Its flags field must contain 0xFC plus the appropriate free JV3_SIZE field value given above.
It's obvious, by the way, how to extend the format to more than two header blocks, but no known emulators support more than two. Having more than two is not useful to describe any floppy format that was supported by any real TRS-80. One block is sufficient for 5.25" DD drives, and two are sufficient for 8" drives, 5.25" HD drives, or 3.5" HD drives.
If the highest in-use sector is freed, the emulator should search backward for the new highest in-use sector, and should truncate the file to eliminate any sector data for trailing free sectors, as well as the second header block if it is no longer needed. Why do this? If the user reformats some sectors to be shorter or longer than they used to be, the nominal start of the second header block (as defined above) will shift. If you have garbage at the end of the file, and the block shifts to start somewhere in the garbage, then when you read the file back in again later, you'll have a second header block full of garbage that will confuse you.
JV1 | JV3 Basic features | JV3 Write protect | JV3 Second header block | JV3 Sizes other than 256 | JV3 Non-IBM kludge | DMK | |
Jeff Vavasour Model I v3.02u | Yes | No | No | No | No | No | No |
Jeff Vavasour Model III/4 v2.3 | No | Yes | No (future) | No | No | No | No |
Matthew Reed Model I/III v1.10 | Yes | Yes | No (future) | No (future) | No | No | No |
Matthew Reed Model 4 v1.01 | Yes | Yes | Yes | Yes | No | No | No |
xtrs Model I/III/4 v3.7 | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
WinTRS80 Model I/III/4 v1.02 | Yes | Yes | Respected but not generated | Yes | No | No | No |
Yves Lempereur Model I | Yes | No | No | No | No | No | No |
David Keil Model III/4 | Yes | Yes, with some limitations | Respected but not generated | No | Yes | No | Yes |
Additional information or corrections for this table are welcome.