ProDOS-Utilities/prodos/directory.go

257 lines
7.3 KiB
Go
Raw Normal View History

2021-06-06 03:22:30 +00:00
package prodos
import (
"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
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-06 12:00:20 +00:00
func ReadDirectory(file *os.File, path string) (VolumeHeader, []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
//dumpVolumeHeader(volumeHeader)
if len(path) == 0 {
path = fmt.Sprintf("/%s", volumeHeader.VolumeName)
}
path = strings.ToUpper(path)
paths := strings.Split(path, "/")
fileEntries := getFileEntriesInDirectory(file, 2, 1, paths)
return volumeHeader, fileEntries
}
func getFileEntriesInDirectory(file *os.File, blockNumber int, currentPath int, paths []string) []FileEntry {
//fmt.Printf("Parsing '%s'...\n", paths[currentPath])
buffer := ReadBlock(file, blockNumber)
2021-06-06 12:00:20 +00:00
directoryHeader := parseDirectoryHeader(buffer)
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
return nil
}
for {
if entryNumber > 13 {
entryOffset = 4
entryNumber = 1
if blockNumber == 0 {
return nil
}
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
//DumpFileEntry(fileEntry)
if fileEntry.StorageType != StorageDeleted {
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++
if matchedDirectory && activeEntries == directoryHeader.ActiveFileCount {
return fileEntries[0: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-06 12:00:20 +00:00
func parseDirectoryHeader(buffer []byte) DirectoryHeader {
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{
NextBlock: nextBlock,
Name: name,
ActiveFileCount: fileCount,
}
return directoryEntry
}
2021-06-12 02:43:35 +00:00
func writeDirectoryHeader(file *os.File, directoryHeader DirectoryHeader, blockNumber int) {
buffer := ReadBlock(file, blockNumber)
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)
file.WriteAt(buffer, int64(blockNumber*512))
}