diskm8/disk/diskimagerdos.go

383 lines
6.7 KiB
Go

package disk
import (
"bytes"
"regexp"
"strings"
)
const RDOS_CATALOG_TRACK = 0x01
const RDOS_CATALOG_LENGTH = 0xB
const RDOS_ENTRY_LENGTH = 0x20
const RDOS_NAME_LENGTH = 0x18
var RDOS_SIGNATURE = []byte{
byte('R' + 0x80),
byte('D' + 0x80),
byte('O' + 0x80),
byte('S' + 0x80),
byte(' ' + 0x80),
}
var RDOS_SIGNATURE_32 = []byte{
byte('R' + 0x80),
byte('D' + 0x80),
byte('O' + 0x80),
byte('S' + 0x80),
byte(' ' + 0x80),
byte('2' + 0x80),
}
var RDOS_SIGNATURE_33 = []byte{
byte('R' + 0x80),
byte('D' + 0x80),
byte('O' + 0x80),
byte('S' + 0x80),
byte(' ' + 0x80),
byte('3' + 0x80),
}
type RDOSFormatSpec struct {
SectorStride int
SectorMax int
CatalogTrack int
CatalogSector int
Ordering SectorOrder
}
type RDOSFormat int
const (
RDOS_Unknown RDOSFormat = iota
RDOS_3
RDOS_32
RDOS_33
)
func (f RDOSFormat) String() string {
switch f {
case RDOS_3:
return "RDOS3"
case RDOS_32:
return "RDOS32"
case RDOS_33:
return "RDOS33"
}
return "Unknown"
}
func (f RDOSFormat) Spec() *RDOSFormatSpec {
switch f {
case RDOS_32:
return &RDOSFormatSpec{
SectorStride: 16,
SectorMax: 13,
CatalogTrack: 1,
CatalogSector: 0,
Ordering: SectorOrderDOS33,
}
case RDOS_3:
return &RDOSFormatSpec{
SectorStride: 16,
SectorMax: 13,
CatalogTrack: 1,
CatalogSector: 0,
Ordering: SectorOrderDOS33,
}
case RDOS_33:
return &RDOSFormatSpec{
SectorStride: 16,
SectorMax: 16,
CatalogTrack: 1,
CatalogSector: 12,
Ordering: SectorOrderProDOS,
}
}
return nil
}
func (dsk *DSKWrapper) IsRDOS() (bool, RDOSFormat) {
// It needs to be either 140K or 113K
if len(dsk.Data) != STD_DISK_BYTES && len(dsk.Data) != STD_DISK_BYTES_OLD {
return false, RDOS_Unknown
}
sectorStride := (len(dsk.Data) / STD_TRACKS_PER_DISK) / 256
idbytes := dsk.Data[sectorStride*256 : sectorStride*256+6]
if bytes.Compare(idbytes, RDOS_SIGNATURE_32) == 0 && sectorStride == 13 {
return true, RDOS_32
}
if bytes.Compare(idbytes, RDOS_SIGNATURE_32) == 0 && sectorStride == 16 {
return true, RDOS_3
}
if bytes.Compare(idbytes, RDOS_SIGNATURE_33) == 0 && sectorStride == 16 {
return true, RDOS_33
}
return false, RDOS_Unknown
}
type RDOSFileDescriptor struct {
data [RDOS_ENTRY_LENGTH]byte
}
func (fd *RDOSFileDescriptor) SetData(in []byte) {
for i, b := range in {
if i < RDOS_ENTRY_LENGTH {
fd.data[i] = b
}
}
}
func (fd *RDOSFileDescriptor) IsDeleted() bool {
return fd.data[24] == 0xa0 || fd.data[0] == 0x80
}
func (fd *RDOSFileDescriptor) IsUnused() bool {
return fd.data[24] == 0x00
}
func (fd *RDOSFileDescriptor) IsLocked() bool {
return true
}
type RDOSFileType int
const (
FileType_RDOS_Unknown RDOSFileType = iota
FileType_RDOS_AppleSoft
FileType_RDOS_Binary
FileType_RDOS_Text
)
var RDOSTypeMap = map[RDOSFileType][2]string{
FileType_RDOS_Unknown: [2]string{"UNK", "Unknown"},
FileType_RDOS_AppleSoft: [2]string{"BAS", "Applesoft Basic Program"},
FileType_RDOS_Binary: [2]string{"BIN", "Binary File"},
FileType_RDOS_Text: [2]string{"TXT", "ASCII Text"},
}
func (ft RDOSFileType) String() string {
info, ok := RDOSTypeMap[ft]
if ok {
return info[1]
}
return "Unknown"
}
func (ft RDOSFileType) Ext() string {
info, ok := RDOSTypeMap[ft]
if ok {
return info[0]
}
return "UNK"
}
func RDOSFileTypeFromExt(ext string) RDOSFileType {
for ft, info := range RDOSTypeMap {
if strings.ToUpper(ext) == info[0] {
return ft
}
}
return 0x00
}
func (fd *RDOSFileDescriptor) Type() RDOSFileType {
switch rune(fd.data[24]) {
case 'A' + 0x80:
return FileType_RDOS_AppleSoft
case 'B' + 0x80:
return FileType_RDOS_Binary
case 'T' + 0x80:
return FileType_RDOS_Text
}
return FileType_RDOS_Unknown
}
func (fd *RDOSFileDescriptor) Name() string {
str := ""
for i := 0; i < RDOS_NAME_LENGTH; i++ {
ch := rune(fd.data[i] & 127)
if ch == 0 {
break
}
str += string(ch)
}
str = strings.TrimRight(str, " ")
switch fd.Type() {
case FileType_RDOS_AppleSoft:
str += ".bas"
case FileType_RDOS_Binary:
str += ".bin"
case FileType_RDOS_Text:
str += ".txt"
}
return str
}
func (fd *RDOSFileDescriptor) NameUnadorned() string {
str := ""
for i := 0; i < RDOS_NAME_LENGTH; i++ {
ch := rune(fd.data[i] & 127)
if ch == 0 {
break
}
str += string(ch)
}
return strings.TrimSpace(str)
}
func (fd RDOSFileDescriptor) NumSectors() int {
return int(fd.data[25])
}
func (fd RDOSFileDescriptor) LoadAddress() int {
return int(fd.data[26]) + 256*int(fd.data[27])
}
func (fd RDOSFileDescriptor) StartSector() int {
return int(fd.data[30]) + 256*int(fd.data[31])
}
func (fd RDOSFileDescriptor) Length() int {
return int(fd.data[28]) + 256*int(fd.data[29])
}
func (dsk *DSKWrapper) RDOSGetCatalog(pattern string) ([]*RDOSFileDescriptor, error) {
pattern = strings.Replace(pattern, ".", "[.]", -1)
pattern = strings.Replace(pattern, "*", ".*", -1)
pattern = strings.Replace(pattern, "?", ".", -1)
rx := regexp.MustCompile("(?i)" + pattern)
var files = make([]*RDOSFileDescriptor, 0)
d := make([]byte, 0)
for s := 0; s < RDOS_CATALOG_LENGTH; s++ {
dsk.SetTrack(1)
dsk.SetSector(s)
chunk := dsk.Read()
d = append(d, chunk...)
}
var dirPtr int
for i := 0; i < RDOS_CATALOG_LENGTH*RDOS_ENTRY_LENGTH; i++ {
entry := &RDOSFileDescriptor{}
entry.SetData(d[dirPtr : dirPtr+RDOS_ENTRY_LENGTH])
dirPtr += RDOS_ENTRY_LENGTH
if entry.IsUnused() {
break
}
if !entry.IsDeleted() && rx.MatchString(entry.NameUnadorned()) {
files = append(files, entry)
}
}
return files, nil
}
func (dsk *DSKWrapper) RDOSUsedBitmap() ([]bool, error) {
spt := dsk.RDOSFormat.Spec().SectorMax
activeSectors := spt * 35
used := make([]bool, activeSectors)
files, err := dsk.RDOSGetCatalog("*")
if err != nil {
return used, err
}
for _, file := range files {
length := file.NumSectors()
start := file.StartSector()
if start+length > activeSectors {
continue // file is bad
}
for block := start; block < start+length; block++ {
used[block] = true
}
}
return used, nil
}
func (dsk *DSKWrapper) RDOSReadFile(file *RDOSFileDescriptor) ([]byte, error) {
spt := dsk.RDOSFormat.Spec().SectorMax
activeSectors := spt * 35
length := file.NumSectors()
start := file.StartSector()
// If file is damaged return nothing
if start+length > activeSectors {
return []byte(nil), nil
}
block := start
data := make([]byte, 0)
for block < start+length && len(data) < file.Length() {
track := block / spt
sector := block % spt
dsk.SetTrack(track)
dsk.SetSector(sector)
chunk := dsk.Read()
needed := file.Length() - len(data)
if needed >= 256 {
data = append(data, chunk...)
} else {
data = append(data, chunk[:needed]...)
}
block++
}
return data, nil
}