mirror of
https://github.com/paleotronic/diskm8.git
synced 2024-12-21 18:29:43 +00:00
1137 lines
21 KiB
Go
1137 lines
21 KiB
Go
package disk
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
type FileType byte
|
|
|
|
const (
|
|
FileTypeTXT FileType = 0x00
|
|
FileTypeINT FileType = 0x01
|
|
FileTypeAPP FileType = 0x02
|
|
FileTypeBIN FileType = 0x04
|
|
FileTypeS FileType = 0x08
|
|
FileTypeREL FileType = 0x10
|
|
FileTypeA FileType = 0x20
|
|
FileTypeB FileType = 0x40
|
|
)
|
|
|
|
var AppleDOSTypeMap = map[FileType][2]string{
|
|
0x00: [2]string{"TXT", "ASCII Text"},
|
|
0x01: [2]string{"INT", "Integer Basic Program"},
|
|
0x02: [2]string{"BAS", "Applesoft Basic Program"},
|
|
0x04: [2]string{"BIN", "Binary File"},
|
|
0x08: [2]string{"S", "S File Type"},
|
|
0x10: [2]string{"REL", "Relocatable Object Code"},
|
|
0x20: [2]string{"A", "A File Type"},
|
|
0x40: [2]string{"B", "B File Type"},
|
|
}
|
|
|
|
func (ft FileType) String() string {
|
|
|
|
info, ok := AppleDOSTypeMap[ft]
|
|
if ok {
|
|
return info[1]
|
|
}
|
|
|
|
return "Unknown"
|
|
}
|
|
|
|
func AppleDOSFileTypeFromExt(ext string) FileType {
|
|
for ft, info := range AppleDOSTypeMap {
|
|
if strings.ToUpper(ext) == info[0] {
|
|
return ft
|
|
}
|
|
}
|
|
return 0x04
|
|
}
|
|
|
|
func (ft FileType) Ext() string {
|
|
|
|
info, ok := AppleDOSTypeMap[ft]
|
|
if ok {
|
|
return info[0]
|
|
}
|
|
|
|
return "BIN"
|
|
}
|
|
|
|
type FileDescriptor struct {
|
|
Data []byte
|
|
trackid, sectorid int
|
|
sectoroffset int
|
|
}
|
|
|
|
func (fd *FileDescriptor) SetData(data []byte, t, s, o int) {
|
|
|
|
fd.trackid = t
|
|
fd.sectorid = s
|
|
fd.sectoroffset = o
|
|
|
|
if fd.Data == nil && len(data) == 35 {
|
|
fd.Data = data
|
|
}
|
|
|
|
for i, v := range data {
|
|
fd.Data[i] = v
|
|
}
|
|
}
|
|
|
|
func (fd *FileDescriptor) Publish(dsk *DSKWrapper) error {
|
|
|
|
err := dsk.Seek(fd.trackid, fd.sectorid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
block := dsk.Read()
|
|
|
|
for i, v := range fd.Data {
|
|
block[fd.sectoroffset+i] = v
|
|
}
|
|
|
|
dsk.Write(block)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
func (fd *FileDescriptor) IsUnused() bool {
|
|
return fd.Data[0] == 0xff || fd.Type().String() == "Unknown" || fd.TotalSectors() == 0
|
|
}
|
|
|
|
func (fd *FileDescriptor) GetTrackSectorListStart() (int, int) {
|
|
return int(fd.Data[0]), int(fd.Data[1])
|
|
}
|
|
|
|
func (fd *FileDescriptor) IsLocked() bool {
|
|
return (fd.Data[2]&0x80 != 0)
|
|
}
|
|
|
|
func (fd *FileDescriptor) SetLocked(b bool) {
|
|
fd.Data[2] = fd.Data[2] & 0x7f
|
|
if b {
|
|
fd.Data[2] = fd.Data[2] | 0x80
|
|
}
|
|
}
|
|
|
|
func (fd *FileDescriptor) Type() FileType {
|
|
return FileType(fd.Data[2] & 0x7f)
|
|
}
|
|
|
|
func (fd *FileDescriptor) SetType(t FileType) {
|
|
fd.Data[0x02] = byte(t)
|
|
}
|
|
|
|
func AsciiToPoke(b byte) byte {
|
|
if b < 32 || b > 127 {
|
|
b = 32
|
|
}
|
|
return b | 128
|
|
}
|
|
|
|
func (fd *FileDescriptor) SetName(s string) {
|
|
maxlen := len(s)
|
|
if maxlen > 30 {
|
|
maxlen = 30
|
|
}
|
|
for i := 0; i < 30; i++ {
|
|
fd.Data[0x03+i] = 0xa0
|
|
}
|
|
for i, b := range []byte(s) {
|
|
if i >= maxlen {
|
|
break
|
|
}
|
|
fd.Data[0x03+i] = AsciiToPoke(b)
|
|
}
|
|
}
|
|
|
|
func (fd *FileDescriptor) Name() string {
|
|
r := fd.Data[0x03:0x21]
|
|
s := ""
|
|
for _, v := range r {
|
|
ch := PokeToAscii(uint(v), false)
|
|
s = s + string(ch)
|
|
}
|
|
|
|
s = strings.ToLower(strings.Trim(s, " "))
|
|
|
|
switch fd.Type() {
|
|
case FileTypeAPP:
|
|
s += ".a"
|
|
case FileTypeINT:
|
|
s += ".i"
|
|
case FileTypeBIN:
|
|
s += ".s"
|
|
case FileTypeTXT:
|
|
s += ".t"
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
func (fd *FileDescriptor) NameUnadorned() string {
|
|
r := fd.Data[0x03:0x21]
|
|
s := ""
|
|
for _, v := range r {
|
|
ch := PokeToAscii(uint(v), false)
|
|
s = s + string(ch)
|
|
}
|
|
|
|
s = strings.ToLower(strings.Trim(s, " "))
|
|
|
|
return s
|
|
}
|
|
|
|
func (fd *FileDescriptor) NameBytes() []byte {
|
|
return fd.Data[0x03:0x21]
|
|
}
|
|
|
|
func (fd *FileDescriptor) NameOK() bool {
|
|
for _, v := range fd.NameBytes() {
|
|
if v < 32 {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (fd *FileDescriptor) TotalSectors() int {
|
|
return int(fd.Data[0x21]) + 256*int(fd.Data[0x22])
|
|
}
|
|
|
|
func (fd *FileDescriptor) SetTotalSectors(v int) {
|
|
fd.Data[0x21] = byte(v & 0xff)
|
|
fd.Data[0x22] = byte(v / 0x100)
|
|
}
|
|
|
|
func (fd *FileDescriptor) SetTrackSectorListStart(t, s int) {
|
|
fd.Data[0] = byte(t)
|
|
fd.Data[1] = byte(s)
|
|
}
|
|
|
|
type VTOC struct {
|
|
Data [256]byte
|
|
t, s int
|
|
}
|
|
|
|
func (fd *VTOC) SetData(data []byte, t, s int) {
|
|
fd.t, fd.s = t, s
|
|
for i, v := range data {
|
|
fd.Data[i] = v
|
|
}
|
|
}
|
|
|
|
func (fd *VTOC) GetCatalogStart() (int, int) {
|
|
return int(fd.Data[1]), int(fd.Data[2])
|
|
}
|
|
|
|
func (fd *VTOC) GetDOSVersion() byte {
|
|
return fd.Data[3]
|
|
}
|
|
|
|
func (fd *VTOC) GetVolumeID() byte {
|
|
return fd.Data[6]
|
|
}
|
|
|
|
func (fd *VTOC) GetMaxTSPairsPerSector() int {
|
|
return int(fd.Data[0x27])
|
|
}
|
|
|
|
func (fd *VTOC) GetTracks() int {
|
|
return int(fd.Data[0x34])
|
|
}
|
|
|
|
func (fd *VTOC) GetSectors() int {
|
|
return int(fd.Data[0x35])
|
|
}
|
|
|
|
func (fd *VTOC) GetTrackOrder() int {
|
|
return int(fd.Data[0x31])
|
|
}
|
|
|
|
func (fd *VTOC) BytesPerSector() int {
|
|
return int(fd.Data[0x36]) + 256*int(fd.Data[0x37])
|
|
}
|
|
|
|
func (fd *VTOC) IsTSFree(t, s int) bool {
|
|
offset := 0x38 + t*4
|
|
if s < 8 {
|
|
offset++
|
|
}
|
|
bitmask := byte(1 << uint(s&0x7))
|
|
|
|
return (fd.Data[offset]&bitmask != 0)
|
|
}
|
|
|
|
// SetTSFree marks a T/S free or not
|
|
func (fd *VTOC) SetTSFree(t, s int, b bool) {
|
|
offset := 0x38 + t*4
|
|
if s < 8 {
|
|
offset++
|
|
}
|
|
bitmask := byte(1 << uint(s&0x7))
|
|
clrmask := 0xff ^ bitmask
|
|
|
|
v := fd.Data[offset]
|
|
if b {
|
|
v |= bitmask
|
|
} else {
|
|
v &= clrmask
|
|
}
|
|
|
|
fd.Data[offset] = v
|
|
}
|
|
|
|
func (fd *VTOC) Publish(dsk *DSKWrapper) error {
|
|
err := dsk.Seek(fd.t, fd.s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
dsk.Write(fd.Data[:])
|
|
|
|
return nil
|
|
}
|
|
|
|
func (fd *VTOC) DumpMap() {
|
|
|
|
fmt.Printf("Disk has %d tracks and %d sectors per track, %d bytes per sector (ordering %d)...\n", fd.GetTracks(), fd.GetSectors(), fd.BytesPerSector(), fd.GetTrackOrder())
|
|
fmt.Printf("Volume ID is %d\n", fd.GetVolumeID())
|
|
ct, cs := fd.GetCatalogStart()
|
|
fmt.Printf("Catalog starts at T%d, S%d\n", ct, cs)
|
|
|
|
tcount := fd.GetTracks()
|
|
scount := fd.GetSectors()
|
|
|
|
for t := 0; t < tcount; t++ {
|
|
fmt.Printf("TRACK %.2x: |", t)
|
|
for s := 0; s < scount; s++ {
|
|
if fd.IsTSFree(t, s) {
|
|
fmt.Print(".")
|
|
} else {
|
|
fmt.Print("X")
|
|
}
|
|
}
|
|
fmt.Println("|")
|
|
}
|
|
|
|
}
|
|
|
|
func (dsk *DSKWrapper) IsAppleDOS() (bool, DiskFormat, SectorOrder) {
|
|
|
|
oldFormat := dsk.Format
|
|
oldLayout := dsk.Layout
|
|
|
|
defer func() {
|
|
dsk.Format = oldFormat
|
|
dsk.Layout = oldLayout
|
|
}()
|
|
|
|
if len(dsk.Data) == STD_DISK_BYTES {
|
|
|
|
layouts := []SectorOrder{SectorOrderDOS33, SectorOrderDOS33Alt, SectorOrderProDOS, SectorOrderProDOSLinear}
|
|
|
|
for _, l := range layouts {
|
|
|
|
dsk.Layout = l
|
|
|
|
vtoc, err := dsk.AppleDOSGetVTOC()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if vtoc.GetTracks() != 35 || vtoc.GetSectors() != 16 {
|
|
continue
|
|
}
|
|
|
|
_, files, err := dsk.AppleDOSGetCatalog("*")
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if len(files) > 0 {
|
|
return true, GetDiskFormat(DF_DOS_SECTORS_16), l
|
|
}
|
|
|
|
}
|
|
|
|
} else if len(dsk.Data) == STD_DISK_BYTES_OLD {
|
|
|
|
layouts := []SectorOrder{SectorOrderDOS33, SectorOrderDOS33Alt, SectorOrderProDOS, SectorOrderProDOSLinear}
|
|
|
|
dsk.Format = GetDiskFormat(DF_DOS_SECTORS_13)
|
|
|
|
for _, l := range layouts {
|
|
dsk.Layout = l
|
|
|
|
vtoc, err := dsk.AppleDOSGetVTOC()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if vtoc.GetTracks() != 35 || vtoc.GetSectors() != 13 {
|
|
continue
|
|
}
|
|
|
|
_, files, err := dsk.AppleDOSGetCatalog("*")
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if len(files) > 0 {
|
|
return true, GetDiskFormat(DF_DOS_SECTORS_13), l
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false, oldFormat, oldLayout
|
|
|
|
}
|
|
|
|
func (d *DSKWrapper) AppleDOSReadFileRaw(fd FileDescriptor) (int, int, []byte, error) {
|
|
|
|
data, e := d.AppleDOSReadFileSectors(fd, -1)
|
|
|
|
if e != nil || len(data) == 0 {
|
|
return 0, 0, data, e
|
|
}
|
|
|
|
switch fd.Type() {
|
|
case FileTypeINT:
|
|
l := int(data[0]) + 256*int(data[1])
|
|
if l+2 > len(data) {
|
|
l = len(data) - 2
|
|
}
|
|
return l, 0x801, data[2 : 2+l], nil
|
|
case FileTypeAPP:
|
|
l := int(data[0]) + 256*int(data[1])
|
|
if l+2 > len(data) {
|
|
l = len(data) - 2
|
|
}
|
|
return l, 0x801, data[2 : 2+l], nil
|
|
case FileTypeTXT:
|
|
return len(data), 0x0000, data, nil
|
|
case FileTypeBIN:
|
|
addr := int(data[0]) + 256*int(data[1])
|
|
l := int(data[2]) + 256*int(data[3])
|
|
if l+4 > len(data) {
|
|
l = len(data) - 4
|
|
}
|
|
//fmt.Printf("%x, %x, %x\n", l, addr, len(data))
|
|
return l, addr, data[4 : 4+l], nil
|
|
default:
|
|
l := int(data[0]) + 256*int(data[1])
|
|
if l+2 > len(data) {
|
|
l = len(data) - 2
|
|
}
|
|
return l, 0, data[2 : 2+l], nil
|
|
}
|
|
|
|
}
|
|
|
|
func (d *DSKWrapper) AppleDOSGetVTOC() (*VTOC, error) {
|
|
e := d.Seek(17, 0)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
data := d.Read()
|
|
|
|
vtoc := &VTOC{}
|
|
vtoc.SetData(data, 17, 0)
|
|
return vtoc, nil
|
|
}
|
|
|
|
func (dsk *DSKWrapper) AppleDOSUsedBitmap() ([]bool, error) {
|
|
|
|
var out []bool = make([]bool, dsk.Format.TPD()*dsk.Format.SPT())
|
|
|
|
_, files, err := dsk.AppleDOSGetCatalog("*")
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
|
|
for _, f := range files {
|
|
tslist, err := dsk.AppleDOSGetFileSectors(f, 0)
|
|
if err == nil {
|
|
for _, pair := range tslist {
|
|
track := pair[0]
|
|
sector := pair[1]
|
|
out[track*dsk.Format.SPT()+sector] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
func (d *DSKWrapper) AppleDOSGetCatalog(pattern string) (*VTOC, []FileDescriptor, error) {
|
|
|
|
var files []FileDescriptor
|
|
var e error
|
|
var vtoc *VTOC
|
|
|
|
vtoc, e = d.AppleDOSGetVTOC()
|
|
if e != nil {
|
|
return vtoc, files, e
|
|
}
|
|
|
|
count := 0
|
|
ct, cs := vtoc.GetCatalogStart()
|
|
|
|
e = d.Seek(ct, cs)
|
|
if e != nil {
|
|
return vtoc, files, e
|
|
}
|
|
|
|
data := d.Read()
|
|
|
|
var re *regexp.Regexp
|
|
if pattern != "" {
|
|
patterntmp := strings.Replace(pattern, ".", "[.]", -1)
|
|
patterntmp = strings.Replace(patterntmp, "*", ".*", -1)
|
|
patterntmp = "(?i)^" + patterntmp + "$"
|
|
re = regexp.MustCompile(patterntmp)
|
|
}
|
|
|
|
for e == nil && count < 105 {
|
|
slot := count % 7
|
|
pos := 0x0b + 35*slot
|
|
|
|
fd := FileDescriptor{}
|
|
fd.SetData(data[pos:pos+35], ct, cs, pos)
|
|
|
|
var skipname bool = false
|
|
if re != nil {
|
|
skipname = !re.MatchString(fd.Name())
|
|
}
|
|
|
|
if fd.Data[0] != 0xff && fd.Data[0] != 0x00 && fd.Type().String() != "Unknown" && !skipname {
|
|
files = append(files, fd)
|
|
}
|
|
count++
|
|
if count%7 == 0 {
|
|
// move to next catalog sector
|
|
ct = int(data[1])
|
|
cs = int(data[2])
|
|
if ct == 0 {
|
|
return vtoc, files, nil
|
|
}
|
|
e = d.Seek(ct, cs)
|
|
if e != nil {
|
|
return vtoc, files, e
|
|
}
|
|
data = d.Read()
|
|
}
|
|
}
|
|
|
|
return vtoc, files, nil
|
|
}
|
|
|
|
func (d *DSKWrapper) AppleDOSGetFileSectors(fd FileDescriptor, maxblocks int) ([][2]int, error) {
|
|
var e error
|
|
var data []byte
|
|
tl, sl := fd.GetTrackSectorListStart()
|
|
|
|
// var tracks []int
|
|
// var sectors []int
|
|
|
|
var tslist [][2]int
|
|
|
|
var tsmap = make(map[int]int)
|
|
|
|
for e == nil && (tl != 0 || sl != 0) {
|
|
// Get TS List
|
|
e = d.Seek(tl, sl)
|
|
if e != nil {
|
|
return tslist, e
|
|
}
|
|
data = d.Read()
|
|
|
|
//fmt.Printf("DEBUG: T/S List follows from T%d, S%d:\n", tl, sl)
|
|
//Dump(data)
|
|
|
|
ptr := 0x0c
|
|
for ptr < 0x100 {
|
|
// check entry
|
|
t, s := int(data[ptr]), int(data[ptr+1])
|
|
|
|
if t == 0 && s == 0 || t >= d.Format.TPD() || s >= d.Format.SPT() {
|
|
//fmt.Println("BREAK ptr =", ptr, len(tracks))
|
|
break
|
|
}
|
|
|
|
//fmt.Printf("File block at T%d, S%d\n", t, s)
|
|
|
|
// tracks = append(tracks, t)
|
|
// sectors = append(sectors, s)
|
|
|
|
tslist = append(tslist, [2]int{t, s})
|
|
|
|
// next entry
|
|
ptr += 2
|
|
}
|
|
|
|
// get next TS List block
|
|
ntl, nsl := int(data[1]), int(data[2])
|
|
if _, ex := tsmap[100*ntl+nsl]; ex {
|
|
//fmt.Printf("circular ts list")
|
|
break
|
|
}
|
|
|
|
tl, sl = ntl, nsl
|
|
|
|
tsmap[100*tl+sl] = 1
|
|
|
|
//fmt.Printf("Next Track Sector list is at T%d, S%d (%d)\n", tl, sl, len(tracks))
|
|
|
|
}
|
|
|
|
return tslist, nil
|
|
}
|
|
|
|
func (d *DSKWrapper) AppleDOSReadFileSectors(fd FileDescriptor, maxblocks int) ([]byte, error) {
|
|
var e error
|
|
var data []byte
|
|
var file []byte
|
|
tl, sl := fd.GetTrackSectorListStart()
|
|
|
|
var tracks []int
|
|
var sectors []int
|
|
|
|
var tsmap = make(map[int]int)
|
|
|
|
for e == nil && (tl != 0 || sl != 0) {
|
|
// Get TS List
|
|
e = d.Seek(tl, sl)
|
|
if e != nil {
|
|
return file, e
|
|
}
|
|
data = d.Read()
|
|
|
|
//fmt.Printf("DEBUG: T/S List follows from T%d, S%d:\n", tl, sl)
|
|
//Dump(data)
|
|
|
|
ptr := 0x0c
|
|
for ptr < 0x100 {
|
|
// check entry
|
|
t, s := int(data[ptr]), int(data[ptr+1])
|
|
|
|
if t == 0 && s == 0 || t >= d.Format.TPD() || s >= d.Format.SPT() {
|
|
//fmt.Println("BREAK ptr =", ptr, len(tracks))
|
|
break
|
|
}
|
|
|
|
//fmt.Printf("File block at T%d, S%d\n", t, s)
|
|
|
|
tracks = append(tracks, t)
|
|
sectors = append(sectors, s)
|
|
|
|
// next entry
|
|
ptr += 2
|
|
}
|
|
|
|
// get next TS List block
|
|
ntl, nsl := int(data[1]), int(data[2])
|
|
if _, ex := tsmap[100*ntl+nsl]; ex {
|
|
//fmt.Printf("circular ts list")
|
|
break
|
|
}
|
|
|
|
tl, sl = ntl, nsl
|
|
|
|
tsmap[100*tl+sl] = 1
|
|
|
|
//fmt.Printf("Next Track Sector list is at T%d, S%d (%d)\n", tl, sl, len(tracks))
|
|
|
|
}
|
|
|
|
// Here got T/S list
|
|
//fmt.Println("READING FILE")
|
|
blocksread := 0
|
|
for i, t := range tracks {
|
|
s := sectors[i]
|
|
|
|
//fmt.Printf("TS Fetch #%d: Track %d, %d\n", i, t, s)
|
|
|
|
e = d.Seek(t, s)
|
|
if e != nil {
|
|
return file, e
|
|
}
|
|
c := d.Read()
|
|
|
|
//Dump(c)
|
|
|
|
file = append(file, c...)
|
|
blocksread++
|
|
|
|
if maxblocks != -1 && blocksread >= maxblocks {
|
|
break
|
|
}
|
|
}
|
|
|
|
return file, nil
|
|
}
|
|
|
|
func (d *DSKWrapper) AppleDOSGetTSListSectors(fd FileDescriptor, maxblocks int) ([][2]int, error) {
|
|
var e error
|
|
var data []byte
|
|
|
|
tl, sl := fd.GetTrackSectorListStart()
|
|
|
|
var tslist [][2]int
|
|
|
|
var tsmap = make(map[int]int)
|
|
|
|
for e == nil && (tl != 0 || sl != 0) {
|
|
// Get TS List
|
|
e = d.Seek(tl, sl)
|
|
if e != nil {
|
|
return tslist, e
|
|
}
|
|
data = d.Read()
|
|
|
|
tslist = append(tslist, [2]int{tl, sl})
|
|
|
|
// get next TS List block
|
|
ntl, nsl := int(data[1]), int(data[2])
|
|
if _, ex := tsmap[100*ntl+nsl]; ex {
|
|
break
|
|
}
|
|
|
|
tl, sl = ntl, nsl
|
|
|
|
tsmap[100*tl+sl] = 1
|
|
|
|
}
|
|
|
|
return tslist, nil
|
|
}
|
|
|
|
func (d *DSKWrapper) AppleDOSReadFile(fd FileDescriptor) (int, int, []byte, error) {
|
|
|
|
data, e := d.AppleDOSReadFileSectors(fd, -1)
|
|
|
|
if e != nil {
|
|
return 0, 0, data, e
|
|
}
|
|
|
|
switch fd.Type() {
|
|
case FileTypeINT:
|
|
l := int(data[0]) + 256*int(data[1])
|
|
return l, 0x801, IntegerDetoks(data[2 : 2+l]), nil
|
|
case FileTypeAPP:
|
|
l := int(data[0]) + 256*int(data[1])
|
|
return l, 0x801, ApplesoftDetoks(data[2 : 2+l]), nil
|
|
case FileTypeTXT:
|
|
return len(data), 0x0000, data, nil
|
|
case FileTypeBIN:
|
|
addr := int(data[0]) + 256*int(data[1])
|
|
l := int(data[2]) + 256*int(data[3])
|
|
//fmt.Printf("%x, %x, %x\n", l, addr, len(data))
|
|
return l, addr, data[4 : 4+l], nil
|
|
default:
|
|
l := int(data[0]) + 256*int(data[1])
|
|
return l, 0, data[2 : 2+l], nil
|
|
}
|
|
|
|
}
|
|
|
|
// AppleDOSGetFreeSectors tries to find free sectors for certain size file...
|
|
// Remember, we need space for the T/S list as well...
|
|
func (dsk *DSKWrapper) AppleDOSGetFreeSectors(size int) ([][2]int, [][2]int, error) {
|
|
|
|
needed := make([][2]int, 0)
|
|
vtoc, err := dsk.AppleDOSGetVTOC()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
catTrack, _ := vtoc.GetCatalogStart()
|
|
|
|
// needed:
|
|
// size/256 + 1 for data
|
|
// 1 for T/S list
|
|
dataBlocks := (size / 256) + 1
|
|
tsListBlocks := (dataBlocks / vtoc.GetMaxTSPairsPerSector()) + 1
|
|
totalBlocks := tsListBlocks + dataBlocks
|
|
|
|
for t := dsk.Format.TPD() - 1; t >= 0; t-- {
|
|
|
|
if t == catTrack {
|
|
continue // skip catalog track
|
|
}
|
|
|
|
for s := dsk.Format.SPT() - 1; s >= 0; s-- {
|
|
|
|
if len(needed) >= totalBlocks {
|
|
break
|
|
}
|
|
|
|
if vtoc.IsTSFree(t, s) {
|
|
needed = append(needed, [2]int{t, s})
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if len(needed) >= totalBlocks {
|
|
return needed[:tsListBlocks], needed[tsListBlocks:], nil
|
|
}
|
|
|
|
return nil, nil, errors.New("Insufficent space")
|
|
|
|
}
|
|
|
|
func (d *DSKWrapper) AppleDOSNextFreeCatalogEntry(name string) (*FileDescriptor, error) {
|
|
|
|
var e error
|
|
var vtoc *VTOC
|
|
|
|
vtoc, e = d.AppleDOSGetVTOC()
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
|
|
count := 0
|
|
ct, cs := vtoc.GetCatalogStart()
|
|
|
|
e = d.Seek(ct, cs)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
|
|
data := d.Read()
|
|
|
|
for e == nil && count < 105 {
|
|
slot := count % 7
|
|
pos := 0x0b + 35*slot
|
|
|
|
fd := FileDescriptor{}
|
|
fd.SetData(data[pos:pos+35], ct, cs, pos)
|
|
|
|
if fd.IsUnused() {
|
|
return &fd, nil
|
|
} else if name != "" && strings.ToLower(fd.NameUnadorned()) == strings.ToLower(name) {
|
|
return &fd, nil
|
|
}
|
|
count++
|
|
if count%7 == 0 {
|
|
// move to next catalog sector
|
|
ct = int(data[1])
|
|
cs = int(data[2])
|
|
if ct == 0 {
|
|
return nil, nil
|
|
}
|
|
e = d.Seek(ct, cs)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
data = d.Read()
|
|
}
|
|
}
|
|
|
|
return nil, errors.New("No free entry")
|
|
}
|
|
|
|
func (d *DSKWrapper) AppleDOSNamedCatalogEntry(name string) (*FileDescriptor, error) {
|
|
|
|
var e error
|
|
var vtoc *VTOC
|
|
|
|
vtoc, e = d.AppleDOSGetVTOC()
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
|
|
count := 0
|
|
ct, cs := vtoc.GetCatalogStart()
|
|
|
|
e = d.Seek(ct, cs)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
|
|
data := d.Read()
|
|
|
|
for e == nil && count < 105 {
|
|
slot := count % 7
|
|
pos := 0x0b + 35*slot
|
|
|
|
fd := FileDescriptor{}
|
|
fd.SetData(data[pos:pos+35], ct, cs, pos)
|
|
|
|
//fmt.Printf("FILE NAME CHECK [%s] vs [%s]\n", strings.ToLower(fd.NameUnadorned()), strings.ToLower(name))
|
|
|
|
if name != "" && strings.ToLower(fd.NameUnadorned()) == strings.ToLower(name) {
|
|
return &fd, nil
|
|
}
|
|
count++
|
|
if count%7 == 0 {
|
|
// move to next catalog sector
|
|
ct = int(data[1])
|
|
cs = int(data[2])
|
|
if ct == 0 {
|
|
return nil, errors.New("Not found")
|
|
}
|
|
e = d.Seek(ct, cs)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
data = d.Read()
|
|
}
|
|
}
|
|
|
|
return nil, errors.New("Not found")
|
|
}
|
|
|
|
func (dsk *DSKWrapper) AppleDOSWriteFile(name string, kind FileType, data []byte, loadAddr int) error {
|
|
|
|
name = strings.ToUpper(name)
|
|
|
|
vtoc, err := dsk.AppleDOSGetVTOC()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if kind != FileTypeTXT {
|
|
header := []byte{byte(loadAddr % 256), byte(loadAddr / 256)}
|
|
data = append(header, data...)
|
|
}
|
|
|
|
// 1st: check we have sufficient space...
|
|
tsBlocks, dataBlocks, err := dsk.AppleDOSGetFreeSectors(len(data))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fd, err := dsk.AppleDOSNamedCatalogEntry(name)
|
|
//fmt.Println("FD=", fd)
|
|
if err == nil {
|
|
if kind != fd.Type() {
|
|
return errors.New("File type mismatch")
|
|
} else {
|
|
// need to delete this file...
|
|
err = dsk.AppleDOSDeleteFile(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else {
|
|
fd, err = dsk.AppleDOSNextFreeCatalogEntry(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// 2nd: check we can get a free catalog entry
|
|
|
|
// 3rd: Write the datablocks
|
|
var block int = 0
|
|
for len(data) > 0 {
|
|
|
|
max := STD_BYTES_PER_SECTOR
|
|
if len(data) < STD_BYTES_PER_SECTOR {
|
|
max = len(data)
|
|
}
|
|
chunk := data[:max]
|
|
// Pad final sector with 0x00 bytes
|
|
for len(chunk) < STD_BYTES_PER_SECTOR {
|
|
chunk = append(chunk, 0x00)
|
|
}
|
|
data = data[max:]
|
|
|
|
pair := dataBlocks[block]
|
|
|
|
track, sector := pair[0], pair[1]
|
|
|
|
err = dsk.Seek(track, sector)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dsk.Write(chunk)
|
|
|
|
block++
|
|
|
|
}
|
|
|
|
// 4th: Write the T/S List
|
|
offset := 0
|
|
for blockIdx, block := range tsBlocks {
|
|
listTrack, listSector := block[0], block[1]
|
|
nextTrack, nextSector := 0, 0
|
|
if blockIdx < len(tsBlocks)-1 {
|
|
nextTrack, nextSector = tsBlocks[blockIdx+1][0], tsBlocks[blockIdx+1][1]
|
|
}
|
|
|
|
buffer := make([]byte, STD_BYTES_PER_SECTOR)
|
|
|
|
// header
|
|
buffer[0x01] = byte(nextTrack)
|
|
buffer[0x02] = byte(nextSector)
|
|
buffer[0x05] = byte(offset & 0xff)
|
|
buffer[0x06] = byte(offset / 0x100)
|
|
|
|
//
|
|
count := vtoc.GetMaxTSPairsPerSector()
|
|
if offset+count >= len(dataBlocks) {
|
|
count = len(dataBlocks) - offset
|
|
}
|
|
|
|
for i := 0; i < count; i++ {
|
|
pos := 0x0c + i*2
|
|
buffer[pos+0x00] = byte(dataBlocks[offset+i][0])
|
|
buffer[pos+0x01] = byte(dataBlocks[offset+i][1])
|
|
vtoc.SetTSFree(dataBlocks[offset+i][0], dataBlocks[offset+i][1], false)
|
|
}
|
|
|
|
// Write the sector
|
|
err = dsk.Seek(listTrack, listSector)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dsk.Write(buffer)
|
|
vtoc.SetTSFree(listTrack, listSector, false)
|
|
}
|
|
|
|
err = vtoc.Publish(dsk)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 5th and finally: Let's make that catalog entry
|
|
fd.SetName(name)
|
|
fd.SetTrackSectorListStart(tsBlocks[0][0], tsBlocks[0][1])
|
|
fd.SetType(kind)
|
|
fd.SetTotalSectors(len(dataBlocks))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
func (d *DSKWrapper) AppleDOSRemoveFile(fd *FileDescriptor) error {
|
|
|
|
vtoc, err := d.AppleDOSGetVTOC()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if fd.IsUnused() {
|
|
return errors.New("File does not exist")
|
|
}
|
|
|
|
tsBlocks, e := d.AppleDOSGetTSListSectors(*fd, -1)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
|
|
dataBlocks, e := d.AppleDOSGetFileSectors(*fd, -1)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
|
|
for _, pair := range dataBlocks {
|
|
vtoc.SetTSFree(pair[0], pair[1], true)
|
|
}
|
|
|
|
for _, pair := range tsBlocks {
|
|
vtoc.SetTSFree(pair[0], pair[1], true)
|
|
}
|
|
|
|
fd.Data[0x00] = 0xff
|
|
fd.SetName("")
|
|
return fd.Publish(d)
|
|
|
|
}
|
|
|
|
func (dsk *DSKWrapper) AppleDOSDeleteFile(name string) error {
|
|
|
|
vtoc, err := dsk.AppleDOSGetVTOC()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// We cheat here a bit and use the get first free entry call with
|
|
// autogrow turned off.
|
|
fd, err := dsk.AppleDOSNamedCatalogEntry(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if fd.IsUnused() {
|
|
return errors.New("Not found")
|
|
}
|
|
|
|
// At this stage we have a match so get blocks to remove
|
|
tsBlocks, e := dsk.AppleDOSGetTSListSectors(*fd, -1)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
|
|
dataBlocks, e := dsk.AppleDOSGetFileSectors(*fd, -1)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
|
|
for _, pair := range dataBlocks {
|
|
vtoc.SetTSFree(pair[0], pair[1], true)
|
|
}
|
|
|
|
for _, pair := range tsBlocks {
|
|
vtoc.SetTSFree(pair[0], pair[1], true)
|
|
}
|
|
|
|
err = vtoc.Publish(dsk)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fd.Data[0x00] = 0xff
|
|
fd.SetName("")
|
|
return fd.Publish(dsk)
|
|
|
|
}
|
|
|
|
func (dsk *DSKWrapper) AppleDOSSetLocked(name string, lock bool) error {
|
|
|
|
// We cheat here a bit and use the get first free entry call with
|
|
// autogrow turned off.
|
|
fd, err := dsk.AppleDOSNamedCatalogEntry(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if fd.IsUnused() {
|
|
return errors.New("Not found")
|
|
}
|
|
|
|
fd.SetLocked(lock)
|
|
return fd.Publish(dsk)
|
|
|
|
}
|
|
|
|
func (dsk *DSKWrapper) AppleDOSRenameFile(name, newname string) error {
|
|
|
|
fd, err := dsk.AppleDOSNamedCatalogEntry(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = dsk.AppleDOSNamedCatalogEntry(newname)
|
|
if err == nil {
|
|
return errors.New("New name already exists")
|
|
}
|
|
|
|
// can rename here
|
|
fd.SetName(newname)
|
|
return fd.Publish(dsk)
|
|
|
|
}
|