mirror of
https://github.com/paleotronic/diskm8.git
synced 2024-12-22 09:30:12 +00:00
1091 lines
22 KiB
Go
1091 lines
22 KiB
Go
|
package disk
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
"io"
|
||
|
"io/ioutil"
|
||
|
"os"
|
||
|
"strings"
|
||
|
|
||
|
"crypto/sha256"
|
||
|
"encoding/hex"
|
||
|
|
||
|
"fmt"
|
||
|
)
|
||
|
|
||
|
//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
|
||
|
}
|