ProDOS-Utilities/prodos/directory.go

297 lines
8.8 KiB
Go
Raw Normal View History

2021-06-06 03:22:30 +00:00
package prodos
import (
2021-06-30 04:04:59 +00:00
"errors"
2021-06-06 03:22:30 +00:00
"fmt"
"os"
"strings"
"time"
)
type VolumeHeader struct {
VolumeName string
CreationTime time.Time
ActiveFileCount int
BitmapStartBlock int
TotalBlocks int
NextBlock int
EntryLength int
EntriesPerBlock int
2021-06-12 02:43:35 +00:00
MinVersion int
Version int
2021-06-06 03:22:30 +00:00
}
type DirectoryHeader struct {
Name string
ActiveFileCount int
2021-06-26 01:15:20 +00:00
StartingBlock int
PreviousBlock int
2021-06-06 03:22:30 +00:00
NextBlock int
}
const (
StorageDeleted = 0
StorageSeedling = 1
StorageSapling = 2
StorageTree = 3
StoragePascal = 4
StorageDirectory = 13
)
type FileEntry struct {
2021-06-12 02:43:35 +00:00
StorageType int
FileName string
FileType int
CreationTime time.Time
KeyPointer int
Version int
MinVersion int
BlocksUsed int
EndOfFile int
Access int
AuxType int
ModifiedTime time.Time
HeaderPointer int
DirectoryBlock int
DirectoryOffset int
2021-06-06 03:22:30 +00:00
}
2021-06-26 01:15:20 +00:00
func ReadDirectory(file *os.File, path string) (VolumeHeader, DirectoryHeader, []FileEntry) {
2021-06-06 03:22:30 +00:00
buffer := ReadBlock(file, 2)
2021-06-06 12:00:20 +00:00
volumeHeader := parseVolumeHeader(buffer)
2021-06-06 03:22:30 +00:00
if len(path) == 0 {
path = fmt.Sprintf("/%s", volumeHeader.VolumeName)
}
path = strings.ToUpper(path)
paths := strings.Split(path, "/")
2021-06-26 01:15:20 +00:00
directoryHeader, fileEntries := getFileEntriesInDirectory(file, 2, 1, paths)
2021-06-06 03:22:30 +00:00
2021-06-26 01:15:20 +00:00
return volumeHeader, directoryHeader, fileEntries
2021-06-06 03:22:30 +00:00
}
2021-07-02 10:54:25 +00:00
func getFreeFileEntryInDirectory(file *os.File, directory string) (FileEntry, error) {
2021-06-26 01:15:20 +00:00
_, directoryHeader, _ := ReadDirectory(file, directory)
2021-06-30 04:04:59 +00:00
//DumpDirectoryHeader(directoryHeader)
2021-06-26 01:15:20 +00:00
blockNumber := directoryHeader.StartingBlock
buffer := ReadBlock(file, blockNumber)
entryOffset := 43 // start at offset after header
entryNumber := 2 // header is essentially the first entry so start at 2
for {
if entryNumber > 13 {
blockNumber = int(buffer[2]) + int(buffer[3])*256
// if we ran out of blocks in the directory, return empty
// TODO: expand the directory to add more entries
if blockNumber == 0 {
2021-06-30 04:04:59 +00:00
return FileEntry{}, errors.New("No free file entries found")
2021-06-26 01:15:20 +00:00
}
// else read the next block in the directory
buffer = ReadBlock(file, blockNumber)
entryOffset = 4
entryNumber = 1
}
fileEntry := parseFileEntry(buffer[entryOffset:entryOffset+40], blockNumber, entryOffset)
if fileEntry.StorageType == StorageDeleted {
fileEntry.DirectoryBlock = blockNumber
fileEntry.DirectoryOffset = entryOffset
fileEntry.HeaderPointer = directoryHeader.StartingBlock
2021-06-30 04:04:59 +00:00
return fileEntry, nil
2021-06-26 01:15:20 +00:00
}
entryNumber++
entryOffset += 39
}
}
func getFileEntriesInDirectory(file *os.File, blockNumber int, currentPath int, paths []string) (DirectoryHeader, []FileEntry) {
2021-06-06 03:22:30 +00:00
buffer := ReadBlock(file, blockNumber)
2021-06-26 01:15:20 +00:00
directoryHeader := parseDirectoryHeader(buffer, blockNumber)
2021-06-06 03:22:30 +00:00
fileEntries := make([]FileEntry, directoryHeader.ActiveFileCount)
entryOffset := 43 // start at offset after header
activeEntries := 0
entryNumber := 2 // header is essentially the first entry so start at 2
nextBlock := directoryHeader.NextBlock
matchedDirectory := (currentPath == len(paths)-1) && (paths[currentPath] == directoryHeader.Name)
if !matchedDirectory && (currentPath == len(paths)-1) {
// path not matched by last path part
2021-06-26 01:15:20 +00:00
return DirectoryHeader{}, nil
2021-06-06 03:22:30 +00:00
}
for {
if entryNumber > 13 {
entryOffset = 4
entryNumber = 1
if blockNumber == 0 {
2021-06-26 01:15:20 +00:00
return DirectoryHeader{}, nil
2021-06-06 03:22:30 +00:00
}
buffer = ReadBlock(file, nextBlock)
nextBlock = int(buffer[2]) + int(buffer[3])*256
}
2021-06-12 02:43:35 +00:00
fileEntry := parseFileEntry(buffer[entryOffset:entryOffset+40], blockNumber, entryOffset)
2021-06-06 03:22:30 +00:00
if fileEntry.StorageType != StorageDeleted {
2021-06-26 01:15:20 +00:00
if matchedDirectory && activeEntries == directoryHeader.ActiveFileCount {
return directoryHeader, fileEntries[0:activeEntries]
}
2021-06-06 03:22:30 +00:00
if matchedDirectory {
fileEntries[activeEntries] = fileEntry
} else if !matchedDirectory && fileEntry.FileType == 15 && paths[currentPath+1] == fileEntry.FileName {
2021-06-12 02:43:35 +00:00
return getFileEntriesInDirectory(file, fileEntry.KeyPointer, currentPath+1, paths)
2021-06-06 03:22:30 +00:00
}
activeEntries++
}
entryNumber++
entryOffset += 39
}
}
2021-06-12 02:43:35 +00:00
func parseFileEntry(buffer []byte, blockNumber int, entryOffset int) FileEntry {
2021-06-06 03:22:30 +00:00
storageType := int(buffer[0] >> 4)
fileNameLength := int(buffer[0] & 15)
fileName := string(buffer[1 : fileNameLength+1])
fileType := int(buffer[16])
startingBlock := int(buffer[17]) + int(buffer[18])*256
blocksUsed := int(buffer[19]) + int(buffer[20])*256
endOfFile := int(buffer[21]) + int(buffer[22])*256 + int(buffer[23])*65536
creationTime := DateTimeFromProDOS(buffer[24:28])
2021-06-12 02:43:35 +00:00
version := int(buffer[28])
minVersion := int(buffer[29])
2021-06-06 03:22:30 +00:00
access := int(buffer[30])
auxType := int(buffer[31]) + int(buffer[32])*256
modifiedTime := DateTimeFromProDOS((buffer[33:37]))
2021-06-12 02:43:35 +00:00
headerPointer := int(buffer[0x25]) + int(buffer[0x26])*256
2021-06-06 03:22:30 +00:00
fileEntry := FileEntry{
2021-06-12 02:43:35 +00:00
StorageType: storageType,
FileName: fileName,
FileType: fileType,
CreationTime: creationTime,
Version: version,
MinVersion: minVersion,
KeyPointer: startingBlock,
BlocksUsed: blocksUsed,
EndOfFile: endOfFile,
Access: access,
AuxType: auxType,
ModifiedTime: modifiedTime,
HeaderPointer: headerPointer,
DirectoryBlock: blockNumber,
DirectoryOffset: entryOffset,
2021-06-06 03:22:30 +00:00
}
return fileEntry
}
2021-06-12 02:43:35 +00:00
func writeFileEntry(file *os.File, fileEntry FileEntry) {
buffer := make([]byte, 39)
buffer[0] = byte(fileEntry.StorageType)<<4 + byte(len(fileEntry.FileName))
for i := 0; i < len(fileEntry.FileName); i++ {
buffer[i+1] = fileEntry.FileName[i]
}
buffer[0x10] = byte(fileEntry.FileType)
buffer[0x11] = byte(fileEntry.KeyPointer & 0xFF)
buffer[0x12] = byte(fileEntry.KeyPointer >> 8)
buffer[0x13] = byte(fileEntry.BlocksUsed & 0xFF)
buffer[0x14] = byte(fileEntry.BlocksUsed >> 8)
buffer[0x15] = byte(fileEntry.EndOfFile & 0x0000FF)
buffer[0x16] = byte(fileEntry.EndOfFile & 0x00FF00 >> 8)
buffer[0x17] = byte(fileEntry.EndOfFile & 0xFF0000 >> 16)
creationTime := DateTimeToProDOS(fileEntry.CreationTime)
for i := 0; i < 4; i++ {
buffer[0x18+i] = creationTime[i]
}
buffer[0x1C] = byte(fileEntry.Version)
buffer[0x1D] = byte(fileEntry.MinVersion)
buffer[0x1E] = byte(fileEntry.Access)
buffer[0x1F] = byte(fileEntry.AuxType & 0x00FF)
buffer[0x20] = byte(fileEntry.AuxType >> 8)
modifiedTime := DateTimeToProDOS(fileEntry.CreationTime)
for i := 0; i < 4; i++ {
buffer[0x21+i] = modifiedTime[i]
}
buffer[0x25] = byte(fileEntry.HeaderPointer & 0x00FF)
buffer[0x26] = byte(fileEntry.HeaderPointer >> 8)
_, err := file.WriteAt(buffer, int64(fileEntry.DirectoryBlock*512+fileEntry.DirectoryOffset))
if err != nil {
}
}
2021-06-06 12:00:20 +00:00
func parseVolumeHeader(buffer []byte) VolumeHeader {
2021-06-06 03:22:30 +00:00
nextBlock := int(buffer[2]) + int(buffer[3])*256
filenameLength := buffer[4] & 15
volumeName := string(buffer[5 : filenameLength+5])
creationTime := DateTimeFromProDOS(buffer[28:32])
version := int(buffer[32])
minVersion := int(buffer[33])
entryLength := int(buffer[35])
entriesPerBlock := int(buffer[36])
fileCount := int(buffer[37]) + int(buffer[38])*256
bitmapBlock := int(buffer[39]) + int(buffer[40])*256
totalBlocks := int(buffer[41]) + int(buffer[42])*256
if version > 0 || minVersion > 0 {
panic("Unsupported ProDOS version")
}
volumeHeader := VolumeHeader{
VolumeName: volumeName,
CreationTime: creationTime,
ActiveFileCount: fileCount,
BitmapStartBlock: bitmapBlock,
TotalBlocks: totalBlocks,
NextBlock: nextBlock,
EntriesPerBlock: entriesPerBlock,
EntryLength: entryLength,
2021-06-12 02:43:35 +00:00
MinVersion: minVersion,
Version: version,
2021-06-06 03:22:30 +00:00
}
return volumeHeader
}
2021-06-26 01:15:20 +00:00
func parseDirectoryHeader(buffer []byte, blockNumber int) DirectoryHeader {
previousBlock := int(buffer[0x00]) + int(buffer[0x01])*256
2021-06-12 02:43:35 +00:00
nextBlock := int(buffer[0x02]) + int(buffer[0x03])*256
filenameLength := buffer[0x04] & 15
name := string(buffer[0x05 : filenameLength+0x05])
fileCount := int(buffer[0x25]) + int(buffer[0x26])*256
2021-06-06 03:22:30 +00:00
directoryEntry := DirectoryHeader{
2021-06-26 01:15:20 +00:00
PreviousBlock: previousBlock,
2021-06-06 03:22:30 +00:00
NextBlock: nextBlock,
2021-06-26 01:15:20 +00:00
StartingBlock: blockNumber,
2021-06-06 03:22:30 +00:00
Name: name,
ActiveFileCount: fileCount,
}
return directoryEntry
}
2021-06-12 02:43:35 +00:00
2021-06-30 12:22:08 +00:00
func writeDirectoryHeader(file *os.File, directoryHeader DirectoryHeader) {
buffer := ReadBlock(file, directoryHeader.StartingBlock)
buffer[0x00] = byte(directoryHeader.PreviousBlock & 0x00FF)
buffer[0x01] = byte(directoryHeader.PreviousBlock >> 8)
2021-06-12 02:43:35 +00:00
buffer[0x02] = byte(directoryHeader.NextBlock & 0x00FF)
buffer[0x03] = byte(directoryHeader.NextBlock >> 8)
buffer[0x04] = buffer[0x04] | byte(len(directoryHeader.Name))
for i := 0; i < len(directoryHeader.Name); i++ {
buffer[0x05+i] = directoryHeader.Name[i]
}
buffer[0x25] = byte(directoryHeader.ActiveFileCount & 0x00FF)
buffer[0x26] = byte(directoryHeader.ActiveFileCount >> 8)
2021-06-30 12:22:08 +00:00
WriteBlock(file, directoryHeader.StartingBlock, buffer)
2021-06-12 02:43:35 +00:00
}