diskm8/disk/diskimagepd.go

1898 lines
42 KiB
Go

package disk
import (
"errors"
"regexp"
"strings"
"time"
)
type VDH struct {
Data []byte
blockid int
blockoffset int
}
func (fd *VDH) CreateTime() time.Time {
b := fd.Data[0x1C:0x20]
return prodosStampBytesToTime(b)
}
func (fd *VDH) SetCreateTime(t time.Time) {
b := timeToProdosStampBytes(t)
for i, v := range b {
fd.Data[0x1C+i] = v
}
}
func (fd *VDH) SetData(data []byte, blockid, blockoffset int) {
fd.blockid = blockid
fd.blockoffset = blockoffset
if fd.Data == nil && len(data) == 39 {
fd.Data = data
//Println("VDH: ")
//Dump(data)
}
for i, v := range data {
fd.Data[i] = v
}
}
func (fd *VDH) SetName(name string) {
name = strings.ToUpper(name)
if len(name) > 15 {
name = name[:15]
}
l := len(name)
for i, v := range []byte(name) {
fd.Data[1+i] = v
}
fd.Data[0] = (fd.Data[0] & 0xf0) | byte(l)
}
func (fd *VDH) SetStorageType(t ProDOSStorageType) {
fd.Data[0] = (fd.Data[0] & 0x0f) | (byte(t) << 4)
}
func (fd *VDH) GetNameLength() int {
return int(fd.Data[0] & 0xf)
}
func (fd *VDH) SetNameLength(l int) {
fd.Data[0] = (fd.Data[0] & 0xf0) | byte(l&0x0f)
}
func (fd *VDH) GetStorageType() ProDOSStorageType {
return ProDOSStorageType((fd.Data[0]) >> 4)
}
func (fd *VDH) GetDirName() string {
return fd.GetVolumeName()
}
func (fd *VDH) SetVolumeName(s string) {
if len(s) > 15 {
s = s[:15]
}
l := len(s)
for i := 0; i < 15; i++ {
if i < l {
fd.Data[1+i] = byte(s[i])
} else {
fd.Data[1+i] = 0x00
}
}
fd.SetNameLength(l)
}
func (fd *VDH) GetVolumeName() string {
l := fd.GetNameLength()
b := fd.Data[1 : 1+l]
s := ""
for _, v := range b {
s += string(rune(PokeToAscii(uint(v), false)))
}
return strings.Trim(s, " ")
}
func (fd *VDH) GetVersion() int {
return int(fd.Data[28])
}
func (fd *VDH) SetVersion(b int) {
fd.Data[28] = byte(b)
}
func (fd *VDH) GetMinVersion() int {
return int(fd.Data[29])
}
func (fd *VDH) SetMinVersion(b int) {
fd.Data[29] = byte(b)
}
func (fd *VDH) GetAccess() ProDOSAccessMode {
return ProDOSAccessMode(fd.Data[30])
}
func (fd *VDH) SetAccess(m ProDOSAccessMode) {
fd.Data[30] = byte(m)
}
func (fd *VDH) GetEntryLength() int {
return int(fd.Data[31])
}
func (fd *VDH) SetEntryLength(b int) {
fd.Data[31] = byte(b & 0xff)
}
func (fd *VDH) GetEntriesPerBlock() int {
return int(fd.Data[32])
}
func (fd *VDH) SetEntriesPerBlock(b int) {
fd.Data[32] = byte(b & 0xff)
}
func (fd *VDH) GetFileCount() int {
return int(fd.Data[33]) + 256*int(fd.Data[34])
}
func (fd *VDH) SetFileCount(c int) {
fd.Data[33] = byte(c & 0xff)
fd.Data[34] = byte(c / 0x100)
}
func (fd *VDH) GetBitmapPointer() int {
return int(fd.Data[35]) + 256*int(fd.Data[36])
}
func (fd *VDH) GetTotalBlocks() int {
return int(fd.Data[37]) + 256*int(fd.Data[38])
}
func (fd *VDH) SetTotalBlocks(b int) {
fd.Data[37] = byte(b % 256)
fd.Data[38] = byte(b / 256)
}
func (fd *VDH) GetDirParentPointer() int {
return int(fd.Data[35]) + 256*int(fd.Data[35])
}
func (fd *VDH) SetDirParentPointer(b int) {
fd.Data[35] = byte(b & 0xff)
fd.Data[36] = byte(b / 0x100)
}
func (fd *VDH) GetDirParentEntry() int {
return int(fd.Data[37])
}
func (fd *VDH) SetDirParentEntry(b int) {
fd.Data[37] = byte(b & 0xff)
}
func (fd *VDH) GetDirParentEntryLength() int {
return int(fd.Data[38])
}
func (fd *VDH) SetDirParentEntryLength(b int) {
fd.Data[38] = byte(b & 0xff)
}
func (fd *VDH) Publish(dsk *DSKWrapper) error {
bd, err := dsk.PRODOSGetBlock(fd.blockid)
if err != nil {
return err
}
for i, v := range fd.Data {
bd[fd.blockoffset+i] = v
}
//fmt.Printf("Writing dir header at block %d\n", fd.blockid)
//fmt.Printf("Data=%v\n", bd)
return dsk.PRODOSWrite(fd.blockid, bd)
}
func (dsk *DSKWrapper) IsProDOS() (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
vdh, err := dsk.PRODOSGetVDH(2)
if err != nil {
return false, oldFormat, oldLayout
}
if vdh.GetTotalBlocks() == 280 && vdh.GetStorageType() == 0xf {
return true, GetDiskFormat(DF_PRODOS), l
}
}
} else if len(dsk.Data) == PRODOS_800KB_DISK_BYTES {
layouts := []SectorOrder{SectorOrderDOS33, SectorOrderDOS33Alt, SectorOrderProDOS}
for _, l := range layouts {
dsk.Layout = l
vdh, err := dsk.PRODOSGetVDH(2)
if err != nil {
return false, oldFormat, oldLayout
}
if vdh.GetTotalBlocks() == 1600 && vdh.GetStorageType() == 0xf {
return true, GetDiskFormat(DF_PRODOS_800KB), l
}
}
}
return false, oldFormat, oldLayout
}
func (dsk *DSKWrapper) PRODOS800GetVolumeBitmap() (ProDOSVolumeBitmap, error) {
var vb ProDOSVolumeBitmap
vdh, err := dsk.PRODOS800GetVDH(2)
if err != nil {
return vb, err
}
b := vdh.GetBitmapPointer()
data, err := dsk.PRODOS800GetBlock(b)
if err != nil {
return vb, err
}
//copy(vb[:], data)
vb = ProDOSVolumeBitmap{
Data: data,
blockid: b,
blockoffset: 0,
}
return vb, nil
}
func (dsk *DSKWrapper) PRODOSGetVolumeBitmap() (ProDOSVolumeBitmap, error) {
var vb ProDOSVolumeBitmap
vdh, err := dsk.PRODOSGetVDH(2)
if err != nil {
return vb, err
}
b := vdh.GetBitmapPointer()
data, err := dsk.PRODOSGetBlock(b)
if err != nil {
return vb, err
}
//copy(vb[:], data)
vb = ProDOSVolumeBitmap{
Data: data,
blockid: b,
blockoffset: 0,
}
return vb, nil
}
type ProDOSVolumeBitmap struct {
Data []byte
blockid int
blockoffset int
}
func (vb ProDOSVolumeBitmap) IsBlockFree(b int) bool {
bidx := b / 8
bit := 7 - (b % 8)
mask := byte(1 << uint(bit))
return (vb.Data[bidx] & mask) == mask
}
func (vb ProDOSVolumeBitmap) SetBlockFree(b int, free bool) {
//log.Printf("Freeing block %d", b)
bidx := b / 8
bit := 7 - (b % 8)
setmask := byte(1 << uint(bit))
clrmask := 0xff ^ setmask
if free {
vb.Data[bidx] = vb.Data[bidx] | setmask
} else {
vb.Data[bidx] = vb.Data[bidx] & clrmask
}
}
// -- Prodos file descriptor
type ProDOSFileDescriptor struct {
Data []byte
blockid int
blockoffset int
entryNum int
}
func (fd *ProDOSFileDescriptor) SetData(data []byte, blockid int, blockoffset int) {
fd.blockid = blockid
fd.blockoffset = blockoffset
if fd.Data == nil && len(data) == 39 {
fd.Data = data
return
}
for i, v := range data {
fd.Data[i] = v
}
}
func (fd *ProDOSFileDescriptor) GetNameLength() int {
return int(fd.Data[0] & 0xf)
}
func (fd *ProDOSFileDescriptor) GetStorageType() ProDOSStorageType {
return ProDOSStorageType((fd.Data[0]) >> 4)
}
func (fd *ProDOSFileDescriptor) Name() string {
l := fd.GetNameLength()
b := fd.Data[1 : 1+l]
s := ""
for _, v := range b {
s += string(rune(PokeToAscii(uint(v), false)))
}
s = strings.ToLower(strings.Trim(s, " "))
switch fd.Type() {
case FileType_PD_APP:
s += ".a"
case FileType_PD_INT:
s += ".i"
case FileType_PD_BIN:
s += ".s"
case FileType_PD_TXT:
s += ".t"
case FileType_PD_SYS:
s += ".s"
}
return s
}
func (fd *ProDOSFileDescriptor) NameUnadorned() string {
l := fd.GetNameLength()
b := fd.Data[1 : 1+l]
s := ""
for _, v := range b {
s += string(rune(PokeToAscii(uint(v), false)))
}
s = strings.ToLower(strings.Trim(s, " "))
return s
}
func (fd *ProDOSFileDescriptor) SetName(name string) {
name = strings.ToUpper(name)
if len(name) > 15 {
name = name[:15]
}
l := len(name)
for i, v := range []byte(name) {
fd.Data[1+i] = v
}
fd.Data[0] = (fd.Data[0] & 0xf0) | byte(l)
}
func (fd *ProDOSFileDescriptor) SetStorageType(t ProDOSStorageType) {
fd.Data[0] = (fd.Data[0] & 0x0f) | (byte(t) << 4)
}
func (fd *ProDOSFileDescriptor) SetAccessMode(t ProDOSAccessMode) {
fd.Data[0x1e] = byte(t)
}
func (fd *ProDOSFileDescriptor) AccessMode() ProDOSAccessMode {
return ProDOSAccessMode(fd.Data[0x1e])
}
func (fd *ProDOSFileDescriptor) SetLocked(b bool) {
accessMode := fd.AccessMode()
if b {
accessMode = accessMode & (AccessType_Changed | AccessType_Readable)
} else {
accessMode = accessMode | AccessType_Destroy | AccessType_Writable | AccessType_Rename
}
fd.SetAccessMode(accessMode)
}
func (fd *ProDOSFileDescriptor) IsLocked() bool {
a := fd.AccessMode()
return a&(AccessType_Destroy|AccessType_Rename|AccessType_Writable) == 0
}
func prodosStampBytesToTime(in []byte) time.Time {
dbits := (int(in[0x01]) << 8) | int(in[0x00])
day := dbits & 31
month := (dbits >> 5) & 15
year := (dbits >> 9) & 127
tbits := (int(in[0x03]) << 8) | int(in[0x02])
mins := tbits & 63
hours := (tbits >> 8)
if year < 70 {
year += 100
}
year += 1900
return time.Date(year, time.Month(month), day, hours, mins, 0, 0, time.Local)
}
func timeToProdosStampBytes(t time.Time) []byte {
year, month, day, hour, minute := t.Year(), int(t.Month()), t.Day(), t.Hour(), t.Minute()
year = year - 1900
if year > 99 {
year -= 100
}
dbits := (year << 9) | (month << 5) | day
tbits := (hour << 8) | minute
return []byte{
byte(dbits & 0xff),
byte(dbits >> 8),
byte(tbits & 0xff),
byte(tbits >> 8),
}
}
func (fd *ProDOSFileDescriptor) CreateTime() time.Time {
b := fd.Data[0x18:0x1C]
return prodosStampBytesToTime(b)
}
func (fd *ProDOSFileDescriptor) SetCreateTime(t time.Time) {
b := timeToProdosStampBytes(t)
for i, v := range b {
fd.Data[0x18+i] = v
}
}
func (fd *ProDOSFileDescriptor) ModTime() time.Time {
b := fd.Data[0x21:0x25]
return prodosStampBytesToTime(b)
}
func (fd *ProDOSFileDescriptor) SetModTime(t time.Time) {
b := timeToProdosStampBytes(t)
for i, v := range b {
fd.Data[0x21+i] = v
}
}
func (fd *ProDOSFileDescriptor) GetVersion() int {
return int(fd.Data[0x1c])
}
func (fd *ProDOSFileDescriptor) SetVersion(b int) {
fd.Data[0x1c] = byte(b)
}
func (fd *ProDOSFileDescriptor) GetMinVersion() int {
return int(fd.Data[0x1d])
}
func (fd *ProDOSFileDescriptor) SetMinVersion(b int) {
fd.Data[0x1d] = byte(b)
}
type ProDOSAccessMode byte
const (
AccessType_Destroy ProDOSAccessMode = 0x80
AccessType_Rename ProDOSAccessMode = 0x40
AccessType_Changed ProDOSAccessMode = 0x20
AccessType_Writable ProDOSAccessMode = 0x02
AccessType_Readable ProDOSAccessMode = 0x01
//
AccessType_Default ProDOSAccessMode = AccessType_Readable | AccessType_Writable | AccessType_Rename | AccessType_Destroy
)
type ProDOSStorageType byte
const (
StorageType_Inactive ProDOSStorageType = 0x0
StorageType_Seedling ProDOSStorageType = 0x1
StorageType_Sapling ProDOSStorageType = 0x2
StorageType_Tree ProDOSStorageType = 0x3
StorageType_SubDir_File ProDOSStorageType = 0xd
StorageType_SubDir_Header ProDOSStorageType = 0xe
StorageType_Volume_Header ProDOSStorageType = 0xf
)
type ProDOSFileType byte
const (
FileType_PD_None ProDOSFileType = 0x00
FileType_PD_TXT ProDOSFileType = 0x04
FileType_PD_Directory ProDOSFileType = 0x0f
FileType_PD_BIN ProDOSFileType = 0x06
FileType_PD_INT ProDOSFileType = 0xfa
FileType_PD_INT_Var ProDOSFileType = 0xfb
FileType_PD_APP ProDOSFileType = 0xfc
FileType_PD_APP_Var ProDOSFileType = 0xfd
FileType_PD_Reloc ProDOSFileType = 0xfe
FileType_PD_SYS ProDOSFileType = 0xff
)
var ProDOSTypeMap = map[ProDOSFileType][2]string{
0x00: [2]string{"UNK", "Unknown"},
0x01: [2]string{"BAD", "Bad Block"},
0x02: [2]string{"PCD", "Pascal Code"},
0x03: [2]string{"PTX", "Pascal Text"},
0x04: [2]string{"TXT", "ASCII Text"},
0x05: [2]string{"PDA", "Pascal Data"},
0x06: [2]string{"BIN", "Binary File"},
0x07: [2]string{"FNT", "Apple III Font"},
0x08: [2]string{"FOT", "HiRes/Double HiRes Graphics"},
0x09: [2]string{"BA3", "Apple III Basic Program"},
0x0A: [2]string{"DA3", "Apple III Basic Data"},
0x0B: [2]string{"WPF", "Generic Word Processing"},
0x0C: [2]string{"SOS", "SOS System File"},
0x0F: [2]string{"DIR", "ProDOS Directory"},
0x10: [2]string{"RPD", "RPS Data"},
0x11: [2]string{"RPI", "RPS Index"},
0x12: [2]string{"AFD", "AppleFile Discard"},
0x13: [2]string{"AFM", "AppleFile Model"},
0x14: [2]string{"AFR", "AppleFile Report"},
0x15: [2]string{"SCL", "Screen Library"},
0x16: [2]string{"PFS", "PFS Document"},
0x19: [2]string{"ADB", "AppleWorks Database"},
0x1A: [2]string{"AWP", "AppleWorks Word Processing"},
0x1B: [2]string{"ASP", "AppleWorks Spreadsheet"},
0x80: [2]string{"GES", "System File"},
0x81: [2]string{"GEA", "Desk Accessory"},
0x82: [2]string{"GEO", "Application"},
0x83: [2]string{"GED", "Document"},
0x84: [2]string{"GEF", "Font"},
0x85: [2]string{"GEP", "Printer Driver"},
0x86: [2]string{"GEI", "Input Driver"},
0x87: [2]string{"GEX", "Auxiliary Driver"},
0x89: [2]string{"GEV", "Swap File"},
0x8B: [2]string{"GEC", "Clock Driver"},
0x8C: [2]string{"GEK", "Interface Card Driver"},
0x8D: [2]string{"GEW", "Formatting Data"},
0xA0: [2]string{"WP", " WordPerfect"},
0xAB: [2]string{"GSB", "Apple IIgs BASIC Program"},
0xAC: [2]string{"TDF", "Apple IIgs BASIC TDF"},
0xAD: [2]string{"BDF", "Apple IIgs BASIC Data"},
0x60: [2]string{"PRE", "PC Pre-Boot"},
0x6B: [2]string{"BIO", "PC BIOS"},
0x66: [2]string{"NCF", "ProDOS File Navigator Command File"},
0x6D: [2]string{"DVR", "PC Driver"},
0x6E: [2]string{"PRE", "PC Pre-Boot"},
0x6F: [2]string{"HDV", "PC Hard Disk Image"},
0x50: [2]string{"GWP", "Apple IIgs Word Processing"},
0x51: [2]string{"GSS", "Apple IIgs Spreadsheet"},
0x52: [2]string{"GDB", "Apple IIgs Database"},
0x53: [2]string{"DRW", "Object Oriented Graphics"},
0x54: [2]string{"GDP", "Apple IIgs Desktop Publishing"},
0x55: [2]string{"HMD", "HyperMedia"},
0x56: [2]string{"EDU", "Educational Program Data"},
0x57: [2]string{"STN", "Stationery"},
0x58: [2]string{"HLP", "Help File"},
0x59: [2]string{"COM", "Communications"},
0x5A: [2]string{"CFG", "Configuration"},
0x5B: [2]string{"ANM", "Animation"},
0x5C: [2]string{"MUM", "Multimedia"},
0x5D: [2]string{"ENT", "Entertainment"},
0x5E: [2]string{"DVU", "Development Utility"},
0x41: [2]string{"OCR", "Optical Character Recognition"},
0x42: [2]string{"FTD", "File Type Definitions"},
0x20: [2]string{"TDM", "Desktop Manager File"},
0x21: [2]string{"IPS", "Instant Pascal Source"},
0x22: [2]string{"UPV", "UCSD Pascal Volume"},
0x29: [2]string{"3SD", "SOS Directory"},
0x2A: [2]string{"8SC", "Source Code"},
0x2B: [2]string{"8OB", "Object Code"},
0x2C: [2]string{"8IC", "Interpreted Code"},
0x2D: [2]string{"8LD", "Language Data"},
0x2E: [2]string{"P8C", "ProDOS 8 Code Module"},
0xB0: [2]string{"SRC", "Apple IIgs Source Code"},
0xB1: [2]string{"OBJ", "Apple IIgs Object Code"},
0xB2: [2]string{"LIB", "Apple IIgs Library"},
0xB3: [2]string{"S16", "Apple IIgs Application Program"},
0xB4: [2]string{"RTL", "Apple IIgs Runtime Library"},
0xB5: [2]string{"EXE", "Apple IIgs Shell Script"},
0xB6: [2]string{"PIF", "Apple IIgs Permanent INIT"},
0xB7: [2]string{"TIF", "Apple IIgs Temporary INIT"},
0xB8: [2]string{"NDA", "Apple IIgs New Desk Accessory"},
0xB9: [2]string{"CDA", "Apple IIgs Classic Desk Accessory"},
0xBA: [2]string{"TOL", "Apple IIgs Tool"},
0xBB: [2]string{"DRV", "Apple IIgs Device Driver"},
0xBC: [2]string{"LDF", "Apple IIgs Generic Load File"},
0xBD: [2]string{"FST", "Apple IIgs File System Translator"},
0xBF: [2]string{"DOC", "Apple IIgs Document "},
0xC0: [2]string{"PNT", "Apple IIgs Packed Super HiRes"},
0xC1: [2]string{"PIC", "Apple IIgs Super HiRes"},
0xC2: [2]string{"ANI", "PaintWorks Animation"},
0xC3: [2]string{"PAL", "PaintWorks Palette"},
0xC5: [2]string{"OOG", "Object-Oriented Graphics"},
0xC6: [2]string{"SCR", "Script"},
0xC7: [2]string{"CDV", "Apple IIgs Control Panel"},
0xC8: [2]string{"FON", "Apple IIgs Font"},
0xC9: [2]string{"FND", "Apple IIgs Finder Data"},
0xCA: [2]string{"ICN", "Apple IIgs Icon "},
0xD5: [2]string{"MUS", "Music"},
0xD6: [2]string{"INS", "Instrument"},
0xD7: [2]string{"MDI", "MIDI"},
0xD8: [2]string{"SND", "Apple IIgs Audio"},
0xDB: [2]string{"DBM", "DB Master Document"},
0xE0: [2]string{"LBR", "Archive"},
0xE2: [2]string{"ATK", "AppleTalk Data"},
0xEE: [2]string{"R16", "EDASM 816 Relocatable Code"},
0xEF: [2]string{"PAR", "Pascal Area"},
0xF0: [2]string{"CMD", "ProDOS Command File"},
0xF1: [2]string{"OVL", "User Defined 1"},
0xF2: [2]string{"UD2", "User Defined 2"},
0xF3: [2]string{"UD3", "User Defined 3"},
0xF4: [2]string{"UD4", "User Defined 4"},
0xF5: [2]string{"BAT", "User Defined 5"},
0xF6: [2]string{"UD6", "User Defined 6"},
0xF7: [2]string{"UD7", "User Defined 7"},
0xF8: [2]string{"PRG", "User Defined 8"},
0xF9: [2]string{"P16", "ProDOS-16 System File"},
0xFA: [2]string{"INT", "Integer BASIC Program"},
0xFB: [2]string{"IVR", "Integer BASIC Variables"},
0xFC: [2]string{"BAS", "Applesoft BASIC Program"},
0xFD: [2]string{"VAR", "Applesoft BASIC Variables"},
0xFE: [2]string{"REL", "EDASM Relocatable Code"},
0xFF: [2]string{"SYS", "ProDOS-8 System File"},
}
func (t ProDOSFileType) String() string {
info, ok := ProDOSTypeMap[t]
if ok {
return info[1]
}
return "Unknown"
}
func (ft ProDOSFileType) Ext() string {
info, ok := ProDOSTypeMap[ft]
if ok {
return info[0]
}
return "BIN"
}
func ProDOSFileTypeFromExt(ext string) ProDOSFileType {
for ft, info := range ProDOSTypeMap {
if strings.ToUpper(ext) == info[0] {
return ft
}
}
return 0x06
}
func (t ProDOSFileType) Valid() bool {
if t == FileType_PD_None {
return false
}
_, ok := ProDOSTypeMap[t]
return ok
}
func (fd *ProDOSFileDescriptor) Type() ProDOSFileType {
return ProDOSFileType(fd.Data[16])
}
func (fd *ProDOSFileDescriptor) SetType(t ProDOSFileType) {
fd.Data[16] = byte(t)
}
func (fd *ProDOSFileDescriptor) IndexBlock() int {
return int(fd.Data[17]) + 256*int(fd.Data[18])
}
func (fd *ProDOSFileDescriptor) SetIndexBlock(b int) {
fd.Data[17] = byte(b & 0xff)
fd.Data[18] = byte(b / 256)
}
func (fd *ProDOSFileDescriptor) TotalBlocks() int {
return int(fd.Data[19]) + 256*int(fd.Data[20])
}
func (fd *ProDOSFileDescriptor) SetTotalBlocks(b int) {
fd.Data[19] = byte(b & 0xff)
fd.Data[20] = byte(b / 256)
}
func (fd *ProDOSFileDescriptor) AuxType() int {
return int(fd.Data[31]) + 256*int(fd.Data[32])
}
func (fd *ProDOSFileDescriptor) SetAuxType(b int) {
fd.Data[31] = byte(b & 0xff)
fd.Data[32] = byte(b / 256)
}
func (fd *ProDOSFileDescriptor) TotalSectors() int {
return fd.TotalBlocks() / 2
}
func (fd *ProDOSFileDescriptor) Size() int {
return int(fd.Data[21]) + 256*int(fd.Data[22]) + 65536*int(fd.Data[23])
}
func (fd *ProDOSFileDescriptor) SetSize(v int) {
fd.Data[21] = byte(v & 0xff)
fd.Data[22] = byte((v >> 8) & 0xff)
fd.Data[23] = byte((v >> 16) & 0xff)
}
func (fd *ProDOSFileDescriptor) SetHeaderPointer(v int) {
fd.Data[0x25] = byte(v & 0xff)
fd.Data[0x26] = byte((v >> 8) & 0xff)
}
func (fd *ProDOSFileDescriptor) HeaderPointer() int {
return int(fd.Data[0x25]) + 256*int(fd.Data[0x26])
}
func (d *DSKWrapper) PRODOS800GetBlock(block int) ([]byte, error) {
t, s1, s2 := d.PRODOS800GetBlockSectors(block)
e := d.Seek(t, s1)
if e != nil {
return []byte(nil), e
}
data := make([]byte, 0)
c1 := d.Read()
data = append(data, c1...)
e = d.Seek(t, s2)
if e != nil {
return []byte(nil), e
}
c2 := d.Read()
data = append(data, c2...)
return data, nil
}
func (d *DSKWrapper) PRODOSGetBlock(block int) ([]byte, error) {
t, s1, s2 := d.PRODOSGetBlockSectors(block)
e := d.Seek(t, s1)
if e != nil {
return []byte(nil), e
}
data := make([]byte, 0)
c1 := d.Read()
data = append(data, c1...)
e = d.Seek(t, s2)
if e != nil {
return []byte(nil), e
}
c2 := d.Read()
data = append(data, c2...)
return data, nil
}
func (d *DSKWrapper) PRODOS800GetVDH(b int) (*VDH, error) {
data, e := d.PRODOS800GetBlock(b)
if e != nil {
return nil, e
}
vdh := &VDH{}
vdh.SetData(data[4:43], b, 4)
return vdh, nil
}
func (d *DSKWrapper) PRODOSGetVDH(b int) (*VDH, error) {
data, e := d.PRODOSGetBlock(b)
if e != nil {
return nil, e
}
vdh := &VDH{}
vdh.SetData(data[4:43], b, 4)
return vdh, nil
}
func (d *DSKWrapper) PRODOSSetVDH(b int, vdh *VDH) error {
data, e := d.PRODOSGetBlock(b)
if e != nil {
return e
}
for i, v := range vdh.Data {
data[4+i] = v
}
return d.PRODOSWrite(b, data)
}
func (d *DSKWrapper) PRODOSGetCatalogPathed(start int, path string, pattern string) (*VDH, []ProDOSFileDescriptor, error) {
path = strings.Trim(path, "/")
//fmt.Printf("PRODOSGetCatalogPathed(%d, \"%s\", \"%s\" )\n", start, path, pattern)
//start := 2 // where we start our descent
if path != "" {
parts := strings.Split(strings.Trim(path, "/"), "/")
subdir := parts[0]
parts = parts[1:]
// find subdirectories
vdh, files, e := d.PRODOSGetCatalog(start, subdir)
if e != nil {
return vdh, files, e
}
if len(files) != 1 {
return vdh, files, e
}
if files[0].Type() != FileType_PD_Directory {
return vdh, files, errors.New("Not a directory")
}
// ok, we found the directory...
if len(parts) > 0 {
// more subdirs
//fmt.Printf(">> Entering prodos subdir [%s]\n", files[0].Name())
newpathstr := strings.Join(parts, "/")
return d.PRODOSGetCatalogPathed(files[0].IndexBlock(), newpathstr, pattern)
} else {
// get directory based on this block
//fmt.Printf("-- Entering prodos subdir [%s]\n", files[0].Name())
return d.PRODOSGetCatalog(files[0].IndexBlock(), pattern)
}
} else {
return d.PRODOSGetCatalog(start, pattern)
}
}
func (d *DSKWrapper) PRODOSGetCatalog(startblock int, pattern string) (*VDH, []ProDOSFileDescriptor, error) {
//fmt.Printf("GetCatalogProDOS(%d, %s)\n", startblock, pattern)
var err error
var re *regexp.Regexp
var patterntmp string
if pattern != "" {
patterntmp = strings.Replace(pattern, ".", "[.]", -1)
patterntmp = strings.Replace(patterntmp, "*", ".*", -1)
patterntmp = "(?i)^" + patterntmp + "$"
re = regexp.MustCompile(patterntmp)
}
var files []ProDOSFileDescriptor
var e error
var vtoc *VDH
if d.Format.ID == DF_PRODOS_800KB {
vtoc, e = d.PRODOS800GetVDH(startblock)
} else {
vtoc, e = d.PRODOSGetVDH(startblock)
}
if e != nil {
return vtoc, files, e
}
activeentries := 0
filecount := vtoc.GetFileCount()
blockentries := 2
entriesperblock := vtoc.GetEntriesPerBlock()
refnum := startblock
var data []byte
if d.Format.ID == DF_PRODOS_800KB {
data, _ = d.PRODOS800GetBlock(refnum)
} else {
data, _ = d.PRODOSGetBlock(refnum)
}
nextblock := int(data[2]) + 256*int(data[3])
//fmt.Printf("ActiveCount = %d\n", filecount)
entrypointer := 4 + PRODOS_ENTRY_SIZE
for activeentries < filecount {
if data[entrypointer] != 0x00 {
// Valid entry
chunk := data[entrypointer : entrypointer+PRODOS_ENTRY_SIZE]
fd := ProDOSFileDescriptor{}
fd.SetData(chunk, refnum, entrypointer)
if fd.Type().Valid() {
var skipname bool = false
if re != nil {
//fmt.Printf("Checking [%s] against regex /%s/\n", fd.Name(), patterntmp)
skipname = !re.MatchString(fd.Name())
}
if fd.GetStorageType() != StorageType_Inactive && !skipname {
files = append(files, fd)
}
}
activeentries++
}
if activeentries < filecount {
if blockentries == entriesperblock {
refnum = nextblock
if d.Format.ID == DF_PRODOS_800KB {
data, err = d.PRODOS800GetBlock(refnum)
} else {
data, err = d.PRODOSGetBlock(refnum)
}
if err != nil {
break
}
nextblock = int(data[2]) + 256*int(data[3])
blockentries = 0x01
entrypointer = 0x04
} else {
entrypointer += PRODOS_ENTRY_SIZE
blockentries++
}
}
}
return vtoc, files, nil
}
func (d *DSKWrapper) PRODOSReadFileSectors(fd ProDOSFileDescriptor, maxblocks int) ([]byte, error) {
var data, index, chunk []byte
var e error
switch fd.GetStorageType() {
case StorageType_Seedling:
/* single block pointed to */
if d.Format.ID == DF_PRODOS_800KB {
data, _ = d.PRODOS800GetBlock(fd.IndexBlock())
} else {
data, _ = d.PRODOSGetBlock(fd.IndexBlock())
}
count := fd.Size()
if count > len(data) {
count = len(data)
}
return data[:count], e
case StorageType_Sapling:
if d.Format.ID == DF_PRODOS_800KB {
index, _ = d.PRODOS800GetBlock(fd.IndexBlock())
} else {
index, _ = d.PRODOSGetBlock(fd.IndexBlock())
}
data := make([]byte, 0)
bptr := 0
remaining := fd.Size() - len(data)
for len(data) < fd.Size() && bptr+256 < len(index) {
blocknum := int(index[bptr]) + 256*int(index[bptr+256])
//fmt.Printf("File block %d (%d %d)\n", blocknum, int(index[bptr]), int(index[bptr+1]))
if d.Format.ID == DF_PRODOS_800KB {
chunk, e = d.PRODOS800GetBlock(blocknum)
} else {
chunk, e = d.PRODOSGetBlock(blocknum)
}
if e != nil {
return data, e
}
count := 512
if remaining < count {
count = remaining
}
data = append(data, chunk[:count]...)
bptr += 1
remaining = fd.Size() - len(data)
}
return data, e
case StorageType_Tree:
return []byte(nil), errors.New("Unimplemented yet... soz :(")
}
return []byte(nil), nil
}
func (d *DSKWrapper) PRODOSReadFile(fd ProDOSFileDescriptor) (int, int, []byte, error) {
data, e := d.PRODOSReadFileSectors(fd, -1)
if e != nil {
return 0, 0, data, e
}
switch fd.Type() {
case FileType_PD_INT:
return fd.Size(), fd.AuxType(), IntegerDetoks(data), nil
case FileType_PD_APP:
return fd.Size(), fd.AuxType(), ApplesoftDetoks(data), nil
case FileType_PD_TXT:
return fd.Size(), fd.AuxType(), data, nil
case FileType_PD_BIN:
return fd.Size(), fd.AuxType(), data, nil
default:
return fd.Size(), fd.AuxType(), data, nil
}
}
func (d *DSKWrapper) PRODOSReadFileRaw(fd ProDOSFileDescriptor) (int, int, []byte, error) {
data, e := d.PRODOSReadFileSectors(fd, -1)
if e != nil {
return 0, 0, data, e
}
switch fd.Type() {
case FileType_PD_INT:
return fd.Size(), fd.AuxType(), data, nil
case FileType_PD_APP:
return fd.Size(), fd.AuxType(), data, nil
case FileType_PD_TXT:
return fd.Size(), fd.AuxType(), data, nil
case FileType_PD_BIN:
return fd.Size(), fd.AuxType(), data, nil
default:
return fd.Size(), fd.AuxType(), data, nil
}
}
func (d *DSKWrapper) PRODOSChecksumBlock(b int) string {
bl, _ := d.PRODOSGetBlock(b)
return Checksum(bl)
}
func (d *DSKWrapper) PRODOS800ChecksumBlock(b int) string {
bl, _ := d.PRODOS800GetBlock(b)
return Checksum(bl)
}
func (d *DSKWrapper) PRODOSGetBlockSectors(block int) (int, int, int) {
bpt := d.Format.USPT() / 2
track := block / bpt
bo := block % bpt
if d.Layout == SectorOrderProDOSLinear || len(d.Data) >= PRODOS_800KB_DISK_BYTES {
return track, bo * 2, bo*2 + 1
}
switch bo {
case 0:
return track, 0x0, 0xe
case 1:
return track, 0xd, 0xc
case 2:
return track, 0xb, 0xa
case 3:
return track, 0x9, 0x8
case 4:
return track, 0x7, 0x6
case 5:
return track, 0x5, 0x4
case 6:
return track, 0x3, 0x2
case 7:
return track, 0x1, 0xf
}
return track, 0x0, 0xe
}
func (d *DSKWrapper) PRODOS800GetBlockSectors(block int) (int, int, int) {
dblBlock := block * 2
spt := 40
track := dblBlock / spt
s1 := dblBlock % spt
s2 := (dblBlock + 1) % spt
return track, s1, s2
}
// PRODOSGetDirBlocks returns a list of blocks for the current directory level
func (dsk *DSKWrapper) PRODOSGetDirBlocks(start int) (*VDH, []int, [][]byte, error) {
blocks := make([]int, 0)
chunks := make([][]byte, 0)
vdh, err := dsk.PRODOSGetVDH(start)
if err != nil {
return vdh, blocks, chunks, err
}
// okay lets trail the directory
blocks = append(blocks, start)
data, _ := dsk.PRODOSGetBlock(start)
chunks = append(chunks, data)
nextBlock := int(data[0x02]) + 256*int(data[0x03])
for nextBlock != 0 {
blocks = append(blocks, nextBlock)
data, _ := dsk.PRODOSGetBlock(nextBlock)
chunks = append(chunks, data)
nextBlock = int(data[0x02]) + 256*int(data[0x03])
}
return vdh, blocks, chunks, nil
}
// PRODOSFindDirBlocks locates the blocks for a given directory (if it exists!)
func (dsk *DSKWrapper) PRODOSFindDirBlocks(start int, path string) (*VDH, []int, [][]byte, error) {
path = strings.Trim(path, "/")
if path == "" {
return dsk.PRODOSGetDirBlocks(start)
}
segments := strings.Split(path, "/")
target := segments[0]
segments = segments[1:]
vdh, files, err := dsk.PRODOSGetCatalog(start, "")
if err != nil {
return vdh, nil, nil, err
}
for _, f := range files {
if f.Type() != FileType_PD_Directory {
continue
}
if strings.ToLower(target) == strings.ToLower(f.NameUnadorned()) {
// matched path
newpath := strings.Join(segments, "/")
return dsk.PRODOSFindDirBlocks(f.IndexBlock(), newpath)
}
}
return vdh, nil, nil, errors.New("Path not found")
}
// PRODOSGetFirstFreeEntry for a given path, will find the first free file descriptor
// it will expand the directory if needed to create a free block
func (dsk *DSKWrapper) PRODOSGetFirstFreeEntry(path string, name string, grow bool) (*ProDOSFileDescriptor, error) {
vdh, blockList, blockData, err := dsk.PRODOSFindDirBlocks(2, path)
if err != nil {
return nil, err
}
entries := 0
// if we are here, we found the right directory...
for idx, data := range blockData {
count := 0
if idx == 0 {
count = 1
entries = 1
}
for count < vdh.GetEntriesPerBlock() {
offset := 4 + count*PRODOS_ENTRY_SIZE
chunk := data[offset : offset+PRODOS_ENTRY_SIZE]
fd := &ProDOSFileDescriptor{}
fd.SetData(chunk, blockList[idx], offset)
fd.entryNum = entries
//Printf("--> Check entry: %s, %v, %v\n", fd.NameUnadorned(), fd.CreateTime(), fd.ModTime())
if fd.GetStorageType() != 0x00 && strings.ToLower(fd.NameUnadorned()) == strings.ToLower(name) {
return fd, nil
} else if fd.GetStorageType() == 0x00 {
//fmt.Printf("found at entry %d in block %d\n", entries, blockList[idx])
return fd, nil
}
count += 1
entries++
}
}
if !grow {
return nil, errors.New("No free slot: told not to grow directory")
}
// If we got here, we need to create a new block
freeBlocks, err := dsk.PRODOSGetFreeBlocks(1, vdh.GetTotalBlocks())
if err != nil {
return nil, errors.New("Could not extend directory")
}
data, err := dsk.PRODOSGetBlock(freeBlocks[0])
prevBlock := blockList[len(blockList)-1]
data[0x00] = byte(prevBlock & 0xff)
data[0x01] = byte(prevBlock / 0x100)
offset := 4
chunk := data[offset : offset+PRODOS_ENTRY_SIZE]
fd := &ProDOSFileDescriptor{}
fd.entryNum = entries
fd.SetData(chunk, freeBlocks[0], offset)
dsk.PRODOSMarkBlocks(freeBlocks, false)
return fd, nil
}
func (dsk *DSKWrapper) PRODOSMarkBlocks(list []int, free bool) error {
vbm, err := dsk.PRODOSGetVolumeBitmap()
if err != nil {
return err
}
for _, b := range list {
vbm.SetBlockFree(b, free)
}
//fmt.Printf("Writing Volume bitmap to block %d\n", vbm.blockid)
return dsk.PRODOSWrite(vbm.blockid, vbm.Data)
}
func (dsk *DSKWrapper) PRODOSGetFreeBlocks(count int, totalBlocks int) ([]int, error) {
vbm, err := dsk.PRODOSGetVolumeBitmap()
if err != nil {
return nil, err
}
b := 0
blocks := make([]int, 0)
for b < totalBlocks && len(blocks) < count {
if vbm.IsBlockFree(b) {
blocks = append(blocks, b)
}
b++
}
if len(blocks) == count {
return blocks, nil
}
return blocks, errors.New("Not enough blocks")
}
func (dsk *DSKWrapper) PRODOSDeleteFile(path string, name string) error {
fd, err := dsk.PRODOSGetNamedEntry(path, name)
if err != nil {
return err
}
if fd.GetStorageType() == 0x00 {
return errors.New("Not found")
}
// At this stage we have a match
access := fd.AccessMode()
if access&AccessType_Destroy == 0 {
return errors.New("Permission denied")
}
if access&AccessType_Writable == 0 {
return errors.New("Read-only file")
}
// Make sure its either a sapling or a seedling
st := fd.GetStorageType()
if st != StorageType_Sapling && st != StorageType_Seedling && st != StorageType_SubDir_File {
return errors.New("Special file deletion not implemented: yet.")
} else if st == StorageType_SubDir_File {
return dsk.PRODOSDeleteDirectory(path, name)
}
var removeBlocks []int
switch st {
case StorageType_Seedling:
removeBlocks = append(removeBlocks, fd.IndexBlock())
case StorageType_Sapling:
removeBlocks = append(removeBlocks, fd.IndexBlock())
ib, err := dsk.PRODOSGetBlock(removeBlocks[0])
if err != nil {
return err
}
maxBlocks := 1600
if len(dsk.Data) == STD_DISK_BYTES {
maxBlocks = 280
}
i := 0
b := int(ib[i]) + 256*int(ib[256+i])
for i < 256 && b != 0 && b <= maxBlocks {
removeBlocks = append(removeBlocks, b)
b = int(ib[i]) + 256*int(ib[256+i])
i++
}
}
err = dsk.PRODOSMarkBlocks(removeBlocks, true)
if err != nil {
return err
}
// // get the VDH -- we need this later
vdh, _, _, err := dsk.PRODOSFindDirBlocks(2, path)
if err != nil {
return err
}
// now delete the fileentry
fd.SetStorageType(StorageType_Inactive)
err = fd.Publish(dsk)
if err != nil {
return err
}
// update filecount
vdh.SetFileCount(vdh.GetFileCount() - 1)
return vdh.Publish(dsk)
}
func (dsk *DSKWrapper) PRODOSWriteFile(path string, name string, kind ProDOSFileType, data []byte, auxtype int) error {
name = strings.ToUpper(name)
nst := StorageType_Seedling
blocksNeeded := len(data)/512 + 1
totalBlocks := blocksNeeded
if blocksNeeded > 1 {
nst = StorageType_Sapling
totalBlocks++ // extra block for blocklist
} else if blocksNeeded > 256 {
return errors.New("Not implemented: Tree write")
}
var origTime time.Time
var origAccess ProDOSAccessMode
fd, err := dsk.PRODOSGetNamedEntry(path, name)
if err == nil {
origTime = fd.CreateTime() // we need this later
origAccess = fd.AccessMode()
err = dsk.PRODOSDeleteFile(path, name)
if err != nil {
return err
}
} else {
fd, err = dsk.PRODOSGetFirstFreeEntry(path, name, true)
if err != nil {
return err
}
}
vdh, err := dsk.PRODOSGetVDH(2)
if err != nil {
return err
}
freeBlocks, err := dsk.PRODOSGetFreeBlocks(totalBlocks, vdh.GetTotalBlocks())
if err != nil {
return err
}
// Okay got enough blocks
switch nst {
case StorageType_Sapling:
err = dsk.PRODOSWriteSaplingBlocks(freeBlocks[0], freeBlocks[1:], data)
if err != nil {
return err
}
case StorageType_Seedling:
//fmt.Printf("Write Seedling %d bytes data to block %d\n", len(data), freeBlocks[0])
err = dsk.PRODOSWrite(freeBlocks[0], data)
if err != nil {
return err
}
}
// Get the current directories directory header
dvdh, blocks, _, err := dsk.PRODOSFindDirBlocks(2, path)
if err != nil {
return err
}
// common details - note we preserve access mode and time when overwriting the same file
fd.SetAuxType(auxtype)
fd.SetName(name)
fd.SetType(kind)
fd.SetTotalBlocks(totalBlocks)
fd.SetIndexBlock(freeBlocks[0])
fd.SetSize(len(data))
fd.SetStorageType(nst)
if origAccess == 0x00 {
fd.SetAccessMode(AccessType_Default)
} else {
fd.SetAccessMode(origAccess)
}
if origTime.IsZero() {
fd.SetCreateTime(time.Now())
} else {
fd.SetCreateTime(origTime)
}
fd.SetModTime(time.Now())
fd.SetHeaderPointer(blocks[0])
fd.Publish(dsk)
dvdh.SetFileCount(dvdh.GetFileCount() + 1)
dvdh.Publish(dsk)
err = dsk.PRODOSMarkBlocks(freeBlocks, false)
if err != nil {
return err
}
return nil
}
func (fd *ProDOSFileDescriptor) Publish(dsk *DSKWrapper) error {
//fmt.Printf("Writing FD data back to block %d, offset %d\n", fd.blockid, fd.blockoffset)
bd, err := dsk.PRODOSGetBlock(fd.blockid)
if err != nil {
return err
}
for i, v := range fd.Data {
bd[fd.blockoffset+i] = v
}
// for i, _ := range bd {
// bd[i] = byte(0xff ^ (i % 2))
// }
//Dump(bd)
return dsk.PRODOSWrite(fd.blockid, bd)
}
func (dsk *DSKWrapper) PRODOSWrite(b int, data []byte) error {
//log.Printf("====> Request to write block %.4x", b)
for len(data) < 512 {
data = append(data, 0x00)
}
t, s1, s2 := dsk.PRODOSGetBlockSectors(b)
err := dsk.Seek(t, s1)
if err != nil {
return err
}
dsk.Write(data[:256])
err = dsk.Seek(t, s2)
if err != nil {
return err
}
dsk.Write(data[256:])
return nil
}
func (dsk *DSKWrapper) PRODOSWriteSaplingBlocks(indexBlock int, dataBlocks []int, data []byte) error {
if len(dataBlocks) > 256 || len(data) > 0x20000 {
return errors.New("Too many data blocks")
}
ib := make([]byte, 512)
for i, blocknum := range dataBlocks {
// index the block
ib[0+i] = byte(blocknum & 0xff)
ib[256+i] = byte(blocknum / 0x100)
// data offset...
ptr := 512 * i
end := ptr + 512
if end > len(data) {
end = len(data)
}
chunk := data[ptr:end]
for len(chunk) < 512 {
chunk = append(chunk, 0x00)
}
err := dsk.PRODOSWrite(blocknum, chunk)
if err != nil {
return err
}
}
return dsk.PRODOSWrite(indexBlock, ib)
}
// PRODOSCreateDirectory tries to create a subdirectory...
func (dsk *DSKWrapper) PRODOSCreateDirectory(path string, name string) error {
vdh, err := dsk.PRODOSGetVDH(2)
if err != nil {
return err
}
fd, err := dsk.PRODOSGetNamedEntry(path, name)
if err == nil {
return errors.New("Item exists")
}
fd, err = dsk.PRODOSGetFirstFreeEntry(path, name, true)
if err != nil {
return err
}
// got file descriptor
if fd.GetStorageType() != 0 {
if fd.Type() == FileType_PD_Directory {
return errors.New("Directory already exists")
}
return errors.New("Type mismatch")
}
// Get the current directories directory header
dvdh, blocks, _, err := dsk.PRODOSFindDirBlocks(2, path)
if err != nil {
return err
}
// find a freeBlock for the directory
freeBlocks, err := dsk.PRODOSGetFreeBlocks(1, vdh.GetTotalBlocks())
if err != nil {
return err
}
// got a file descriptor
fd.SetStorageType(StorageType_SubDir_File)
fd.SetAccessMode(AccessType_Default | AccessType_Changed)
fd.SetCreateTime(time.Now())
fd.SetModTime(time.Now())
fd.SetName(name)
fd.SetType(FileType_PD_Directory)
fd.SetIndexBlock(freeBlocks[0]) // <------- block for the directory header
fd.SetTotalBlocks(1)
fd.SetSize(512)
fd.SetHeaderPointer(blocks[0])
fd.SetMinVersion(0x00)
fd.SetVersion(0x23)
err = dsk.PRODOSInitDirectoryBlock(freeBlocks[0], blocks[0], dvdh.GetEntriesPerBlock(), dvdh.GetEntryLength(), fd.entryNum, name)
if err != nil {
return err
}
err = dsk.PRODOSMarkBlocks(freeBlocks, false)
if err != nil {
return err
}
dvdh.SetFileCount(vdh.GetFileCount() + 1)
dvdh.Publish(dsk)
if err != nil {
return err
}
return fd.Publish(dsk)
}
func (dsk *DSKWrapper) PRODOSInitDirectoryBlock(targetBlock int, parentBlock int, entriesPerBlock int, entrySize int, entryNum int, name string) error {
block := make([]byte, 512)
dh := &VDH{
Data: block[4 : 4+PRODOS_ENTRY_SIZE],
blockid: targetBlock,
blockoffset: 4,
}
// Must be set (beneath Apple ProDOS)
dh.Data[0x10] = 0x75
dh.SetStorageType(StorageType_SubDir_Header)
dh.SetName(name)
dh.SetCreateTime(time.Now())
dh.SetAccess(AccessType_Default)
dh.SetEntriesPerBlock(entriesPerBlock)
dh.SetEntryLength(entrySize)
dh.SetFileCount(0)
dh.SetDirParentPointer(parentBlock)
dh.SetDirParentEntry(entryNum)
dh.SetDirParentEntryLength(entrySize)
dh.SetMinVersion(0x00)
dh.SetVersion(0x23)
return dsk.PRODOSWrite(targetBlock, block)
}
func (dsk *DSKWrapper) PRODOSDeleteDirectory(path string, name string) error {
fd, err := dsk.PRODOSGetNamedEntry(path, name)
if err != nil {
return err
}
// got file descriptor, is it empty
if fd.GetStorageType() == 0x00 {
return errors.New("Path not found")
}
if fd.Type() != FileType_PD_Directory {
return errors.New("Not a directory")
}
// Get the current directories directory header
_, files, err := dsk.PRODOSGetCatalog(fd.IndexBlock(), "*")
if err != nil {
return err
}
// Remove any files in subdirectory
for _, subfile := range files {
if subfile.GetStorageType() == StorageType_SubDir_File {
// FOLDER, RECURSE
err = dsk.PRODOSDeleteDirectory(path+"/"+name, subfile.NameUnadorned())
if err != nil {
return err
}
} else if subfile.GetStorageType() == StorageType_Tree {
// TREE FILE -- UNSUPPORTED FOR NOW
return errors.New("Tree file handling not supported currently")
} else {
err = dsk.PRODOSDeleteFile(path+"/"+name, subfile.NameUnadorned())
if err != nil {
return err
}
}
}
// Check again and make sure it is empty
_, files, err = dsk.PRODOSGetCatalog(fd.IndexBlock(), "*")
if err != nil {
return err
}
if len(files) > 0 {
return errors.New("Could not delete all subfiles")
}
// Get blocks that make up the directory
_, dirBlocks, _, err := dsk.PRODOSFindDirBlocks(2, path+"/"+name)
if err != nil {
return err
}
// Free all the things!
err = dsk.PRODOSMarkBlocks(dirBlocks, true)
if err != nil {
return err
}
// free file entry
fd.SetStorageType(StorageType_Inactive)
err = fd.Publish(dsk)
if err != nil {
return err
}
// reduce count by 1 at top level
tvdh, _, _, err := dsk.PRODOSFindDirBlocks(2, path)
if err != nil {
return err
}
tvdh.SetFileCount(tvdh.GetFileCount() - 1)
return tvdh.Publish(dsk)
}
func (dsk *DSKWrapper) PRODOSSetLocked(path, 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.PRODOSGetNamedEntry(path, name)
if err != nil {
return err
}
fd.SetLocked(lock)
return fd.Publish(dsk)
}
// PRODOSGetNamedEntry for a given path, will find the file descriptor with name
func (dsk *DSKWrapper) PRODOSGetNamedEntry(path string, name string) (*ProDOSFileDescriptor, error) {
//fmt.Printf(">>> Path = %s\n", path)
vdh, blockList, blockData, err := dsk.PRODOSFindDirBlocks(2, path)
if err != nil {
return nil, err
}
entries := 0
// if we are here, we found the right directory...
for idx, data := range blockData {
count := 0
if idx == 0 {
count = 1
entries = 1
}
for count < vdh.GetEntriesPerBlock() {
offset := 4 + count*PRODOS_ENTRY_SIZE
chunk := data[offset : offset+PRODOS_ENTRY_SIZE]
fd := &ProDOSFileDescriptor{}
fd.SetData(chunk, blockList[idx], offset)
fd.entryNum = entries
//fmt.Printf(">>> Comparing %s to %s\n", strings.ToLower(fd.NameUnadorned()), strings.ToLower(name))
if fd.GetStorageType() != 0x00 && strings.ToLower(fd.NameUnadorned()) == strings.ToLower(name) {
return fd, nil
}
count += 1
entries++
}
}
return nil, errors.New("Not found")
}
func (dsk *DSKWrapper) PRODOSRenameFile(path, name, newname string) error {
fd, err := dsk.PRODOSGetNamedEntry(path, name)
if err != nil {
return err
}
_, err = dsk.PRODOSGetNamedEntry(path, newname)
if err == nil {
return errors.New("New name already exists")
}
// can rename here
fd.SetName(newname)
return fd.Publish(dsk)
}