mirror of
https://github.com/tjboldt/ProDOS-Utilities.git
synced 2025-02-17 23:30:49 +00:00
Merge pull request #3 from tjboldt/refactor
Change to reader/writer instead of file
This commit is contained in:
commit
819ee47503
@ -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
14
main.go
@ -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()
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
11
prodos/doc.go
Normal 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
|
163
prodos/file.go
163
prodos/file.go
@ -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")
|
||||
|
@ -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 {
|
||||
|
@ -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
26
prodos/memfile.go
Normal 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
|
||||
}
|
@ -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")
|
||||
|
@ -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 &&
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user