package disk import ( "fmt" "io/ioutil" "log" "path" ) // Dos33writeTable is the list of valid DOS 3.3 bytes. // See [UtA2 9-27 Disk Data Formats]. var Dos33writeTable = [64]byte{ 0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6, 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3, 0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, } var Dos33readTable [256]int func init() { for i := 0; i < 256; i++ { Dos33readTable[i] = -1 } for i, j := range Dos33writeTable { Dos33readTable[j] = int(i) } } // Dos33LogicalToPhysicalSectorMap maps logical sector numbers to physical ones. // See [UtA2 9-42 - Read Routines]. var Dos33LogicalToPhysicalSectorMap = []byte{ 0x00, 0x0D, 0x0B, 0x09, 0x07, 0x05, 0x03, 0x01, 0x0E, 0x0C, 0x0A, 0x08, 0x06, 0x04, 0x02, 0x0F, } // Dos33PhysicalToLogicalSectorMap maps physical sector numbers to logical ones. // See [UtA2 9-42 - Read Routines]. var Dos33PhysicalToLogicalSectorMap = []byte{ 0x00, 0x07, 0x0E, 0x06, 0x0D, 0x05, 0x0C, 0x04, 0x0B, 0x03, 0x0A, 0x02, 0x09, 0x01, 0x08, 0x0F, } // ProDosLogicalToPhysicalSectorMap maps logical sector numbers to pysical ones. // See [UtA2e 9-43 - Sectors vs. Blocks]. var ProDosLogicalToPhysicalSectorMap = []byte{ 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F, } // ProDosPhysicalToLogicalSectorMap maps physical sector numbers to logical ones. // See [UtA2e 9-43 - Sectors vs. Blocks]. var ProDosPhysicalToLogicalSectorMap = []byte{ 0x00, 0x08, 0x01, 0x09, 0x02, 0x0A, 0x03, 0x0B, 0x04, 0x0C, 0x05, 0x0D, 0x06, 0x0E, 0x07, 0x0F, } // PreNybble converts from 256 8-bit bytes to 342 6-bit "nybbles". // The equivalent DOS routine, PRE.NYBBLE, would put the 256 MSB-6's into one buffer, // and the 86 LSB-2's into a separate one, but we place the 86 after the 256. // See http://www.txbobsc.com/aal/1981/aal8106.html#a5 func PreNybble(source []byte) (target [342]byte) { if len(source) != 256 { panic(fmt.Sprintf("PreNybble expects 256 bytes as input, got %d", len(source))) } y := 2 for i := 0; i < 3; i++ { for x := 0; x < 86; x++ { // DEY y = (y + 0xff) & 0xff a := source[y] target[y] = a >> 2 target[x+256] = (target[x+256]<<2 + a&2>>1 + a&1<<1) & 0x3F } } return target } // PostNybble converts from 342 6-bit "nybbles to 255 8-bit bytes. // The equivalent DOS routine, POST.NYBBLE, would read the 256 MSB-6's from one buffer, // and the 86 LSB-2's from a separate one, but we place the 86 after the 256. // See http://www.txbobsc.com/aal/1981/aal8106.html#a5 func PostNybble(source []byte) (target [256]byte) { if len(source) != 342 { panic(fmt.Sprintf("PostNybble expects 342 bytes as input, got %d", len(source))) } x := 0 for y := 0; y < 256; y++ { // DEX x = (x + 85) % 86 a := source[256+x] source[256+x] = a >> 2 target[y] = source[y]<<2 + a&2>>1 + a&1<<1 } return target } // appendSyncs appends the given number of sync bytes to the slice, and returns the resulting slice. func appendSyncs(target []byte, count int) []byte { for i := 0; i < count; i++ { target = append(target, 0xFF) } return target } // append44 appends bytes using the 4-4 encoded format used for volume, track, sector, checksum. func append44(target []byte, b byte) []byte { return append(target, 0xAA|(b>>1), 0xAA|b) } // appendAddress appends the encoded sector address to the slice, and returns the resulting slice. func appendAddress(target []byte, t, s, v byte) []byte { target = append(target, 0xD5, 0xAA, 0x96) target = append44(target, v) target = append44(target, t) target = append44(target, s) target = append44(target, v^t^s) target = append(target, 0xDE, 0xAA, 0xEB) return target } // appendData appends the encoded sector data to the slice, and returns the resulting slice. func appendData(target, source []byte) []byte { target = append(target, 0xD5, 0xAA, 0xAD) nybbles := PreNybble(source) checksum := byte(0) for i := 341; i >= 256; i-- { target = append(target, Dos33writeTable[nybbles[i]^checksum]) checksum = nybbles[i] } for i := 0; i < 256; i++ { target = append(target, Dos33writeTable[nybbles[i]^checksum]) checksum = nybbles[i] } target = append(target, Dos33writeTable[checksum]) target = append(target, 0xDE, 0xAA, 0xEB) return target } // dosToNybbleSector appends a single 256-byte DOS sector in .nyb format, and returns the slice. func dosToNybbleSector(target, source []byte, t, s, v byte, preSync, intraSync int) []byte { if len(source) != 256 { panic(fmt.Sprintf("dosToNybbleSector expects 256 bytes, got %d", len(source))) } target = appendSyncs(target, preSync) target = appendAddress(target, t, s, v) target = appendSyncs(target, intraSync) target = appendData(target, source) return target } // dos16ToNybbleTrack converts a 16-sector dos image of a track to a nybblized track. func dos16ToNybbleTrack(source []byte, t, v byte, preSync, intraSync int, sectorOrder []byte) []byte { trackSync := NYBBLE_TRACK_BYTES - 16*(preSync+NYBBLE_ADDRESS_BYTES+intraSync+NYBBLE_DATA_BYTES) target := make([]byte, 0, NYBBLE_TRACK_BYTES) target = appendSyncs(target, trackSync) for s := byte(0); s < 16; s++ { start := 256 * int(sectorOrder[s]) target = dosToNybbleSector(target, source[start:start+256], t, s, v, preSync, intraSync) } if len(target) != NYBBLE_TRACK_BYTES { panic(fmt.Sprintf("Tracks should be %d bytes, go %d", NYBBLE_TRACK_BYTES, len(target))) } return target } func dos16ToNybbleTracks(source []byte, v byte, preSync, intraSync int, sectorOrder []byte) (tracks [][]byte, err error) { if preSync+intraSync > MAX_PRE_INTRA_SYNC { return nil, fmt.Errorf("preSync(%d) + intraSync(%d) cannot be more than %d bytes", preSync, intraSync, MAX_PRE_INTRA_SYNC) } for t := byte(0); t < 35; t++ { start := DOS_TRACK_BYTES * int(t) trackBytes := source[start : start+DOS_TRACK_BYTES] track := dos16ToNybbleTrack(trackBytes, t, v, preSync, intraSync, sectorOrder) tracks = append(tracks, track) } return tracks, nil } func NybbleToDos16(nyb *Nybble, sectorOrder []byte) (bytes []byte, err error) { // Save current disk position pos := nyb.GetPos() defer nyb.SetPos(pos) var trackBytes [DOS_TRACK_BYTES]byte for track := byte(0); track < NUM_TRACKS; track++ { nyb.SetHalfTrack(track * 2) seen := uint16(0) for i := 0; i < 16; i++ { sector, err := readOneSector(nyb) if err != nil { return nil, err } if sector.Sector > 15 { return nil, fmt.Errorf("Found unexpected sector number on track %d: %d", track, sector.Sector) } if seen&(1< 0 { return nil, fmt.Errorf("Found sector %d twice on track %d", sector.Sector, track) } seen |= 1 << sector.Sector start := 256 * int(sectorOrder[sector.Sector]) copy(trackBytes[start:start+256], sector.Data[:]) } bytes = append(bytes, trackBytes[:]...) } return bytes, nil } // DiskToFile saves a Nybble disk to a given filename. func DiskToFile(filename string, disk *Nybble) error { ext := path.Ext(filename) switch ext { case ".nib": var bytes []byte for _, track := range disk.Tracks { bytes = append(bytes, track...) } err := ioutil.WriteFile(filename, bytes, 0644) if err != nil { return err } return nil case ".dsk", ".do", ".po": sectorOrder := Dos33PhysicalToLogicalSectorMap if ext == ".po" { sectorOrder = ProDosPhysicalToLogicalSectorMap } bytes, err := NybbleToDos16(disk, sectorOrder) if err != nil { return err } if err = ioutil.WriteFile(filename, bytes, 0644); err != nil { return err } return nil } return fmt.Errorf("Cannot save to file %s: unexpected extension %s", filename, ext) } // DiskFromFile loads a Nybble disk, given a filename. // defaultVolume is the disk volume used if the file doesn't encode it, 0 for default (254). func DiskFromFile(filename string, defaultVolume byte) (disk *Nybble, err error) { volume := defaultVolume if volume == 0 { volume = DEFAULT_VOLUME } ext := path.Ext(filename) bytes, err := ioutil.ReadFile(filename) if err != nil { return nil, err } switch ext { case ".dsk", ".do", ".po": sectorOrder := Dos33PhysicalToLogicalSectorMap if ext == ".po" { sectorOrder = ProDosPhysicalToLogicalSectorMap } if len(bytes) != DOS_DISK_BYTES { return nil, fmt.Errorf("Expected %d bytes in file %s, got %d", DOS_DISK_BYTES, filename, len(bytes)) } tracks, err := dos16ToNybbleTracks(bytes, volume, DEFAULT_PRESYNC, DEFAULT_INTRASYNC, sectorOrder) if err != nil { return nil, err } nyb := NewNybble(tracks) return nyb, nil case ".nib": if len(bytes) != NYBBLE_DISK_BYTES { return nil, fmt.Errorf("Expected %d bytes in file %s, got %d", NYBBLE_DISK_BYTES, filename, len(bytes)) } var tracks [][]byte for i := 0; i < NUM_TRACKS; i++ { start := NYBBLE_TRACK_BYTES * i tracks = append(tracks, bytes[start:start+NYBBLE_TRACK_BYTES]) } nyb := NewNybble(tracks) return nyb, nil } return nil, fmt.Errorf("Unknown suffix (not .dsk, .do, or .po): %s", filename) } type Sector struct { Volume byte Track byte Sector byte Data [256]byte } func read44(nyb *Nybble) byte { return (nyb.Read()<<1 | 1) & nyb.Read() } func readOneSector(nyb *Nybble) (result Sector, err error) { count := int(NYBBLE_TRACK_BYTES) OUTER: for ; count > 0; count-- { if nyb.Read() != 0xD5 { continue } if nyb.Read() != 0xAA { continue } if nyb.Read() != 0x96 { continue } result.Volume = read44(nyb) result.Track = read44(nyb) result.Sector = read44(nyb) checksum := read44(nyb) count -= 10 if checksum != result.Volume^result.Track^result.Sector { continue } for nyb.Read() != 0xD5 { count-- } if nyb.Read() != 0xAA { continue } if nyb.Read() != 0xAD { continue } var raw [342]byte xor := byte(0) for i := 341; i >= 256; i-- { count-- bi := Dos33readTable[nyb.Read()] if bi < 0 { continue OUTER } b := byte(bi) ^ xor raw[i] = b xor = b } for i := 0; i < 256; i++ { count-- bi := Dos33readTable[nyb.Read()] if bi < 0 { continue OUTER } b := byte(bi) ^ xor raw[i] = b xor = b } if int(xor) != Dos33readTable[nyb.Read()] { log.Print("Checksum error") continue } result.Data = PostNybble(raw[:]) return result, nil } return result, fmt.Errorf("Unable to read sector") }