package disk import ( "bytes" "crypto/sha256" "encoding/hex" "errors" "fmt" "io" "io/ioutil" "os" "strings" ) //import "math/rand" const STD_BYTES_PER_SECTOR = 256 const STD_TRACKS_PER_DISK = 35 const STD_SECTORS_PER_TRACK = 16 const STD_SECTORS_PER_TRACK_OLD = 13 const STD_DISK_BYTES = STD_TRACKS_PER_DISK * STD_SECTORS_PER_TRACK * STD_BYTES_PER_SECTOR const STD_DISK_BYTES_OLD = STD_TRACKS_PER_DISK * STD_SECTORS_PER_TRACK_OLD * STD_BYTES_PER_SECTOR const PRODOS_800KB_BLOCKS = 1600 const PRODOS_800KB_DISK_BYTES = STD_BYTES_PER_SECTOR * 2 * PRODOS_800KB_BLOCKS const PRODOS_400KB_BLOCKS = 800 const PRODOS_400KB_DISK_BYTES = STD_BYTES_PER_SECTOR * 2 * PRODOS_400KB_BLOCKS const PRODOS_SECTORS_PER_BLOCK = 2 const PRODOS_BLOCKS_PER_TRACK = 8 const PRODOS_800KB_BLOCKS_PER_TRACK = 20 const PRODOS_BLOCKS_PER_DISK = 280 const PRODOS_ENTRY_SIZE = 39 const TRACK_NIBBLE_LENGTH = 0x1A00 const TRACK_COUNT = STD_TRACKS_PER_DISK const SECTOR_COUNT = STD_SECTORS_PER_TRACK const HALF_TRACK_COUNT = TRACK_COUNT * 2 const DISK_NIBBLE_LENGTH = TRACK_NIBBLE_LENGTH * TRACK_COUNT const DISK_PLAIN_LENGTH = STD_DISK_BYTES const DISK_2MG_NON_NIB_LENGTH = DISK_PLAIN_LENGTH + 0x040 const DISK_2MG_NIB_LENGTH = DISK_NIBBLE_LENGTH + 0x040 type DSKContainer []byte func Checksum(b []byte) string { sum := sha256.Sum256(b) return hex.EncodeToString(sum[:]) } type SectorOrder int var DOS_33_SECTOR_ORDER = []int{ 0x00, 0x07, 0x0E, 0x06, 0x0D, 0x05, 0x0C, 0x04, 0x0B, 0x03, 0x0A, 0x02, 0x09, 0x01, 0x08, 0x0F, } var DOS_32_SECTOR_ORDER = []int{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, } var PRODOS_SECTOR_ORDER = []int{ 0x00, 0x08, 0x01, 0x09, 0x02, 0x0a, 0x03, 0x0b, 0x04, 0x0c, 0x05, 0x0d, 0x06, 0x0e, 0x07, 0x0f, } var LINEAR_SECTOR_ORDER = []int{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, } var NIBBLE_62 = []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 NIBBLE_53 = []byte{ 0xab, 0xad, 0xae, 0xaf, 0xb5, 0xb6, 0xb7, 0xba, 0xbb, 0xbd, 0xbe, 0xbf, 0xd6, 0xd7, 0xda, 0xdb, 0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xed, 0xee, 0xef, 0xf5, 0xf6, 0xf7, 0xfa, 0xfb, 0xfd, 0xfe, 0xff, } var identity = map[string]DiskFormat{ "99b900080a0a0a990008c8d0f4a62ba9098527adcc03854184408a4a4a4a4a09": GetDiskFormat(DF_DOS_SECTORS_13), "01a527c909d018a52b4a4a4a4a09c0853fa95c853e18adfe086dff088dfe08ae": GetDiskFormat(DF_DOS_SECTORS_16), "0138b0034c32a18643c903088a29704a4a4a4a09c08549a0ff844828c8b148d0": GetDiskFormat(DF_PRODOS), } const ( SectorOrderDOS33 SectorOrder = iota SectorOrderDOS32 SectorOrderDOS33Alt SectorOrderProDOS SectorOrderProDOSLinear ) func (so SectorOrder) String() string { switch so { case SectorOrderDOS32: return "DOS" case SectorOrderDOS33: return "DOS" case SectorOrderDOS33Alt: return "DOS Alternate" case SectorOrderProDOS: return "ProDOS" case SectorOrderProDOSLinear: return "Linear" } return "Linear" } type DiskFormatID int const ( DF_NONE DiskFormatID = iota DF_DOS_SECTORS_13 DF_DOS_SECTORS_16 DF_PRODOS DF_PRODOS_800KB DF_PASCAL DF_RDOS_3 DF_RDOS_32 DF_RDOS_33 DF_PRODOS_400KB DF_PRODOS_CUSTOM ) type DiskFormat struct { ID DiskFormatID bpd int spt int uspt int tpd int } func GetDiskFormat(id DiskFormatID) DiskFormat { return DiskFormat{ID: id} } func GetPDDiskFormat(id DiskFormatID, blocks int) DiskFormat { return DiskFormat{ ID: id, bpd: blocks, tpd: 80, spt: blocks / 80, uspt: blocks / 80, } } func (f DiskFormat) String() string { switch f.ID { case DF_NONE: return "Unrecognized" case DF_DOS_SECTORS_13: return "Apple DOS 13 Sector" case DF_DOS_SECTORS_16: return "Apple DOS 16 Sector" case DF_PRODOS: return "ProDOS" case DF_PASCAL: return "Pascal" case DF_PRODOS_400KB: return "ProDOS 400Kb" case DF_PRODOS_800KB: return "ProDOS 800Kb" case DF_RDOS_3: return "SSI RDOS 3 (16/13/Physical)" case DF_RDOS_32: return "SSI RDOS 32 (13/13/Physical)" case DF_RDOS_33: return "SSI RDOS 32 (16/16/PD)" case DF_PRODOS_CUSTOM: return fmt.Sprintf("ProDOS Custom (%d SPT, %d TPD)", f.SPT(), f.TPD()) } return "Unrecognized" } func (df DiskFormat) BPD() int { switch df.ID { case DF_RDOS_3: return 222 case DF_RDOS_32: return 222 case DF_RDOS_33: return 280 case DF_DOS_SECTORS_13: return 222 case DF_DOS_SECTORS_16: return 280 case DF_PRODOS: return 280 case DF_PASCAL: return 280 case DF_PRODOS_800KB: return 1600 case DF_PRODOS_400KB: return 800 case DF_PRODOS_CUSTOM: return df.bpd } return 16 // fallback } func (df DiskFormat) USPT() int { switch df.ID { case DF_RDOS_3: return 13 case DF_RDOS_32: return 13 case DF_RDOS_33: return 16 case DF_DOS_SECTORS_13: return 13 case DF_DOS_SECTORS_16: return 16 case DF_PRODOS: return 16 case DF_PASCAL: return 16 case DF_PRODOS_800KB: return 40 case DF_PRODOS_400KB: return 20 case DF_PRODOS_CUSTOM: return df.uspt } return 16 // fallback } func (df DiskFormat) SPT() int { switch df.ID { case DF_RDOS_3: return 16 case DF_RDOS_32: return 13 case DF_RDOS_33: return 16 case DF_DOS_SECTORS_13: return 13 case DF_DOS_SECTORS_16: return 16 case DF_PRODOS: return 16 case DF_PASCAL: return 16 case DF_PRODOS_800KB: return 40 case DF_PRODOS_400KB: return 20 case DF_PRODOS_CUSTOM: return df.spt } return 16 // fallback } func (df DiskFormat) TPD() int { switch df.ID { case DF_RDOS_3: return 35 case DF_RDOS_32: return 35 case DF_RDOS_33: return 35 case DF_DOS_SECTORS_13: return 35 case DF_DOS_SECTORS_16: return 35 case DF_PRODOS: return 35 case DF_PASCAL: return 35 case DF_PRODOS_800KB: return 80 case DF_PRODOS_400KB: return 80 case DF_PRODOS_CUSTOM: return df.tpd } return 35 // fallback } type Nibbler interface { SetNibble(offset int, value byte) GetNibble(offset int) byte } type DSKWrapper struct { Data DSKContainer Layout SectorOrder CurrentTrack int CurrentSector int SectorPointer int Format DiskFormat RDOSFormat RDOSFormat Filename string //Nibbles []byte Nibbles Nibbler CurrentSectorOrder []int WriteProtected bool NibblesChanged bool } // SectoreMapperDOS33 handles the interleaving for dos sectors func SectorMapperDOS33(wanted int) int { return wanted switch wanted { case 0: return 0 case 13: return 1 case 11: return 2 case 9: return 3 case 7: return 4 case 5: return 5 case 3: return 6 case 1: return 7 case 14: return 8 case 12: return 9 case 10: return 10 case 8: return 11 case 6: return 12 case 4: return 13 case 2: return 14 case 15: return 15 } return -1 // invalid sector } func SectorMapperDOS33Alt(wanted int) int { switch wanted { case 0: return 0 case 13: return 1 case 11: return 2 case 9: return 3 case 7: return 4 case 5: return 5 case 3: return 6 case 1: return 7 case 14: return 8 case 12: return 9 case 10: return 10 case 8: return 11 case 6: return 12 case 4: return 13 case 2: return 14 case 15: return 15 } return -1 // invalid sector } func SectorMapperDOS33bad(wanted int) int { return wanted switch wanted { case 0: return 0 case 1: return 13 case 2: return 11 case 3: return 9 case 4: return 7 case 5: return 5 case 6: return 3 case 7: return 1 case 8: return 14 case 9: return 12 case 10: return 10 case 11: return 8 case 12: return 6 case 13: return 4 case 14: return 2 case 15: return 15 } return -1 // invalid sector } // SectoreMapperProDOS handles the interleaving for dos sectors func SectorMapperProDOS(wanted int) int { switch wanted { case 0: return 0 case 1: return 2 case 2: return 4 case 3: return 6 case 4: return 8 case 5: return 10 case 6: return 12 case 7: return 14 case 8: return 1 case 9: return 3 case 10: return 5 case 11: return 7 case 12: return 9 case 13: return 11 case 14: return 13 case 15: return 15 } return -1 // invalid sector } func (d *DSKWrapper) SetTrack(t int) error { if t >= 0 && t < d.Format.TPD() { d.CurrentTrack = t d.SetSectorPointer() return nil } return errors.New("Invalid track") } // SetSector changes the sector we are looking at func (d *DSKWrapper) SetSector(s int) error { if s >= 0 && s < d.Format.USPT() { d.CurrentSector = s d.SetSectorPointer() return nil } return errors.New("Invalid sector") } func (d *DSKWrapper) HuntVTOC(t, s int) (int, int) { for block := 0; block < len(d.Data)/256; block++ { data := d.Data[block*256 : block*256+256] var v VTOC v.SetData(data, (block / s), (block % s)) if v.GetTracks() == t && v.GetSectors() == s { return (block / s), (block % s) } } return -1, -1 } // SetSectorPointer calculates the pointer to the current sector, taking into // account the sector interleaving of the DSK image. func (d *DSKWrapper) SetSectorPointer() { track := d.CurrentTrack sector := d.CurrentSector isector := sector switch d.Layout { case SectorOrderDOS33Alt: isector = SectorMapperDOS33Alt(sector) case SectorOrderDOS33: isector = SectorMapperDOS33(sector) case SectorOrderProDOS: isector = SectorMapperProDOS(sector) } d.SectorPointer = (track * d.Format.SPT() * STD_BYTES_PER_SECTOR) + (STD_BYTES_PER_SECTOR * isector) } func (d *DSKWrapper) UpdateTrack(track int) { d.NibblesChanged = true } func (d *DSKWrapper) ChecksumDisk() string { return Checksum(d.Data) } func (d *DSKWrapper) ChecksumSector(t, s int) string { d.SetTrack(t) d.SetSector(s) return Checksum(d.Data[d.SectorPointer : d.SectorPointer+256]) } func (d *DSKWrapper) IsChanged() bool { return d.NibblesChanged } // func (d *DSKWrapper) GetNibbles() []byte { // return d.Nibbles // } // Read is a simple function to return the current pointed to sector func (d *DSKWrapper) Read() []byte { ////fmt.Printf("---> Reading track %d, sector %d\n", d.CurrentTrack, d.CurrentSector) return d.Data[d.SectorPointer : d.SectorPointer+256] } func (d *DSKWrapper) Write(data []byte) { ////fmt.Printf("---> Reading track %d, sector %d\n", d.CurrentTrack, d.CurrentSector) l := len(data) if l > STD_BYTES_PER_SECTOR { l = STD_BYTES_PER_SECTOR } for i, v := range data { if i >= l { break } d.Data[d.SectorPointer+i] = v } } // Seek is a convienience function to go straight to a particular track & sector func (d *DSKWrapper) Seek(t, s int) error { var e error e = d.SetTrack(t) if e != nil { return e } e = d.SetSector(s) return e } func (d *DSKWrapper) SetData(data []byte) { // for i, v := range data { // d.Data[i] = v // } d.Data = data } func NewDSKWrapper(nibbler Nibbler, filename string) (*DSKWrapper, error) { f, e := os.Open(filename) if e != nil { return nil, e } data, e := ioutil.ReadAll(f) f.Close() if e != nil { return nil, e } w, e := NewDSKWrapperBin(nibbler, data, filename) return w, e } func NewDSKWrapperBin(nibbler Nibbler, data []byte, filename string) (*DSKWrapper, error) { if len(data) != 232960 && len(data) != STD_DISK_BYTES && len(data) != STD_DISK_BYTES_OLD && len(data) != PRODOS_400KB_DISK_BYTES && len(data) != PRODOS_400KB_DISK_BYTES+64 && len(data) != PRODOS_800KB_DISK_BYTES && len(data) != PRODOS_800KB_DISK_BYTES+64 && len(data) != STD_DISK_BYTES+64 { return nil, errors.New("Incorrect disk bytes") } this := &DSKWrapper{} this.SetData(data) this.Filename = filename this.Layout = SectorOrderDOS33 this.CurrentSectorOrder = DOS_33_SECTOR_ORDER this.Nibbles = nibbler this.WriteProtected = false this.Identify() return this, nil } func (dsk *DSKWrapper) GetNibbles() []byte { n := make([]byte, DISK_NIBBLE_LENGTH) for i, _ := range n { n[i] = dsk.Nibbles.GetNibble(i) } return n } func (dsk *DSKWrapper) SetNibbles(data []byte) { if dsk.Nibbles == nil { return } for i, v := range data { dsk.Nibbles.SetNibble(i, v) } } func (dsk *DSKWrapper) Identify() { dsk.Format = GetDiskFormat(DF_NONE) var hint DiskFormat dsk.Filename = strings.ToLower(dsk.Filename) switch { case strings.HasSuffix(dsk.Filename, ".po"): hint = GetDiskFormat(DF_PRODOS) case strings.HasSuffix(dsk.Filename, ".do"): hint = GetDiskFormat(DF_DOS_SECTORS_16) default: hint = GetDiskFormat(DF_DOS_SECTORS_16) } is2MG, Format, Layout, zdsk := dsk.Is2MG() if is2MG { ////fmt.Println("repacked", len(zdsk.Data)) dsk.SetData(zdsk.Data) dsk.Layout = Layout dsk.Format = Format return } isPD, Format, Layout := dsk.IsProDOS() if isPD { if Format == GetDiskFormat(DF_PRODOS) { dsk.Format = GetDiskFormat(DF_PRODOS) dsk.Layout = Layout switch dsk.Layout { case SectorOrderProDOS: dsk.CurrentSectorOrder = PRODOS_SECTOR_ORDER case SectorOrderProDOSLinear: dsk.CurrentSectorOrder = PRODOS_SECTOR_ORDER case SectorOrderDOS33: dsk.CurrentSectorOrder = DOS_33_SECTOR_ORDER } dsk.SetNibbles(dsk.Nibblize()) return } else { dsk.Format = GetDiskFormat(DF_PRODOS_800KB) dsk.Layout = Layout switch dsk.Layout { case SectorOrderProDOS: dsk.CurrentSectorOrder = PRODOS_SECTOR_ORDER case SectorOrderProDOSLinear: dsk.CurrentSectorOrder = PRODOS_SECTOR_ORDER case SectorOrderDOS33: dsk.CurrentSectorOrder = DOS_33_SECTOR_ORDER } dsk.SetNibbles(make([]byte, 232960)) return } } isRDOS, Version := dsk.IsRDOS() if isRDOS { dsk.RDOSFormat = Version switch Version { case RDOS_3: dsk.Format = GetDiskFormat(DF_RDOS_3) dsk.Layout = SectorOrderDOS33Alt dsk.CurrentSectorOrder = DOS_33_SECTOR_ORDER dsk.SetNibbles(dsk.Nibblize()) case RDOS_32: dsk.Format = GetDiskFormat(DF_RDOS_32) dsk.Layout = SectorOrderDOS33Alt dsk.CurrentSectorOrder = DOS_33_SECTOR_ORDER dsk.SetNibbles(make([]byte, 232960)) // FIXME: fix nibbles case RDOS_33: dsk.Format = GetDiskFormat(DF_RDOS_33) dsk.Layout = SectorOrderProDOS dsk.CurrentSectorOrder = PRODOS_SECTOR_ORDER dsk.SetNibbles(dsk.Nibblize()) } return } isAppleDOS, Format, Layout := dsk.IsAppleDOS() if isAppleDOS { ////fmt.Printf("Format: %s\n", Format.String()) dsk.Format = Format dsk.Layout = Layout switch Layout { case SectorOrderProDOS: ////fmt.Println("Sector Order: ProDOS") dsk.CurrentSectorOrder = PRODOS_SECTOR_ORDER case SectorOrderProDOSLinear: ////fmt.Println("Sector Order: Linear") dsk.CurrentSectorOrder = LINEAR_SECTOR_ORDER case SectorOrderDOS33: ////fmt.Println("Sector Order: DOS33") dsk.CurrentSectorOrder = DOS_33_SECTOR_ORDER case SectorOrderDOS32: ////fmt.Println("Sector Order: DOS32") //dsk.CurrentSectorOrder = DOS_32_SECTOR_ORDER case SectorOrderDOS33Alt: ////fmt.Println("Sector Order: Alt Linear") dsk.CurrentSectorOrder = LINEAR_SECTOR_ORDER } dsk.SetNibbles(dsk.Nibblize()) return } fp := hex.EncodeToString(dsk.Data[:32]) if dfmt, ok := identity[fp]; ok { dsk.Format = dfmt //fmt.Println(dsk.Format.String()) } //fmt.Printf("Disk name: %s\n", dsk.Filename) // 1. NIB if len(dsk.Data) == 232960 { //fmt.Println("THIS IS A NIB") dsk.Format = GetDiskFormat(DF_DOS_SECTORS_16) dsk.SetNibbles(dsk.Data) return } // 2. Wrong size if len(dsk.Data) != STD_DISK_BYTES && len(dsk.Data) != STD_DISK_BYTES_OLD && len(dsk.Data) != PRODOS_800KB_DISK_BYTES { //fmt.Println("NOT STANDARD DISK") dsk.Format = GetDiskFormat(DF_NONE) dsk.SetNibbles(make([]byte, 232960)) return } // 3. DOS 3x Disk vtoc, e := dsk.AppleDOSGetVTOC() //fmt.Println(vtoc.GetTracks(), vtoc.GetSectors()) if e == nil && vtoc.GetTracks() == 35 { //bps := vtoc.BytesPerSector() t := vtoc.GetTracks() s := vtoc.GetSectors() //fmt.Printf("DOS Tracks = %d, Sectors = %d\n", t, s) if t == 35 && s == 16 { dsk.Layout = SectorOrderDOS33 dsk.CurrentSectorOrder = DOS_33_SECTOR_ORDER dsk.Format = GetDiskFormat(DF_DOS_SECTORS_16) dsk.SetNibbles(dsk.Nibblize()) } else if t == 35 && s == 13 { dsk.Layout = SectorOrderDOS32 dsk.CurrentSectorOrder = DOS_32_SECTOR_ORDER dsk.Format = GetDiskFormat(DF_DOS_SECTORS_13) dsk.SetNibbles(make([]byte, 232960)) } return } //fmt.Println("Trying prodos / pascal") isPAS, volName := dsk.IsPascal() if isPAS && volName != "" { dsk.Format = GetDiskFormat(DF_PASCAL) dsk.Layout = SectorOrderDOS33 dsk.CurrentSectorOrder = DOS_33_SECTOR_ORDER dsk.SetNibbles(dsk.Nibblize()) return } dsk.CurrentSectorOrder = PRODOS_SECTOR_ORDER if dsk.Format == GetDiskFormat(DF_PRODOS) && len(dsk.Data) == PRODOS_800KB_DISK_BYTES { dsk.Format = GetDiskFormat(DF_PRODOS_800KB) } //fmt.Println(dsk.Format.String()) switch dsk.Format.ID { case DF_PRODOS: vdh, e := dsk.PRODOSGetVDH(2) ////fmt.Printf("Blocks = %d\n", vdh.GetTotalBlocks()) if e == nil && vdh.GetStorageType() == 0xf && vdh.GetTotalBlocks() == 280 { dsk.Format = GetDiskFormat(DF_PRODOS) dsk.Layout = SectorOrderDOS33 dsk.CurrentSectorOrder = DOS_33_SECTOR_ORDER dsk.SetNibbles(dsk.Nibblize()) //fmt.Println("THIS IS A PRODOS DISKETTE, DOS Ordered") return } else { dsk.Format = GetDiskFormat(DF_PRODOS) dsk.Layout = SectorOrderDOS33Alt ////fmt.Println("Try again") vdh, e = dsk.PRODOSGetVDH(2) if e == nil && vdh.GetStorageType() == 0xf && vdh.GetTotalBlocks() == 280 { dsk.CurrentSectorOrder = PRODOS_SECTOR_ORDER dsk.SetNibbles(dsk.Nibblize()) //fmt.Println("THIS IS A PRODOS DISKETTE, ProDOS Ordered") return } } case DF_PRODOS_800KB: vdh, e := dsk.PRODOS800GetVDH(2) //fmt.Printf("Blocks = %d\n", vdh.GetTotalBlocks()) if e == nil && vdh.GetStorageType() == 0xf && vdh.GetTotalBlocks() == 1600 { dsk.Format = GetDiskFormat(DF_PRODOS_800KB) dsk.Layout = SectorOrderDOS33 dsk.CurrentSectorOrder = DOS_33_SECTOR_ORDER //dsk.SetNibbles(dsk.nibblize()) dsk.SetNibbles(make([]byte, 232960)) //fmt.Println("THIS IS A PRODOS DISKETTE, DOS Ordered") return } } switch hint.ID { case DF_PRODOS: dsk.Format = GetDiskFormat(DF_PRODOS) dsk.Layout = SectorOrderProDOS dsk.CurrentSectorOrder = PRODOS_SECTOR_ORDER dsk.SetNibbles(dsk.Nibblize()) //fmt.Println("VTOC read failed, will nibblize anyway...") case DF_DOS_SECTORS_16: //fmt.Println("VTOC read failed, will nibblize anyway...") dsk.Layout = SectorOrderProDOS dsk.CurrentSectorOrder = DOS_33_SECTOR_ORDER dsk.Format = GetDiskFormat(DF_DOS_SECTORS_16) dsk.SetNibbles(dsk.Nibblize()) } } //func (d *DSKWrapper) ReadFileSectorsProDOS(fd ProDOSFileDescriptor) ([]byte, error) { //} func Dump(bytes []byte) { perline := 0xC base := 0 ascii := "" for i, v := range bytes { if i%perline == 0 { fmt.Println(" " + ascii) ascii = "" fmt.Printf("%.4X:", base+i) } if v >= 32 && v < 128 { ascii += string(rune(v)) } else { ascii += "." } fmt.Printf(" %.2X", v) } fmt.Println(" " + ascii) } func Between(v, lo, hi uint) bool { return ((v >= lo) && (v <= hi)) } func PokeToAscii(v uint, usealt bool) int { highbit := v & 1024 v = v & 1023 if Between(v, 0, 31) { return int((64 + (v % 32)) | highbit) } if Between(v, 32, 63) { return int((32 + (v % 32)) | highbit) } if Between(v, 64, 95) { if usealt { return int((128 + (v % 32)) | highbit) } else { return int((64 + (v % 32)) | highbit) } } if Between(v, 96, 127) { if usealt { return int((96 + (v % 32)) | highbit) } else { return int((32 + (v % 32)) | highbit) } } if Between(v, 128, 159) { return int((64 + (v % 32)) | highbit) } if Between(v, 160, 191) { return int((32 + (v % 32)) | highbit) } if Between(v, 192, 223) { return int((64 + (v % 32)) | highbit) } if Between(v, 224, 255) { return int((96 + (v % 32)) | highbit) } return int(v | highbit) } func (d *DSKWrapper) Nibblize() []byte { if len(d.Data) != STD_DISK_BYTES { return make([]byte, 232960) } data := d.Data output := bytes.NewBuffer([]byte(nil)) for track := 0; track < STD_TRACKS_PER_DISK; track++ { //d.writeJunkBytes(output, 48); for sector := 0; sector < STD_SECTORS_PER_TRACK; sector++ { //gap2 := int((rand.Float32() * 5.0) + 4) gap2 := 6 // 15 junk bytes d.writeJunkBytes(output, 15) // Address block d.writeAddressBlock(output, track, sector, 254) // 4 junk bytes d.writeJunkBytes(output, gap2) // Data block d.nibblizeBlock(output, track, d.CurrentSectorOrder[sector], data) // 34 junk bytes d.writeJunkBytes(output, 38-gap2) } } return output.Bytes() } func (d *DSKWrapper) NibbleOffsetToTS(offset int) (int, int) { offset = offset - (offset % 256) c := offset / 256 sector := c % SECTOR_COUNT track := (c - sector) / SECTOR_COUNT return track, sector } func (d *DSKWrapper) nibblizeBlock(output io.Writer, track, sector int, nibbles []byte) { //log.Printf("NibblizeBlock(%d, %d)", track, sector) offset := ((track * SECTOR_COUNT) + sector) * 256 temp := make([]int, 342) for i := 0; i < 256; i++ { temp[i] = int((nibbles[offset+i] & 0x0ff) >> 2) } hi := 0x001 med := 0x0AB low := 0x055 for i := 0; i < 0x56; i++ { value := ((nibbles[offset+hi] & 1) << 5) | ((nibbles[offset+hi] & 2) << 3) | ((nibbles[offset+med] & 1) << 3) | ((nibbles[offset+med] & 2) << 1) | ((nibbles[offset+low] & 1) << 1) | ((nibbles[offset+low] & 2) >> 1) temp[i+256] = int(value) hi = (hi - 1) & 0x0ff med = (med - 1) & 0x0ff low = (low - 1) & 0x0ff } output.Write([]byte{0x0d5, 0x0aa, 0x0ad}) last := 0 for i := len(temp) - 1; i > 255; i-- { value := temp[i] ^ last output.Write([]byte{NIBBLE_62[value]}) last = temp[i] } for i := 0; i < 256; i++ { value := temp[i] ^ last output.Write([]byte{NIBBLE_62[value]}) last = temp[i] } // Last data byte used as checksum output.Write([]byte{NIBBLE_62[last]}) output.Write([]byte{0x0de, 0x0aa, 0x0eb}) } func (d *DSKWrapper) writeJunkBytes(output io.Writer, i int) { for c := 0; c < i; c++ { output.Write([]byte{0xff}) } } func (d *DSKWrapper) writeAddressBlock(output io.Writer, track, sector int, volumeNumber int) { output.Write([]byte{0x0d5, 0x0aa, 0x096}) var checksum int = 0x00 // volume checksum ^= volumeNumber output.Write(d.getOddEven(volumeNumber)) // track checksum ^= track output.Write(d.getOddEven(track)) // sector checksum ^= sector output.Write(d.getOddEven(sector)) // checksum output.Write(d.getOddEven(checksum & 0x0ff)) output.Write([]byte{0xde, 0xaa, 0xeb}) } func (d *DSKWrapper) getOddEven(i int) []byte { out := []byte{0, 0} out[0] = byte(0xAA | (i >> 1)) out[1] = byte(0xAA | i) return out }