From 286964d3ee2d41d48a0f65f219a3bcc0c542711f Mon Sep 17 00:00:00 2001 From: Terence Boldt Date: Sun, 22 Jan 2023 10:01:57 -0500 Subject: [PATCH] Add putallrecursive --- main.go | 24 +++++-- prodos/bitmap.go | 1 - prodos/directory.go | 160 ++++++++++++++++++++++++++++++++++++-------- prodos/file.go | 31 ++++++--- prodos/file_test.go | 2 +- prodos/host.go | 62 ++++++++++++++--- prodos/text.go | 17 ++++- 7 files changed, 242 insertions(+), 55 deletions(-) diff --git a/main.go b/main.go index 18d7c3c..2cd5cfe 100644 --- a/main.go +++ b/main.go @@ -32,7 +32,7 @@ func main() { var auxType int flag.StringVar(&fileName, "d", "", "A ProDOS format drive image") flag.StringVar(&pathName, "p", "", "Path name in ProDOS drive image (default is root of volume)") - flag.StringVar(&command, "c", "ls", "Command to execute: ls, get, put, rm, mkdir, readblock, writeblock, create, putall") + flag.StringVar(&command, "c", "ls", "Command to execute: ls, get, put, rm, mkdir, readblock, writeblock, create, putall, putallrecursive") flag.StringVar(&outFileName, "o", "", "Name of file to write") flag.StringVar(&inFileName, "i", "", "Name of file to read") flag.IntVar(&volumeSize, "s", 65535, "Number of blocks to create the volume with (default 65535, 64 to 65535, 0x0040 to 0xFFFF hex input accepted)") @@ -62,13 +62,17 @@ func main() { case "create": create(fileName, volumeName, volumeSize) case "putall": - putall(fileName, inFileName) + putall(fileName, inFileName, pathName, false) + case "putallrecursive": + putall(fileName, inFileName, pathName, true) case "rm": rm(fileName, pathName) case "mkdir": mkdir(fileName, pathName) case "dumpfile": dumpFile(fileName, pathName) + case "dumpdirectory": + dumpDirectory(fileName, pathName) default: fmt.Printf("Invalid command: %s\n\n", command) flag.PrintDefaults() @@ -88,6 +92,18 @@ func dumpFile(fileName string, pathName string) { prodos.DumpFileEntry(fileEntry) } +func dumpDirectory(fileName string, pathName string) { + checkPathName(pathName) + file, err := os.OpenFile(fileName, os.O_RDWR, 0755) + if err != nil { + fmt.Printf("Failed to open drive image %s:\n %s", fileName, err) + os.Exit(1) + } + defer file.Close() + _, directoryheader, _, err := prodos.ReadDirectory(file, pathName) + prodos.DumpDirectoryHeader(directoryheader) +} + func mkdir(fileName string, pathName string) { checkPathName(pathName) file, err := os.OpenFile(fileName, os.O_RDWR, 0755) @@ -114,7 +130,7 @@ func rm(fileName string, pathName string) { prodos.DeleteFile(file, pathName) } -func putall(fileName string, inFileName string) { +func putall(fileName string, inFileName string, pathName string, recursive bool) { if len(inFileName) == 0 { inFileName = "." } @@ -124,7 +140,7 @@ func putall(fileName string, inFileName string) { os.Exit(1) } defer file.Close() - err = prodos.AddFilesFromHostDirectory(file, inFileName) + err = prodos.AddFilesFromHostDirectory(file, inFileName, pathName, recursive) if err != nil { fmt.Printf("failed to add host files: %s\n", err) os.Exit(1) diff --git a/prodos/bitmap.go b/prodos/bitmap.go index 3e6a401..6794360 100644 --- a/prodos/bitmap.go +++ b/prodos/bitmap.go @@ -131,7 +131,6 @@ func createVolumeBitmap(numberOfBlocks int) []byte { markBlockInVolumeBitmap(volumeBitmap, i) } } - //DumpBlock(volumeBitmap) return volumeBitmap } diff --git a/prodos/directory.go b/prodos/directory.go index f6cad1d..7c6487b 100644 --- a/prodos/directory.go +++ b/prodos/directory.go @@ -33,6 +33,7 @@ type VolumeHeader struct { type DirectoryHeader struct { PreviousBlock int NextBlock int + IsSubDirectory bool Name string CreationTime time.Time Version int @@ -64,19 +65,20 @@ const ( // FileEntry from ProDOS type FileEntry struct { - 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 + 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 } @@ -95,6 +97,11 @@ func ReadDirectory(reader io.ReaderAt, path string) (VolumeHeader, DirectoryHead path = fmt.Sprintf("/%s", volumeHeader.VolumeName) } + // add volume name if not full path + if !strings.HasPrefix(path, "/") { + path = fmt.Sprintf("/%s/%s", volumeHeader.VolumeName, path) + } + path = strings.ToUpper(path) paths := strings.Split(path, "/") @@ -106,12 +113,23 @@ func ReadDirectory(reader io.ReaderAt, path string) (VolumeHeader, DirectoryHead return volumeHeader, directoryHeader, fileEntries, nil } +// CreateDirectory creates a directory information of a specified path +// on a ProDOS image func CreateDirectory(readerWriter ReaderWriterAt, path string) error { + if len(path) == 0 { + return errors.New("cannot create directory with path") + } + + // add volume name if not full path + path, err := makeFullPath(path, readerWriter) + if err != nil { + return err + } + parentPath, newDirectory := GetDirectoryAndFileNameFromPath(path) existingFileEntry, _ := GetFileEntry(readerWriter, path) if existingFileEntry.StorageType != StorageDeleted { - //DeleteFile(readerWriter, path) return errors.New("directory already exists") } @@ -140,6 +158,8 @@ func CreateDirectory(readerWriter ReaderWriterAt, path string) error { fileEntry.KeyPointer = blockList[0] fileEntry.Access = 0b11100011 fileEntry.StorageType = StorageDirectory + fileEntry.Version = 0x24 + fileEntry.MinVersion = 0x00 writeFileEntry(readerWriter, fileEntry) @@ -152,6 +172,7 @@ func CreateDirectory(readerWriter ReaderWriterAt, path string) error { directoryEntry := DirectoryHeader{ PreviousBlock: 0, NextBlock: 0, + IsSubDirectory: true, Name: newDirectory, CreationTime: time.Now(), Version: 0x24, @@ -162,7 +183,7 @@ func CreateDirectory(readerWriter ReaderWriterAt, path string) error { ActiveFileCount: 0, StartingBlock: blockList[0], ParentBlock: fileEntry.DirectoryBlock, - ParentEntry: fileEntry.DirectoryOffset, + ParentEntry: (fileEntry.DirectoryOffset - 0x04) / 0x27, ParentEntryLength: 0x27, } @@ -175,14 +196,26 @@ func CreateDirectory(readerWriter ReaderWriterAt, path string) error { return nil } -func getFreeFileEntryInDirectory(reader io.ReaderAt, directory string) (FileEntry, error) { - _, directoryHeader, _, err := ReadDirectory(reader, directory) +func makeFullPath(path string, reader io.ReaderAt) (string, error) { + if !strings.HasPrefix(path, "/") { + buffer, err := ReadBlock(reader, 0x0002) + if err != nil { + return "", err + } + + volumeHeader := parseVolumeHeader(buffer) + path = fmt.Sprintf("/%s/%s", volumeHeader.VolumeName, path) + } + return path, nil +} + +func getFreeFileEntryInDirectory(readerWriter ReaderWriterAt, directory string) (FileEntry, error) { + _, directoryHeader, _, err := ReadDirectory(readerWriter, directory) if err != nil { return FileEntry{}, err } - //DumpDirectoryHeader(directoryHeader) blockNumber := directoryHeader.StartingBlock - buffer, err := ReadBlock(reader, blockNumber) + buffer, err := ReadBlock(readerWriter, blockNumber) if err != nil { return FileEntry{}, err } @@ -191,21 +224,28 @@ func getFreeFileEntryInDirectory(reader io.ReaderAt, directory string) (FileEntr 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 { - return FileEntry{}, errors.New("no free file entries found") + nextBlockNumber := int(buffer[2]) + int(buffer[3])*256 + // if we ran out of blocks in the directory, expand directory or fail + if nextBlockNumber == 0 { + if !directoryHeader.IsSubDirectory { + return FileEntry{}, errors.New("no free file entries found") + } + nextBlockNumber, err = expandDirectory(readerWriter, nextBlockNumber, buffer, blockNumber, directoryHeader) + if err != nil { + return FileEntry{}, err + } } + blockNumber = nextBlockNumber // else read the next block in the directory - buffer, err = ReadBlock(reader, blockNumber) + buffer, err = ReadBlock(readerWriter, blockNumber) if err != nil { return FileEntry{}, nil } + entryOffset = 4 entryNumber = 1 } - fileEntry := parseFileEntry(buffer[entryOffset:entryOffset+40], blockNumber, entryOffset) + fileEntry := parseFileEntry(buffer[entryOffset:entryOffset+0x28], blockNumber, entryOffset) if fileEntry.StorageType == StorageDeleted { fileEntry.DirectoryBlock = blockNumber @@ -219,6 +259,51 @@ func getFreeFileEntryInDirectory(reader io.ReaderAt, directory string) (FileEntr } } +func expandDirectory(readerWriter ReaderWriterAt, nextBlockNumber int, buffer []byte, blockNumber int, directoryHeader DirectoryHeader) (int, error) { + volumeBitMap, err := ReadVolumeBitmap(readerWriter) + if err != nil { + errString := fmt.Sprintf("failed to get volume bitmap to expand directory: %s", err) + return 0, errors.New(errString) + } + blockList := findFreeBlocks(volumeBitMap, 1) + if len(blockList) != 1 { + return 0, errors.New("failed to get free block to expand directory") + } + + nextBlockNumber = blockList[0] + buffer[0x02] = byte(nextBlockNumber & 0x00FF) + buffer[0x03] = byte(nextBlockNumber >> 8) + WriteBlock(readerWriter, blockNumber, buffer) + if err != nil { + errString := fmt.Sprintf("failed to write block to expand directory: %s", err) + return 0, errors.New(errString) + } + + buffer = make([]byte, 0x200) + buffer[0x00] = byte(blockNumber & 0x00FF) + buffer[0x01] = byte(blockNumber >> 8) + err = WriteBlock(readerWriter, nextBlockNumber, buffer) + if err != nil { + errString := fmt.Sprintf("failed to write new block to expand directory: %s", err) + return 0, errors.New(errString) + } + + updateVolumeBitmap(readerWriter, blockList) + + buffer, err = ReadBlock(readerWriter, directoryHeader.ParentBlock) + if err != nil { + errString := fmt.Sprintf("failed to read parent block to expand directory: %s", err) + return 0, errors.New(errString) + } + directoryEntryOffset := directoryHeader.ParentEntry*directoryHeader.EntryLength + 0x04 + directoryFileEntry := parseFileEntry(buffer[directoryEntryOffset:directoryEntryOffset+0x28], directoryHeader.ParentBlock, directoryHeader.ParentEntry*directoryHeader.EntryLength+0x04) + directoryFileEntry.BlocksUsed++ + directoryFileEntry.EndOfFile += 0x200 + writeFileEntry(readerWriter, directoryFileEntry) + + return nextBlockNumber, nil +} + func getFileEntriesInDirectory(reader io.ReaderAt, blockNumber int, currentPath int, paths []string) (DirectoryHeader, []FileEntry, error) { buffer, err := ReadBlock(reader, blockNumber) if err != nil { @@ -333,13 +418,14 @@ func writeFileEntry(writer io.WriterAt, fileEntry FileEntry) { buffer[0x1E] = byte(fileEntry.Access) buffer[0x1F] = byte(fileEntry.AuxType & 0x00FF) buffer[0x20] = byte(fileEntry.AuxType >> 8) - modifiedTime := DateTimeToProDOS(fileEntry.CreationTime) + modifiedTime := DateTimeToProDOS(fileEntry.ModifiedTime) for i := 0; i < 4; i++ { buffer[0x21+i] = modifiedTime[i] } buffer[0x25] = byte(fileEntry.HeaderPointer & 0x00FF) buffer[0x26] = byte(fileEntry.HeaderPointer >> 8) + //fmt.Printf("Writing file entry at block: %04X offset: %04X\n", fileEntry.DirectoryBlock, fileEntry.DirectoryOffset) _, err := writer.WriteAt(buffer, int64(fileEntry.DirectoryBlock*512+fileEntry.DirectoryOffset)) if err != nil { @@ -381,7 +467,8 @@ func parseVolumeHeader(buffer []byte) VolumeHeader { func parseDirectoryHeader(buffer []byte, blockNumber int) DirectoryHeader { previousBlock := int(buffer[0x00]) + int(buffer[0x01])*256 nextBlock := int(buffer[0x02]) + int(buffer[0x03])*256 - filenameLength := buffer[0x04] & 15 + isSubDirectory := (buffer[0x04] & 0xF0) == 0xE0 + filenameLength := buffer[0x04] & 0x0F name := string(buffer[0x05 : filenameLength+0x05]) creationTime := DateTimeFromProDOS(buffer[0x1C:0x20]) version := int(buffer[0x20]) @@ -398,6 +485,7 @@ func parseDirectoryHeader(buffer []byte, blockNumber int) DirectoryHeader { PreviousBlock: previousBlock, NextBlock: nextBlock, StartingBlock: blockNumber, + IsSubDirectory: isSubDirectory, Name: name, CreationTime: creationTime, Version: version, @@ -415,6 +503,7 @@ func parseDirectoryHeader(buffer []byte, blockNumber int) DirectoryHeader { } func writeDirectoryHeader(readerWriter ReaderWriterAt, directoryHeader DirectoryHeader) error { + // Reading back the block preserves values including reserved fields buffer, err := ReadBlock(readerWriter, directoryHeader.StartingBlock) if err != nil { return err @@ -423,6 +512,11 @@ func writeDirectoryHeader(readerWriter ReaderWriterAt, directoryHeader Directory buffer[0x01] = byte(directoryHeader.PreviousBlock >> 8) buffer[0x02] = byte(directoryHeader.NextBlock & 0x00FF) buffer[0x03] = byte(directoryHeader.NextBlock >> 8) + if directoryHeader.IsSubDirectory { + buffer[0x04] = 0xE0 + } else { + buffer[0x04] = 0xF0 + } buffer[0x04] = buffer[0x04] | byte(len(directoryHeader.Name)) for i := 0; i < len(directoryHeader.Name); i++ { buffer[0x05+i] = directoryHeader.Name[i] @@ -431,6 +525,16 @@ func writeDirectoryHeader(readerWriter ReaderWriterAt, directoryHeader Directory for i := 0; i < 4; i++ { buffer[0x1C+i] = creationTime[i] } + // Without these reserved bytes, reading the directory causes I/O ERROR + buffer[0x14] = 0x75 + buffer[0x15] = byte(directoryHeader.Version) + buffer[0x16] = byte(directoryHeader.MinVersion) + buffer[0x17] = 0xC3 + buffer[0x18] = 0x0D + buffer[0x19] = 0x27 + buffer[0x1A] = 0x00 + buffer[0x1B] = 0x00 + buffer[0x20] = byte(directoryHeader.Version) buffer[0x21] = byte(directoryHeader.MinVersion) buffer[0x22] = byte(directoryHeader.Access) diff --git a/prodos/file.go b/prodos/file.go index 974b2b2..0b84580 100644 --- a/prodos/file.go +++ b/prodos/file.go @@ -46,6 +46,10 @@ func LoadFile(reader io.ReaderAt, path string) ([]byte, error) { func WriteFile(readerWriter ReaderWriterAt, path string, fileType int, auxType int, createdTime time.Time, modifiedTime time.Time, buffer []byte) error { directory, fileName := GetDirectoryAndFileNameFromPath(path) + if len(fileName) > 15 { + return errors.New("filename too long") + } + existingFileEntry, _ := GetFileEntry(readerWriter, path) if existingFileEntry.StorageType != StorageDeleted { DeleteFile(readerWriter, path) @@ -91,6 +95,8 @@ func WriteFile(readerWriter ReaderWriterAt, path string, fileType int, auxType i fileEntry.EndOfFile = len(buffer) fileEntry.FileType = fileType fileEntry.KeyPointer = blockList[0] + fileEntry.Version = 0x24 + fileEntry.MinVersion = 0x00 fileEntry.Access = 0b11100011 if len(blockList) == 1 { fileEntry.StorageType = StorageSeedling @@ -135,6 +141,7 @@ func DeleteFile(readerWriter ReaderWriterAt, path string) error { if err != nil { return err } + volumeBitmap, err := ReadVolumeBitmap(readerWriter) if err != nil { return err @@ -307,26 +314,27 @@ func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]int if err != nil { return nil, err } - blockOffset := 0 - if !dataOnly { - blocks[0] = fileEntry.KeyPointer - blockOffset = 1 - } + blockOffset := 1 + blocks[0] = fileEntry.KeyPointer for i := 0; i < fileEntry.BlocksUsed-1; i++ { blocks[i+blockOffset] = int(index[i]) + int(index[i+256])*256 } + if dataOnly { + return blocks[1:], nil + } return blocks, nil case StorageTree: + // this is actually too large dataBlocks := make([]int, fileEntry.BlocksUsed) - numberOfIndexBlocks := fileEntry.BlocksUsed/256 + 1 - if fileEntry.BlocksUsed%256 != 0 { - numberOfIndexBlocks++ - } + // this is also actually too large + numberOfIndexBlocks := fileEntry.BlocksUsed/256 + 2 indexBlocks := make([]int, numberOfIndexBlocks) masterIndex, err := ReadBlock(reader, fileEntry.KeyPointer) if err != nil { return nil, err } + numberOfDataBlocks := 0 + indexBlocks[0] = fileEntry.KeyPointer indexBlockCount := 1 @@ -345,6 +353,7 @@ func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]int if (int(index[j]) + int(index[j+256])*256) == 0 { break } + numberOfDataBlocks++ dataBlocks[i*256+j] = int(index[j]) + int(index[j+256])*256 } } @@ -353,7 +362,7 @@ func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]int return dataBlocks, nil } - blocks = append(indexBlocks, dataBlocks...) + blocks = append(indexBlocks[0:numberOfIndexBlocks], dataBlocks[0:numberOfDataBlocks]...) return blocks, nil } @@ -392,7 +401,7 @@ func createBlockList(reader io.ReaderAt, fileSize int) ([]int, error) { blockList := findFreeBlocks(volumeBitmap, numberOfBlocks) - return blockList, nil + return blockList[0:numberOfBlocks], nil } // GetFileEntry returns a file entry for the given path diff --git a/prodos/file_test.go b/prodos/file_test.go index af222ad..0d6ad96 100644 --- a/prodos/file_test.go +++ b/prodos/file_test.go @@ -9,7 +9,7 @@ import ( "testing" ) -func TestCreatBlocklist(t *testing.T) { +func TestCreateBlocklist(t *testing.T) { var tests = []struct { fileSize int wantBlocks int diff --git a/prodos/host.go b/prodos/host.go index 9024774..843411d 100644 --- a/prodos/host.go +++ b/prodos/host.go @@ -8,6 +8,7 @@ package prodos import ( "encoding/binary" + "errors" "fmt" "os" "path/filepath" @@ -19,7 +20,15 @@ import ( // from the specified host directory func AddFilesFromHostDirectory( readerWriter ReaderWriterAt, - directory string) error { + directory string, + path string, + recursive bool) error { + + path, err := makeFullPath(path, readerWriter) + + if !strings.HasSuffix(path, "/") { + path = path + "/" + } files, err := os.ReadDir(directory) if err != nil { @@ -33,37 +42,67 @@ func AddFilesFromHostDirectory( } if file.Name()[0] != '.' && !file.IsDir() && info.Size() > 0 && info.Size() <= 0x1000000 { - err = WriteFileFromFile(readerWriter, "", 0, 0, info.ModTime(), filepath.Join(directory, file.Name())) + err = WriteFileFromFile(readerWriter, path, 0, 0, info.ModTime(), filepath.Join(directory, file.Name())) if err != nil { return err } } + + if file.Name()[0] != '.' && recursive && file.IsDir() { + newPath := file.Name() + if len(newPath) > 15 { + newPath = newPath[0:15] + } + newFullPath := strings.ToUpper(path + newPath) + + newHostDirectory := filepath.Join(directory, file.Name()) + CreateDirectory(readerWriter, newFullPath) + AddFilesFromHostDirectory(readerWriter, newHostDirectory, newFullPath+"/", recursive) + } } return nil } // WriteFileFromFile writes a file to a ProDOS volume from a host file -func WriteFileFromFile(readerWriter ReaderWriterAt, pathName string, fileType int, auxType int, modifiedTime time.Time, inFileName string) error { - fmt.Printf("WriteFileFromFile: %s\n", inFileName) +func WriteFileFromFile( + readerWriter ReaderWriterAt, + pathName string, + fileType int, + auxType int, + modifiedTime time.Time, + inFileName string) error { + inFile, err := os.ReadFile(inFileName) if err != nil { - fmt.Println("failed to read file") - return err + errString := fmt.Sprintf("write from file failed: %s", err) + return errors.New(errString) } if auxType == 0 && fileType == 0 { auxType, fileType, inFile, err = convertFileByType(inFileName, inFile) if err != nil { - fmt.Println("failed to convert file") - return err + errString := fmt.Sprintf("failed to convert file: %s", err) + return errors.New(errString) } } + trimExtensions := false if len(pathName) == 0 { _, pathName = filepath.Split(inFileName) pathName = strings.ToUpper(pathName) + trimExtensions = true + } + + if strings.HasSuffix(pathName, "/") { + trimExtensions = true + _, fileName := filepath.Split(inFileName) + pathName = strings.ToUpper(pathName + fileName) + } + + if trimExtensions { ext := filepath.Ext(pathName) + if len(ext) > 0 { switch ext { case ".SYS", ".TXT", ".BAS", ".BIN": @@ -72,6 +111,13 @@ func WriteFileFromFile(readerWriter ReaderWriterAt, pathName string, fileType in } } + paths := strings.SplitAfter(pathName, "/") + if len(paths[len(paths)-1]) > 15 { + paths[len(paths)-1] = paths[len(paths)-1][0:15] + pathName = strings.Join(paths, "") + } + + fmt.Printf("Source: %s Destination: %s\n", inFileName, pathName) return WriteFile(readerWriter, pathName, fileType, auxType, time.Now(), modifiedTime, inFile) } diff --git a/prodos/text.go b/prodos/text.go index c5b30e5..15b1a1e 100644 --- a/prodos/text.go +++ b/prodos/text.go @@ -100,6 +100,8 @@ func DumpFileEntry(fileEntry FileEntry) { fmt.Printf("Storage type: %02X\n", fileEntry.StorageType) fmt.Printf("Header pointer: %04X\n", fileEntry.HeaderPointer) fmt.Printf("Access: %04X\n", fileEntry.Access) + fmt.Printf("Directory block: %04X\n", fileEntry.DirectoryBlock) + fmt.Printf("Directory offset: %04X\n", fileEntry.DirectoryOffset) fmt.Printf("\n") } @@ -119,11 +121,22 @@ func DumpVolumeHeader(volumeHeader VolumeHeader) { // DumpDirectoryHeader dumps the directory header as text func DumpDirectoryHeader(directoryHeader DirectoryHeader) { - fmt.Printf("Name: %s\n", directoryHeader.Name) - fmt.Printf("File count: %d\n", directoryHeader.ActiveFileCount) fmt.Printf("Starting block: %04X\n", directoryHeader.StartingBlock) fmt.Printf("Previous block: %04X\n", directoryHeader.PreviousBlock) fmt.Printf("Next block: %04X\n", directoryHeader.NextBlock) + fmt.Printf("Is subdirectory: %t\n", directoryHeader.IsSubDirectory) + fmt.Printf("Name: %s\n", directoryHeader.Name) + fmt.Printf("Creation time: %s\n", TimeToString(directoryHeader.CreationTime)) + fmt.Printf("Version: %02X\n", directoryHeader.Version) + fmt.Printf("MinVersion: %02X\n", directoryHeader.MinVersion) + fmt.Printf("Access: %02X\n", directoryHeader.Access) + fmt.Printf("Entry length: %02X\n", directoryHeader.EntryLength) + fmt.Printf("Entries per block: %02X\n", directoryHeader.EntriesPerBlock) + fmt.Printf("File count: %d\n", directoryHeader.ActiveFileCount) + fmt.Printf("Active file count: %04X\n", directoryHeader.ActiveFileCount) + fmt.Printf("Parent block: %04X\n", directoryHeader.ParentBlock) + fmt.Printf("Parent entry: %02X\n", directoryHeader.ParentEntry) + fmt.Printf("Parent entry length: %02X\n", directoryHeader.ParentEntryLength) } // DumpBlock dumps the block as hexadecimal and text