Merge pull request #3 from tjboldt/refactor

Change to reader/writer instead of file
This commit is contained in:
Terence Boldt 2022-01-23 17:33:04 -05:00 committed by GitHub
commit 819ee47503
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 272 additions and 160 deletions

View File

@ -151,4 +151,4 @@ ProDOS-Utilities -d ../Apple2-IO-RPi/RaspberryPi/Apple2-IO-RPi.hdv -c get -o Upd
1060 NEXT PG
1900 PRINT
2000 PRINT "Firmware Update Complete"
```
```

14
main.go
View File

@ -1,3 +1,11 @@
// Copyright Terence J. Boldt (c)2021-2022
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
// This file provides a command line utility to read, write and delete
// files and directories on a ProDOS drive image as well as format
// new volumes
package main
import (
@ -101,7 +109,7 @@ func main() {
fmt.Printf("Failed to open input file %s: %s", inFileName, err)
os.Exit(1)
}
err = prodos.WriteFile(file, pathName, fileType, auxType, inFile)
err = prodos.WriteFile(file, file, pathName, fileType, auxType, inFile)
if err != nil {
fmt.Printf("Failed to write file %s: %s", pathName, err)
}
@ -136,7 +144,7 @@ func main() {
return
}
defer file.Close()
prodos.CreateVolume(file, volumeName, volumeSize)
prodos.CreateVolume(file, file, volumeName, volumeSize)
case "rm":
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
if err != nil {
@ -144,7 +152,7 @@ func main() {
os.Exit(1)
}
defer file.Close()
prodos.DeleteFile(file, pathName)
prodos.DeleteFile(file, file, pathName)
default:
fmt.Printf("Invalid command: %s\n\n", command)
flag.PrintDefaults()

View File

@ -1,3 +1,9 @@
// Copyright Terence J. Boldt (c)2021-2022
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
// This file provides conversion between BASIC and text
package prodos
import (
@ -115,6 +121,7 @@ var tokens = map[byte]string{
0xEA: "MID$",
}
// ConvertBasicToText converts AppleSoft BASIC to text
func ConvertBasicToText(basic []byte) string {
var builder strings.Builder

View File

@ -1,11 +1,19 @@
// Copyright Terence J. Boldt (c)2021-2022
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
// This file provides access to volum bitmap on
// a ProDOS drive image
package prodos
import (
"os"
"io"
)
func ReadVolumeBitmap(file *os.File) []byte {
headerBlock := ReadBlock(file, 2)
// ReadVolumeBitmap reads the volume bitmap from a ProDOS image
func ReadVolumeBitmap(reader io.ReaderAt) []byte {
headerBlock := ReadBlock(reader, 2)
volumeHeader := parseVolumeHeader(headerBlock)
@ -23,7 +31,7 @@ func ReadVolumeBitmap(file *os.File) []byte {
}
for i := 0; i < totalBitmapBlocks; i++ {
bitmapBlock := ReadBlock(file, i+volumeHeader.BitmapStartBlock)
bitmapBlock := ReadBlock(reader, i+volumeHeader.BitmapStartBlock)
for j := 0; j < 512 && i*512+j < totalBitmapBytes; j++ {
bitmap[i*512+j] = bitmapBlock[j]
@ -33,13 +41,25 @@ func ReadVolumeBitmap(file *os.File) []byte {
return bitmap
}
func writeVolumeBitmap(file *os.File, bitmap []byte) {
headerBlock := ReadBlock(file, 2)
// GetFreeBlockCount gets the number of free blocks on a ProDOS image
func GetFreeBlockCount(volumeBitmap []byte, totalBlocks int) int {
freeBlockCount := 0
for i := 0; i < totalBlocks; i++ {
if checkFreeBlockInVolumeBitmap(volumeBitmap, i) {
freeBlockCount++
}
}
return freeBlockCount
}
func writeVolumeBitmap(writer io.WriterAt, reader io.ReaderAt, bitmap []byte) {
headerBlock := ReadBlock(reader, 2)
volumeHeader := parseVolumeHeader(headerBlock)
for i := 0; i < len(bitmap)/512; i++ {
WriteBlock(file, volumeHeader.BitmapStartBlock+i, bitmap[i*512:i*512+512])
WriteBlock(writer, volumeHeader.BitmapStartBlock+i, bitmap[i*512:i*512+512])
}
}
@ -101,17 +121,6 @@ func findFreeBlocks(volumeBitmap []byte, numberOfBlocks int) []int {
return nil
}
func GetFreeBlockCount(volumeBitmap []byte, totalBlocks int) int {
freeBlockCount := 0
for i := 0; i < totalBlocks; i++ {
if checkFreeBlockInVolumeBitmap(volumeBitmap, i) {
freeBlockCount++
}
}
return freeBlockCount
}
func markBlockInVolumeBitmap(volumeBitmap []byte, blockNumber int) {
bitToChange := blockNumber % 8
byteToChange := blockNumber / 8

View File

@ -1,22 +1,26 @@
// Copyright Terence J. Boldt (c)2021-2022
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
// This file provides access to read and write
// blocks on a ProDOS drive image
package prodos
import (
"os"
"io"
)
func ReadBlock(file *os.File, block int) []byte {
// ReadBlock reads a block from a ProDOS volume into a byte array
func ReadBlock(reader io.ReaderAt, block int) []byte {
buffer := make([]byte, 512)
file.ReadAt(buffer, int64(block)*512)
reader.ReadAt(buffer, int64(block)*512)
return buffer
}
func WriteBlock(file *os.File, block int, buffer []byte) {
WriteBlockNoSync(file, block, buffer)
file.Sync()
}
func WriteBlockNoSync(file *os.File, block int, buffer []byte) {
file.WriteAt(buffer, int64(block)*512)
// WriteBlock writes a block to a ProDOS volume from a byte array
func WriteBlock(writer io.WriterAt, block int, buffer []byte) {
writer.WriteAt(buffer, int64(block)*512)
}

View File

@ -1,13 +1,21 @@
// Copyright Terence J. Boldt (c)2021-2022
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
// This file provides access to read, write, delete
// fand parse directories on a ProDOS drive image
package prodos
import (
"errors"
"fmt"
"os"
"io"
"strings"
"time"
)
// VolumeHeader from ProDOS
type VolumeHeader struct {
VolumeName string
CreationTime time.Time
@ -21,6 +29,7 @@ type VolumeHeader struct {
Version int
}
// DirectoryHeader from ProDOS
type DirectoryHeader struct {
Name string
ActiveFileCount int
@ -38,6 +47,7 @@ const (
StorageDirectory = 13
)
// FileEntry from ProDOS
type FileEntry struct {
StorageType int
FileName string
@ -56,8 +66,10 @@ type FileEntry struct {
DirectoryOffset int
}
func ReadDirectory(file *os.File, path string) (VolumeHeader, DirectoryHeader, []FileEntry) {
buffer := ReadBlock(file, 2)
// ReadDirectory reads the directory information from a specified path
// on a ProDOS image
func ReadDirectory(reader io.ReaderAt, path string) (VolumeHeader, DirectoryHeader, []FileEntry) {
buffer := ReadBlock(reader, 2)
volumeHeader := parseVolumeHeader(buffer)
@ -68,16 +80,16 @@ func ReadDirectory(file *os.File, path string) (VolumeHeader, DirectoryHeader, [
path = strings.ToUpper(path)
paths := strings.Split(path, "/")
directoryHeader, fileEntries := getFileEntriesInDirectory(file, 2, 1, paths)
directoryHeader, fileEntries := getFileEntriesInDirectory(reader, 2, 1, paths)
return volumeHeader, directoryHeader, fileEntries
}
func getFreeFileEntryInDirectory(file *os.File, directory string) (FileEntry, error) {
_, directoryHeader, _ := ReadDirectory(file, directory)
func getFreeFileEntryInDirectory(reader io.ReaderAt, directory string) (FileEntry, error) {
_, directoryHeader, _ := ReadDirectory(reader, directory)
//DumpDirectoryHeader(directoryHeader)
blockNumber := directoryHeader.StartingBlock
buffer := ReadBlock(file, blockNumber)
buffer := ReadBlock(reader, blockNumber)
entryOffset := 43 // start at offset after header
entryNumber := 2 // header is essentially the first entry so start at 2
@ -91,7 +103,7 @@ func getFreeFileEntryInDirectory(file *os.File, directory string) (FileEntry, er
return FileEntry{}, errors.New("No free file entries found")
}
// else read the next block in the directory
buffer = ReadBlock(file, blockNumber)
buffer = ReadBlock(reader, blockNumber)
entryOffset = 4
entryNumber = 1
}
@ -109,8 +121,8 @@ func getFreeFileEntryInDirectory(file *os.File, directory string) (FileEntry, er
}
}
func getFileEntriesInDirectory(file *os.File, blockNumber int, currentPath int, paths []string) (DirectoryHeader, []FileEntry) {
buffer := ReadBlock(file, blockNumber)
func getFileEntriesInDirectory(reader io.ReaderAt, blockNumber int, currentPath int, paths []string) (DirectoryHeader, []FileEntry) {
buffer := ReadBlock(reader, blockNumber)
directoryHeader := parseDirectoryHeader(buffer, blockNumber)
@ -135,7 +147,7 @@ func getFileEntriesInDirectory(file *os.File, blockNumber int, currentPath int,
if blockNumber == 0 {
return DirectoryHeader{}, nil
}
buffer = ReadBlock(file, nextBlock)
buffer = ReadBlock(reader, nextBlock)
nextBlock = int(buffer[2]) + int(buffer[3])*256
}
fileEntry := parseFileEntry(buffer[entryOffset:entryOffset+40], blockNumber, entryOffset)
@ -147,7 +159,7 @@ func getFileEntriesInDirectory(file *os.File, blockNumber int, currentPath int,
if matchedDirectory {
fileEntries[activeEntries] = fileEntry
} else if !matchedDirectory && fileEntry.FileType == 15 && paths[currentPath+1] == fileEntry.FileName {
return getFileEntriesInDirectory(file, fileEntry.KeyPointer, currentPath+1, paths)
return getFileEntriesInDirectory(reader, fileEntry.KeyPointer, currentPath+1, paths)
}
activeEntries++
}
@ -194,7 +206,7 @@ func parseFileEntry(buffer []byte, blockNumber int, entryOffset int) FileEntry {
return fileEntry
}
func writeFileEntry(file *os.File, fileEntry FileEntry) {
func writeFileEntry(writer io.WriterAt, fileEntry FileEntry) {
buffer := make([]byte, 39)
buffer[0] = byte(fileEntry.StorageType)<<4 + byte(len(fileEntry.FileName))
for i := 0; i < len(fileEntry.FileName); i++ {
@ -224,7 +236,7 @@ func writeFileEntry(file *os.File, fileEntry FileEntry) {
buffer[0x25] = byte(fileEntry.HeaderPointer & 0x00FF)
buffer[0x26] = byte(fileEntry.HeaderPointer >> 8)
_, err := file.WriteAt(buffer, int64(fileEntry.DirectoryBlock*512+fileEntry.DirectoryOffset))
_, err := writer.WriteAt(buffer, int64(fileEntry.DirectoryBlock*512+fileEntry.DirectoryOffset))
if err != nil {
}
@ -280,8 +292,8 @@ func parseDirectoryHeader(buffer []byte, blockNumber int) DirectoryHeader {
return directoryEntry
}
func writeDirectoryHeader(file *os.File, directoryHeader DirectoryHeader) {
buffer := ReadBlock(file, directoryHeader.StartingBlock)
func writeDirectoryHeader(writer io.WriterAt, reader io.ReaderAt, directoryHeader DirectoryHeader) {
buffer := ReadBlock(reader, directoryHeader.StartingBlock)
buffer[0x00] = byte(directoryHeader.PreviousBlock & 0x00FF)
buffer[0x01] = byte(directoryHeader.PreviousBlock >> 8)
buffer[0x02] = byte(directoryHeader.NextBlock & 0x00FF)
@ -292,5 +304,5 @@ func writeDirectoryHeader(file *os.File, directoryHeader DirectoryHeader) {
}
buffer[0x25] = byte(directoryHeader.ActiveFileCount & 0x00FF)
buffer[0x26] = byte(directoryHeader.ActiveFileCount >> 8)
WriteBlock(file, directoryHeader.StartingBlock, buffer)
WriteBlock(writer, directoryHeader.StartingBlock, buffer)
}

11
prodos/doc.go Normal file
View File

@ -0,0 +1,11 @@
// Copyright Terence J. Boldt (c)2021-2022
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
/*
Package prodos provides access to read/write/delete/list files and
directories on a ProDOS order drive images.
*/
package prodos

View File

@ -1,19 +1,27 @@
// Copyright Terence J. Boldt (c)2021-2022
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
// This file provides access to read, write and delete
// files on a ProDOS drive image
package prodos
import (
"errors"
"os"
"io"
"strings"
"time"
)
func LoadFile(file *os.File, path string) ([]byte, error) {
fileEntry, err := getFileEntry(file, path)
// LoadFile loads in a file from a ProDOS volume into a byte array
func LoadFile(reader io.ReaderAt, path string) ([]byte, error) {
fileEntry, err := getFileEntry(reader, path)
if err != nil {
return nil, err
}
blockList, err := getDataBlocklist(file, fileEntry)
blockList, err := getDataBlocklist(reader, fileEntry)
if err != nil {
return nil, err
}
@ -21,7 +29,7 @@ func LoadFile(file *os.File, path string) ([]byte, error) {
buffer := make([]byte, fileEntry.EndOfFile)
for i := 0; i < len(blockList); i++ {
block := ReadBlock(file, blockList[i])
block := ReadBlock(reader, blockList[i])
for j := 0; j < 512 && i*512+j < fileEntry.EndOfFile; j++ {
buffer[i*512+j] = block[j]
}
@ -30,36 +38,37 @@ func LoadFile(file *os.File, path string) ([]byte, error) {
return buffer, nil
}
func WriteFile(file *os.File, path string, fileType int, auxType int, buffer []byte) error {
// WriteFile writes a file to a ProDOS volume from a byte array
func WriteFile(writer io.WriterAt, reader io.ReaderAt, path string, fileType int, auxType int, buffer []byte) error {
directory, fileName := GetDirectoryAndFileNameFromPath(path)
existingFileEntry, _ := getFileEntry(file, path)
existingFileEntry, _ := getFileEntry(reader, path)
if existingFileEntry.StorageType != StorageDeleted {
DeleteFile(file, path)
DeleteFile(writer, reader, path)
}
// get list of blocks to write file to
blockList := createBlockList(file, len(buffer))
blockList := createBlockList(reader, len(buffer))
// seedling file
if len(buffer) <= 0x200 {
WriteBlock(file, blockList[0], buffer)
WriteBlock(writer, blockList[0], buffer)
}
// sapling file needs index block
if len(buffer) > 0x200 && len(buffer) <= 0x20000 {
writeSaplingFile(file, buffer, blockList)
writeSaplingFile(writer, buffer, blockList)
}
// TODO: add tree file
if len(buffer) > 0x20000 {
return errors.New("Files > 128KB not supported yet.")
return errors.New("files > 128KB not supported yet")
}
updateVolumeBitmap(file, blockList)
updateVolumeBitmap(writer, reader, blockList)
// add file entry to directory
fileEntry, err := getFreeFileEntryInDirectory(file, directory)
fileEntry, err := getFreeFileEntryInDirectory(reader, directory)
if err != nil {
return err
}
@ -80,59 +89,20 @@ func WriteFile(file *os.File, path string, fileType int, auxType int, buffer []b
fileEntry.StorageType = StorageTree
}
writeFileEntry(file, fileEntry)
writeFileEntry(writer, fileEntry)
// increment file count
directoryHeaderBlock := ReadBlock(file, fileEntry.HeaderPointer)
directoryHeaderBlock := ReadBlock(reader, fileEntry.HeaderPointer)
directoryHeader := parseDirectoryHeader(directoryHeaderBlock, fileEntry.HeaderPointer)
directoryHeader.ActiveFileCount++
writeDirectoryHeader(file, directoryHeader)
writeDirectoryHeader(writer, reader, directoryHeader)
return nil
}
func updateVolumeBitmap(file *os.File, blockList []int) {
volumeBitmap := ReadVolumeBitmap(file)
for i := 0; i < len(blockList); i++ {
markBlockInVolumeBitmap(volumeBitmap, blockList[i])
}
writeVolumeBitmap(file, volumeBitmap)
}
func writeSaplingFile(file *os.File, buffer []byte, blockList []int) {
// write index block with pointers to data blocks
indexBuffer := make([]byte, 512)
for i := 0; i < 256; i++ {
if i < len(blockList)-1 {
indexBuffer[i] = byte(blockList[i+1] & 0x00FF)
indexBuffer[i+256] = byte(blockList[i+1] >> 8)
}
}
WriteBlock(file, blockList[0], indexBuffer)
// write all data blocks
blockBuffer := make([]byte, 512)
blockPointer := 0
blockIndexNumber := 1
for i := 0; i < len(buffer); i++ {
blockBuffer[blockPointer] = buffer[i]
if blockPointer == 511 {
WriteBlock(file, blockList[blockIndexNumber], blockBuffer)
blockPointer = 0
blockIndexNumber++
} else if i == len(buffer)-1 {
for j := blockPointer; j < 512; j++ {
blockBuffer[j] = 0
}
WriteBlock(file, blockList[blockIndexNumber], blockBuffer)
} else {
blockPointer++
}
}
}
func DeleteFile(file *os.File, path string) error {
fileEntry, err := getFileEntry(file, path)
// DeleteFile deletes a file from a ProDOS volume
func DeleteFile(writer io.WriterAt, reader io.ReaderAt, path string) error {
fileEntry, err := getFileEntry(reader, path)
if err != nil {
return errors.New("File not found")
}
@ -144,31 +114,32 @@ func DeleteFile(file *os.File, path string) error {
}
// free the blocks
blocks, err := getBlocklist(file, fileEntry)
blocks, err := getBlocklist(reader, fileEntry)
if err != nil {
return err
}
volumeBitmap := ReadVolumeBitmap(file)
volumeBitmap := ReadVolumeBitmap(reader)
for i := 0; i < len(blocks); i++ {
freeBlockInVolumeBitmap(volumeBitmap, blocks[i])
}
writeVolumeBitmap(file, volumeBitmap)
writeVolumeBitmap(writer, reader, volumeBitmap)
// decrement the directory entry count
directoryBlock := ReadBlock(file, fileEntry.HeaderPointer)
directoryBlock := ReadBlock(reader, fileEntry.HeaderPointer)
directoryHeader := parseDirectoryHeader(directoryBlock, fileEntry.HeaderPointer)
directoryHeader.ActiveFileCount--
writeDirectoryHeader(file, directoryHeader)
writeDirectoryHeader(writer, reader, directoryHeader)
// zero out directory entry
fileEntry.StorageType = 0
fileEntry.FileName = ""
writeFileEntry(file, fileEntry)
writeFileEntry(writer, fileEntry)
return nil
}
// GetDirectoryAndFileNameFromPath gets the directory and filename from a path
func GetDirectoryAndFileNameFromPath(path string) (string, string) {
path = strings.ToUpper(path)
paths := strings.Split(path, "/")
@ -186,8 +157,48 @@ func GetDirectoryAndFileNameFromPath(path string) (string, string) {
return directory, fileName
}
func updateVolumeBitmap(writer io.WriterAt, reader io.ReaderAt, blockList []int) {
volumeBitmap := ReadVolumeBitmap(reader)
for i := 0; i < len(blockList); i++ {
markBlockInVolumeBitmap(volumeBitmap, blockList[i])
}
writeVolumeBitmap(writer, reader, volumeBitmap)
}
func writeSaplingFile(writer io.WriterAt, buffer []byte, blockList []int) {
// write index block with pointers to data blocks
indexBuffer := make([]byte, 512)
for i := 0; i < 256; i++ {
if i < len(blockList)-1 {
indexBuffer[i] = byte(blockList[i+1] & 0x00FF)
indexBuffer[i+256] = byte(blockList[i+1] >> 8)
}
}
WriteBlock(writer, blockList[0], indexBuffer)
// write all data blocks
blockBuffer := make([]byte, 512)
blockPointer := 0
blockIndexNumber := 1
for i := 0; i < len(buffer); i++ {
blockBuffer[blockPointer] = buffer[i]
if blockPointer == 511 {
WriteBlock(writer, blockList[blockIndexNumber], blockBuffer)
blockPointer = 0
blockIndexNumber++
} else if i == len(buffer)-1 {
for j := blockPointer; j < 512; j++ {
blockBuffer[j] = 0
}
WriteBlock(writer, blockList[blockIndexNumber], blockBuffer)
} else {
blockPointer++
}
}
}
// Returns all blocks, including index blocks
func getBlocklist(file *os.File, fileEntry FileEntry) ([]int, error) {
func getBlocklist(reader io.ReaderAt, fileEntry FileEntry) ([]int, error) {
blocks := make([]int, fileEntry.BlocksUsed)
switch fileEntry.StorageType {
@ -195,17 +206,17 @@ func getBlocklist(file *os.File, fileEntry FileEntry) ([]int, error) {
blocks[0] = fileEntry.KeyPointer
return blocks, nil
case StorageSapling:
index := ReadBlock(file, fileEntry.KeyPointer)
index := ReadBlock(reader, fileEntry.KeyPointer)
blocks[0] = fileEntry.KeyPointer
for i := 0; i < fileEntry.BlocksUsed-1; i++ {
blocks[i+1] = int(index[i]) + int(index[i+256])*256
}
return blocks, nil
case StorageTree:
masterIndex := ReadBlock(file, fileEntry.KeyPointer)
masterIndex := ReadBlock(reader, fileEntry.KeyPointer)
blocks[0] = fileEntry.KeyPointer
for i := 0; i < 128; i++ {
index := ReadBlock(file, int(masterIndex[i])+int(masterIndex[i+256])*256)
index := ReadBlock(reader, int(masterIndex[i])+int(masterIndex[i+256])*256)
for j := 0; j < 256 && i*256+j < fileEntry.BlocksUsed; j++ {
if (int(index[j]) + int(index[j+256])*256) == 0 {
return blocks, nil
@ -215,10 +226,10 @@ func getBlocklist(file *os.File, fileEntry FileEntry) ([]int, error) {
}
}
return nil, errors.New("Unsupported file storage type")
return nil, errors.New("unsupported file storage type")
}
func getDataBlocklist(file *os.File, fileEntry FileEntry) ([]int, error) {
func getDataBlocklist(reader io.ReaderAt, fileEntry FileEntry) ([]int, error) {
switch fileEntry.StorageType {
case StorageSeedling:
blocks := make([]int, 1)
@ -226,7 +237,7 @@ func getDataBlocklist(file *os.File, fileEntry FileEntry) ([]int, error) {
return blocks, nil
case StorageSapling:
blocks := make([]int, fileEntry.BlocksUsed-1)
index := ReadBlock(file, fileEntry.KeyPointer)
index := ReadBlock(reader, fileEntry.KeyPointer)
for i := 0; i < fileEntry.BlocksUsed-1; i++ {
blocks[i] = int(index[i]) + int(index[i+256])*256
}
@ -236,7 +247,7 @@ func getDataBlocklist(file *os.File, fileEntry FileEntry) ([]int, error) {
return nil, errors.New("Unsupported file storage type")
}
func createBlockList(file *os.File, fileSize int) []int {
func createBlockList(reader io.ReaderAt, fileSize int) []int {
numberOfBlocks := fileSize / 512
if fileSize%512 > 0 {
numberOfBlocks++
@ -254,15 +265,15 @@ func createBlockList(file *os.File, fileSize int) []int {
numberOfBlocks++
}
}
volumeBitmap := ReadVolumeBitmap(file)
volumeBitmap := ReadVolumeBitmap(reader)
blockList := findFreeBlocks(volumeBitmap, numberOfBlocks)
return blockList
}
func getFileEntry(file *os.File, path string) (FileEntry, error) {
func getFileEntry(reader io.ReaderAt, path string) (FileEntry, error) {
directory, fileName := GetDirectoryAndFileNameFromPath(path)
_, _, fileEntries := ReadDirectory(file, directory)
_, _, fileEntries := ReadDirectory(reader, directory)
if fileEntries == nil || len(fileEntries) == 0 {
return FileEntry{}, errors.New("File entry not found")

View File

@ -1,13 +1,21 @@
// Copyright Terence J. Boldt (c)2021-2022
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
// This file provides access to format a ProDOS drive image
package prodos
import (
"fmt"
"os"
"io"
"strings"
"time"
)
func CreateVolume(file *os.File, volumeName string, numberOfBlocks int) {
// CreateVolume formats a new ProDOS volume including boot block,
// volume bitmap and empty directory
func CreateVolume(writer io.WriterAt, reader io.ReaderAt, volumeName string, numberOfBlocks int) {
if numberOfBlocks > 65535 || numberOfBlocks < 64 {
return
}
@ -20,7 +28,7 @@ func CreateVolume(file *os.File, volumeName string, numberOfBlocks int) {
blankBlock := make([]byte, 512)
for i := 0; i < numberOfBlocks; i++ {
WriteBlockNoSync(file, i, blankBlock)
WriteBlock(writer, i, blankBlock)
}
volumeHeader := [43]byte{}
@ -55,11 +63,10 @@ func CreateVolume(file *os.File, volumeName string, numberOfBlocks int) {
volumeHeader[0x29] = byte(numberOfBlocks & 0xFF)
volumeHeader[0x2A] = byte(numberOfBlocks >> 8)
file.WriteAt(volumeHeader[:], 1024)
file.Sync()
writer.WriteAt(volumeHeader[:], 1024)
// boot block 0
WriteBlock(file, 0, getBootBlock())
WriteBlock(writer, 0, getBootBlock())
// pointers to volume directory blocks
for i := 2; i < 6; i++ {
@ -76,12 +83,12 @@ func CreateVolume(file *os.File, volumeName string, numberOfBlocks int) {
pointers[2] = byte(i + 1)
}
pointers[3] = 0x00
file.WriteAt(pointers, int64(i*512))
writer.WriteAt(pointers, int64(i*512))
}
// volume bit map starting at block 6
volumeBitmap := createVolumeBitmap(numberOfBlocks)
writeVolumeBitmap(file, volumeBitmap)
writeVolumeBitmap(writer, reader, volumeBitmap)
}
func getBootBlock() []byte {

View File

@ -2,7 +2,6 @@ package prodos
import (
"fmt"
"os"
"testing"
)
@ -20,17 +19,9 @@ func TestCreateVolume(t *testing.T) {
for _, tt := range tests {
testname := fmt.Sprintf("%d", tt.blocks)
t.Run(testname, func(t *testing.T) {
fileName := os.TempDir() + "/test-volume.hdv"
defer os.Remove(fileName)
file, err := os.Create(fileName)
if err != nil {
t.Errorf("failed to create file: %s\n", err)
return
}
file := NewMemoryFile(0x2000000)
defer file.Close()
CreateVolume(file, tt.wantVolumeName, tt.blocks)
CreateVolume(file, file, tt.wantVolumeName, tt.blocks)
volumeHeader, _, fileEntries := ReadDirectory(file, "")
if volumeHeader.VolumeName != tt.wantVolumeName {

26
prodos/memfile.go Normal file
View File

@ -0,0 +1,26 @@
// Copyright Terence J. Boldt (c)2021-2022
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
// This file provides a file in memory
package prodos
type MemoryFile struct {
data []byte
size int
}
func NewMemoryFile(size int) *MemoryFile {
return &MemoryFile{make([]byte, size), size}
}
func (memoryFile *MemoryFile) WriteAt(data []byte, offset int64) (int, error) {
copy(memoryFile.data[int(offset):], data)
return len(data), nil
}
func (memoryFile *MemoryFile) ReadAt(data []byte, offset int64) (int, error) {
copy(data, memoryFile.data[int(offset):])
return len(data), nil
}

View File

@ -1,3 +1,10 @@
// Copyright Terence J. Boldt (c)2021-2022
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
// This file provides text dumps for directories
// and blocks
package prodos
import (
@ -6,6 +13,7 @@ import (
"time"
)
// TimeToString displays the date and time in ProDOS format
func TimeToString(printTime time.Time) string {
return fmt.Sprintf("%04d-%s-%02d %02d:%02d",
printTime.Year(),
@ -16,6 +24,7 @@ func TimeToString(printTime time.Time) string {
)
}
// FileTypeToString display the file type as a string
func FileTypeToString(fileType int) string {
switch fileType {
case 1:
@ -78,6 +87,7 @@ func FileTypeToString(fileType int) string {
*/
}
// DumpFileEntry dumps the file entry values as text
func DumpFileEntry(fileEntry FileEntry) {
fmt.Printf("FileName: %s\n", fileEntry.FileName)
fmt.Printf("Creation time: %d-%s-%d %02d:%02d\n", fileEntry.CreationTime.Year(), fileEntry.CreationTime.Month(), fileEntry.CreationTime.Day(), fileEntry.CreationTime.Hour(), fileEntry.CreationTime.Minute())
@ -92,6 +102,7 @@ func DumpFileEntry(fileEntry FileEntry) {
fmt.Printf("\n")
}
// DumpVolumeHeader dumps the volume header values as text
func DumpVolumeHeader(volumeHeader VolumeHeader) {
fmt.Printf("Next block: %d\n", volumeHeader.NextBlock)
fmt.Printf("Volume name: %s\n", volumeHeader.VolumeName)
@ -105,6 +116,7 @@ func DumpVolumeHeader(volumeHeader VolumeHeader) {
fmt.Printf("Total blocks: %d\n", volumeHeader.TotalBlocks)
}
// 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)
@ -113,6 +125,7 @@ func DumpDirectoryHeader(directoryHeader DirectoryHeader) {
fmt.Printf("Next block: %04X\n", directoryHeader.NextBlock)
}
// DumpBlock dumps the block as hexadecimal and text
func DumpBlock(buffer []byte) {
for i := 0; i < len(buffer); i += 16 {
fmt.Printf("%04X: ", i)
@ -131,6 +144,7 @@ func DumpBlock(buffer []byte) {
}
}
// DumpDirectory displays the directory similar to ProDOS catalog
func DumpDirectory(blocksFree int, totalBlocks int, path string, fileEntries []FileEntry) {
fmt.Printf("%s\n\n", path)
fmt.Printf("NAME TYPE BLOCKS MODIFIED CREATED ENDFILE SUBTYPE\n\n")

View File

@ -1,24 +1,29 @@
// Copyright Terence J. Boldt (c)2021-2022
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
// This file provides conversion to and from ProDOS time format
package prodos
import (
"time"
)
/* 49041 ($BF91) 49040 ($BF90)
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
DATE: | year | month | day |
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
TIME: | hour | | minute |
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
49043 ($BF93) 49042 ($BF92)
*/
// DateTimeToProDOS converts Time to ProDOS date time
// 49041 ($BF91) 49040 ($BF90)
//
// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
// DATE: | year | month | day |
// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
//
// 49043 ($BF93) 49042 ($BF92)
//
// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
// TIME: | hour | | minute |
// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
func DateTimeToProDOS(dateTime time.Time) []byte {
year := dateTime.Year() % 100
month := dateTime.Month()
@ -35,6 +40,7 @@ func DateTimeToProDOS(dateTime time.Time) []byte {
return buffer
}
// DateTimeToProDOS converts Time from ProDOS date time
func DateTimeFromProDOS(buffer []byte) time.Time {
if buffer[0] == 0 &&
buffer[1] == 0 &&

View File

@ -1,3 +1,9 @@
// Copyright Terence J. Boldt (c)2021-2022
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
// This file provides readme for command line utility
package main
import "fmt"
@ -8,7 +14,7 @@ func printReadme() {
fmt.Println(`
MIT License
Copyright (c) 2021 Terence Boldt
Copyright (c)2021-2022 Terence Boldt
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal