diskm8/disk/diskimagepas.go
2024-05-25 08:13:50 -07:00

317 lines
6.5 KiB
Go

package disk
import (
"errors"
"regexp"
"strings"
)
const PASCAL_BLOCK_SIZE = 512
const PASCAL_VOLUME_BLOCK = 2
const PASCAL_MAX_VOLUME_NAME = 7
const PASCAL_DIRECTORY_ENTRY_LENGTH = 26
const PASCAL_OVERSIZE_DIR = 32
func (dsk *DSKWrapper) IsPascal() (bool, string) {
dsk.Format = GetDiskFormat(DF_PRODOS)
data, err := dsk.PRODOSGetBlock(PASCAL_VOLUME_BLOCK)
if err != nil {
return false, ""
}
if !(data[0x00] == 0 && data[0x01] == 0) ||
!(data[0x04] == 0 && data[0x05] == 0) ||
!(data[0x06] > 0 && data[0x06] <= PASCAL_MAX_VOLUME_NAME) {
return false, ""
}
l := int(data[0x06])
name := data[0x07 : 0x07+l]
str := ""
for _, ch := range name {
if ch == 0x00 {
break
}
if ch < 0x20 || ch >= 0x7f {
return false, ""
}
if strings.Contains("$=?,[#:", string(ch)) {
return false, ""
}
str += string(ch)
}
return true, str
}
type PascalVolumeHeader struct {
data [PASCAL_DIRECTORY_ENTRY_LENGTH]byte
}
func (pvh *PascalVolumeHeader) SetData(data []byte) {
for i, v := range data {
if i < len(pvh.data) {
pvh.data[i] = v
}
}
}
func (pvh *PascalVolumeHeader) GetStartBlock() int {
return int(pvh.data[0x00]) + 256*int(pvh.data[0x01])
}
func (pvh *PascalVolumeHeader) GetNextBlock() int {
return int(pvh.data[0x02]) + 256*int(pvh.data[0x03])
}
type PascalFileType int
const (
FileType_PAS_NONE PascalFileType = 0
FileType_PAS_BADD PascalFileType = 1
FileType_PAS_CODE PascalFileType = 2
FileType_PAS_TEXT PascalFileType = 3
FileType_PAS_INFO PascalFileType = 4
FileType_PAS_DATA PascalFileType = 5
FileType_PAS_GRAF PascalFileType = 6
FileType_PAS_FOTO PascalFileType = 7
FileType_PAS_SECD PascalFileType = 8
)
var PascalTypeMap = map[PascalFileType][2]string{
0x00: [2]string{"UNK", "ASCII Text"},
0x01: [2]string{"BAD", "Bad Block"},
0x02: [2]string{"PCD", "Pascal Code"},
0x03: [2]string{"PTX", "Pascal Text"},
0x04: [2]string{"PIF", "Pascal Info"},
0x05: [2]string{"PDA", "Pascal Data"},
0x06: [2]string{"GRF", "Pascal Graphics"},
0x07: [2]string{"FOT", "HiRes Graphics"},
0x08: [2]string{"SEC", "Secure Directory"},
}
func (ft PascalFileType) String() string {
info, ok := PascalTypeMap[ft]
if ok {
return info[1]
}
return "Unknown"
}
func (ft PascalFileType) Ext() string {
info, ok := PascalTypeMap[ft]
if ok {
return info[0]
}
return "UNK"
}
func PascalFileTypeFromExt(ext string) PascalFileType {
for ft, info := range PascalTypeMap {
if strings.ToUpper(ext) == info[0] {
return ft
}
}
return 0x00
}
func (pvh *PascalVolumeHeader) GetType() int {
return int(int(pvh.data[0x04]) + 256*int(pvh.data[0x05]))
}
func (pvh *PascalVolumeHeader) GetNameLength() int {
return int(pvh.data[0x06]) & 0x07
}
func (pvh *PascalVolumeHeader) GetName() string {
l := pvh.GetNameLength()
s := strings.Trim(string(pvh.data[0x07:0x07+l]), " ")
s += "." + PascalFileType(pvh.GetType()).Ext()
return s
}
func (pvh *PascalVolumeHeader) GetTotalBlocks() int {
return int(pvh.data[0x0e]) + 256*int(pvh.data[0x0f])
}
func (pvh *PascalVolumeHeader) GetNumFiles() int {
return int(pvh.data[0x10]) + 256*int(pvh.data[0x11])
}
type PascalFileEntry struct {
data [PASCAL_DIRECTORY_ENTRY_LENGTH]byte
}
func (pfe *PascalFileEntry) SetData(data []byte) {
for i, v := range data {
if i < len(pfe.data) {
pfe.data[i] = v
}
}
}
func (pvh *PascalFileEntry) IsLocked() bool {
return true
}
func (pvh *PascalFileEntry) GetStartBlock() int {
return int(pvh.data[0x00]) + 256*int(pvh.data[0x01])
}
func (pvh *PascalFileEntry) GetNextBlock() int {
return int(pvh.data[0x02]) + 256*int(pvh.data[0x03])
}
func (pvh *PascalFileEntry) GetType() PascalFileType {
return PascalFileType(int(pvh.data[0x04]) + 256*int(pvh.data[0x05]))
}
func (pvh *PascalFileEntry) GetNameLength() int {
return int(pvh.data[0x06]) & 0x0f
}
func (pvh *PascalFileEntry) GetName() string {
l := pvh.GetNameLength()
return strings.Trim(string(pvh.data[0x07:0x07+l]), "")
}
func (pvh *PascalFileEntry) GetBytesRemaining() int {
return int(pvh.data[0x16]) + 256*int(pvh.data[0x17])
}
func (pvh *PascalFileEntry) GetFileSize() int {
return pvh.GetBytesRemaining() + (pvh.GetNextBlock()-pvh.GetStartBlock()-1)*PASCAL_BLOCK_SIZE
}
func (dsk *DSKWrapper) PascalGetCatalog(pattern string) ([]*PascalFileEntry, error) {
pattern = strings.Replace(pattern, ".", "[.]", -1)
pattern = strings.Replace(pattern, "*", ".*", -1)
pattern = strings.Replace(pattern, "?", ".", -1)
rx := regexp.MustCompile("(?i)" + pattern)
files := make([]*PascalFileEntry, 0)
//
d, err := dsk.PRODOSGetBlock(PASCAL_VOLUME_BLOCK)
if err != nil {
return nil, err
}
pvh := &PascalVolumeHeader{}
pvh.SetData(d)
numBlocks := pvh.GetNextBlock() - PASCAL_VOLUME_BLOCK
if numBlocks < 0 || numBlocks > PASCAL_OVERSIZE_DIR {
return files, errors.New("Directory appears corrupt")
}
// disk catalog is okay
catdata := make([]byte, 0)
for block := PASCAL_VOLUME_BLOCK; block < PASCAL_VOLUME_BLOCK+numBlocks; block++ {
data, err := dsk.PRODOSGetBlock(block)
if err != nil {
return files, err
}
catdata = append(catdata, data...)
}
dirPtr := PASCAL_DIRECTORY_ENTRY_LENGTH
for i := 0; i < pvh.GetNumFiles(); i++ {
b := catdata[dirPtr : dirPtr+PASCAL_DIRECTORY_ENTRY_LENGTH]
fd := &PascalFileEntry{}
fd.SetData(b)
// add file
if rx.MatchString(fd.GetName()) {
files = append(files, fd)
}
// move
dirPtr += PASCAL_DIRECTORY_ENTRY_LENGTH
}
return files, nil
}
func (dsk *DSKWrapper) PascalUsedBitmap() ([]bool, error) {
activeBlocks := dsk.Format.BPD()
used := make([]bool, activeBlocks)
files, err := dsk.PascalGetCatalog("*")
if err != nil {
return used, err
}
for _, file := range files {
length := file.GetNextBlock() - file.GetStartBlock()
start := file.GetStartBlock()
if start+length > activeBlocks {
continue // file is bad
}
for block := start; block < start+length; block++ {
used[block] = true
}
}
return used, nil
}
func (dsk *DSKWrapper) PascalReadFile(file *PascalFileEntry) ([]byte, error) {
activeSectors := dsk.Format.BPD()
length := file.GetNextBlock() - file.GetStartBlock()
start := file.GetStartBlock()
// 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.GetFileSize() {
chunk, err := dsk.PRODOSGetBlock(block)
if err != nil {
return data, err
}
needed := file.GetFileSize() - len(data)
if needed >= PASCAL_BLOCK_SIZE {
data = append(data, chunk...)
} else {
data = append(data, chunk[:needed]...)
}
block++
}
return data, nil
}