mirror of
https://github.com/tjboldt/ProDOS-Utilities.git
synced 2025-02-19 20:30:44 +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
|
1060 NEXT PG
|
||||||
1900 PRINT
|
1900 PRINT
|
||||||
2000 PRINT "Firmware Update Complete"
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -101,7 +109,7 @@ func main() {
|
|||||||
fmt.Printf("Failed to open input file %s: %s", inFileName, err)
|
fmt.Printf("Failed to open input file %s: %s", inFileName, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
err = prodos.WriteFile(file, pathName, fileType, auxType, inFile)
|
err = prodos.WriteFile(file, file, pathName, fileType, auxType, inFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to write file %s: %s", pathName, err)
|
fmt.Printf("Failed to write file %s: %s", pathName, err)
|
||||||
}
|
}
|
||||||
@ -136,7 +144,7 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
prodos.CreateVolume(file, volumeName, volumeSize)
|
prodos.CreateVolume(file, file, volumeName, volumeSize)
|
||||||
case "rm":
|
case "rm":
|
||||||
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
|
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -144,7 +152,7 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
prodos.DeleteFile(file, pathName)
|
prodos.DeleteFile(file, file, pathName)
|
||||||
default:
|
default:
|
||||||
fmt.Printf("Invalid command: %s\n\n", command)
|
fmt.Printf("Invalid command: %s\n\n", command)
|
||||||
flag.PrintDefaults()
|
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
|
package prodos
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -115,6 +121,7 @@ var tokens = map[byte]string{
|
|||||||
0xEA: "MID$",
|
0xEA: "MID$",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConvertBasicToText converts AppleSoft BASIC to text
|
||||||
func ConvertBasicToText(basic []byte) string {
|
func ConvertBasicToText(basic []byte) string {
|
||||||
var builder strings.Builder
|
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
|
package prodos
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ReadVolumeBitmap(file *os.File) []byte {
|
// ReadVolumeBitmap reads the volume bitmap from a ProDOS image
|
||||||
headerBlock := ReadBlock(file, 2)
|
func ReadVolumeBitmap(reader io.ReaderAt) []byte {
|
||||||
|
headerBlock := ReadBlock(reader, 2)
|
||||||
|
|
||||||
volumeHeader := parseVolumeHeader(headerBlock)
|
volumeHeader := parseVolumeHeader(headerBlock)
|
||||||
|
|
||||||
@ -23,7 +31,7 @@ func ReadVolumeBitmap(file *os.File) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < totalBitmapBlocks; i++ {
|
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++ {
|
for j := 0; j < 512 && i*512+j < totalBitmapBytes; j++ {
|
||||||
bitmap[i*512+j] = bitmapBlock[j]
|
bitmap[i*512+j] = bitmapBlock[j]
|
||||||
@ -33,13 +41,25 @@ func ReadVolumeBitmap(file *os.File) []byte {
|
|||||||
return bitmap
|
return bitmap
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeVolumeBitmap(file *os.File, bitmap []byte) {
|
// GetFreeBlockCount gets the number of free blocks on a ProDOS image
|
||||||
headerBlock := ReadBlock(file, 2)
|
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)
|
volumeHeader := parseVolumeHeader(headerBlock)
|
||||||
|
|
||||||
for i := 0; i < len(bitmap)/512; i++ {
|
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
|
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) {
|
func markBlockInVolumeBitmap(volumeBitmap []byte, blockNumber int) {
|
||||||
bitToChange := blockNumber % 8
|
bitToChange := blockNumber % 8
|
||||||
byteToChange := 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
|
package prodos
|
||||||
|
|
||||||
import (
|
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)
|
buffer := make([]byte, 512)
|
||||||
|
|
||||||
file.ReadAt(buffer, int64(block)*512)
|
reader.ReadAt(buffer, int64(block)*512)
|
||||||
|
|
||||||
return buffer
|
return buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteBlock(file *os.File, block int, buffer []byte) {
|
// WriteBlock writes a block to a ProDOS volume from a byte array
|
||||||
WriteBlockNoSync(file, block, buffer)
|
func WriteBlock(writer io.WriterAt, block int, buffer []byte) {
|
||||||
file.Sync()
|
writer.WriteAt(buffer, int64(block)*512)
|
||||||
}
|
|
||||||
|
|
||||||
func WriteBlockNoSync(file *os.File, block int, buffer []byte) {
|
|
||||||
file.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
|
package prodos
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// VolumeHeader from ProDOS
|
||||||
type VolumeHeader struct {
|
type VolumeHeader struct {
|
||||||
VolumeName string
|
VolumeName string
|
||||||
CreationTime time.Time
|
CreationTime time.Time
|
||||||
@ -21,6 +29,7 @@ type VolumeHeader struct {
|
|||||||
Version int
|
Version int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DirectoryHeader from ProDOS
|
||||||
type DirectoryHeader struct {
|
type DirectoryHeader struct {
|
||||||
Name string
|
Name string
|
||||||
ActiveFileCount int
|
ActiveFileCount int
|
||||||
@ -38,6 +47,7 @@ const (
|
|||||||
StorageDirectory = 13
|
StorageDirectory = 13
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// FileEntry from ProDOS
|
||||||
type FileEntry struct {
|
type FileEntry struct {
|
||||||
StorageType int
|
StorageType int
|
||||||
FileName string
|
FileName string
|
||||||
@ -56,8 +66,10 @@ type FileEntry struct {
|
|||||||
DirectoryOffset int
|
DirectoryOffset int
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadDirectory(file *os.File, path string) (VolumeHeader, DirectoryHeader, []FileEntry) {
|
// ReadDirectory reads the directory information from a specified path
|
||||||
buffer := ReadBlock(file, 2)
|
// on a ProDOS image
|
||||||
|
func ReadDirectory(reader io.ReaderAt, path string) (VolumeHeader, DirectoryHeader, []FileEntry) {
|
||||||
|
buffer := ReadBlock(reader, 2)
|
||||||
|
|
||||||
volumeHeader := parseVolumeHeader(buffer)
|
volumeHeader := parseVolumeHeader(buffer)
|
||||||
|
|
||||||
@ -68,16 +80,16 @@ func ReadDirectory(file *os.File, path string) (VolumeHeader, DirectoryHeader, [
|
|||||||
path = strings.ToUpper(path)
|
path = strings.ToUpper(path)
|
||||||
paths := strings.Split(path, "/")
|
paths := strings.Split(path, "/")
|
||||||
|
|
||||||
directoryHeader, fileEntries := getFileEntriesInDirectory(file, 2, 1, paths)
|
directoryHeader, fileEntries := getFileEntriesInDirectory(reader, 2, 1, paths)
|
||||||
|
|
||||||
return volumeHeader, directoryHeader, fileEntries
|
return volumeHeader, directoryHeader, fileEntries
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFreeFileEntryInDirectory(file *os.File, directory string) (FileEntry, error) {
|
func getFreeFileEntryInDirectory(reader io.ReaderAt, directory string) (FileEntry, error) {
|
||||||
_, directoryHeader, _ := ReadDirectory(file, directory)
|
_, directoryHeader, _ := ReadDirectory(reader, directory)
|
||||||
//DumpDirectoryHeader(directoryHeader)
|
//DumpDirectoryHeader(directoryHeader)
|
||||||
blockNumber := directoryHeader.StartingBlock
|
blockNumber := directoryHeader.StartingBlock
|
||||||
buffer := ReadBlock(file, blockNumber)
|
buffer := ReadBlock(reader, blockNumber)
|
||||||
|
|
||||||
entryOffset := 43 // start at offset after header
|
entryOffset := 43 // start at offset after header
|
||||||
entryNumber := 2 // header is essentially the first entry so start at 2
|
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")
|
return FileEntry{}, errors.New("No free file entries found")
|
||||||
}
|
}
|
||||||
// else read the next block in the directory
|
// else read the next block in the directory
|
||||||
buffer = ReadBlock(file, blockNumber)
|
buffer = ReadBlock(reader, blockNumber)
|
||||||
entryOffset = 4
|
entryOffset = 4
|
||||||
entryNumber = 1
|
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) {
|
func getFileEntriesInDirectory(reader io.ReaderAt, blockNumber int, currentPath int, paths []string) (DirectoryHeader, []FileEntry) {
|
||||||
buffer := ReadBlock(file, blockNumber)
|
buffer := ReadBlock(reader, blockNumber)
|
||||||
|
|
||||||
directoryHeader := parseDirectoryHeader(buffer, blockNumber)
|
directoryHeader := parseDirectoryHeader(buffer, blockNumber)
|
||||||
|
|
||||||
@ -135,7 +147,7 @@ func getFileEntriesInDirectory(file *os.File, blockNumber int, currentPath int,
|
|||||||
if blockNumber == 0 {
|
if blockNumber == 0 {
|
||||||
return DirectoryHeader{}, nil
|
return DirectoryHeader{}, nil
|
||||||
}
|
}
|
||||||
buffer = ReadBlock(file, nextBlock)
|
buffer = ReadBlock(reader, nextBlock)
|
||||||
nextBlock = int(buffer[2]) + int(buffer[3])*256
|
nextBlock = int(buffer[2]) + int(buffer[3])*256
|
||||||
}
|
}
|
||||||
fileEntry := parseFileEntry(buffer[entryOffset:entryOffset+40], blockNumber, entryOffset)
|
fileEntry := parseFileEntry(buffer[entryOffset:entryOffset+40], blockNumber, entryOffset)
|
||||||
@ -147,7 +159,7 @@ func getFileEntriesInDirectory(file *os.File, blockNumber int, currentPath int,
|
|||||||
if matchedDirectory {
|
if matchedDirectory {
|
||||||
fileEntries[activeEntries] = fileEntry
|
fileEntries[activeEntries] = fileEntry
|
||||||
} else if !matchedDirectory && fileEntry.FileType == 15 && paths[currentPath+1] == fileEntry.FileName {
|
} 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++
|
activeEntries++
|
||||||
}
|
}
|
||||||
@ -194,7 +206,7 @@ func parseFileEntry(buffer []byte, blockNumber int, entryOffset int) FileEntry {
|
|||||||
return fileEntry
|
return fileEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeFileEntry(file *os.File, fileEntry FileEntry) {
|
func writeFileEntry(writer io.WriterAt, fileEntry FileEntry) {
|
||||||
buffer := make([]byte, 39)
|
buffer := make([]byte, 39)
|
||||||
buffer[0] = byte(fileEntry.StorageType)<<4 + byte(len(fileEntry.FileName))
|
buffer[0] = byte(fileEntry.StorageType)<<4 + byte(len(fileEntry.FileName))
|
||||||
for i := 0; i < len(fileEntry.FileName); i++ {
|
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[0x25] = byte(fileEntry.HeaderPointer & 0x00FF)
|
||||||
buffer[0x26] = byte(fileEntry.HeaderPointer >> 8)
|
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 {
|
if err != nil {
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -280,8 +292,8 @@ func parseDirectoryHeader(buffer []byte, blockNumber int) DirectoryHeader {
|
|||||||
return directoryEntry
|
return directoryEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeDirectoryHeader(file *os.File, directoryHeader DirectoryHeader) {
|
func writeDirectoryHeader(writer io.WriterAt, reader io.ReaderAt, directoryHeader DirectoryHeader) {
|
||||||
buffer := ReadBlock(file, directoryHeader.StartingBlock)
|
buffer := ReadBlock(reader, directoryHeader.StartingBlock)
|
||||||
buffer[0x00] = byte(directoryHeader.PreviousBlock & 0x00FF)
|
buffer[0x00] = byte(directoryHeader.PreviousBlock & 0x00FF)
|
||||||
buffer[0x01] = byte(directoryHeader.PreviousBlock >> 8)
|
buffer[0x01] = byte(directoryHeader.PreviousBlock >> 8)
|
||||||
buffer[0x02] = byte(directoryHeader.NextBlock & 0x00FF)
|
buffer[0x02] = byte(directoryHeader.NextBlock & 0x00FF)
|
||||||
@ -292,5 +304,5 @@ func writeDirectoryHeader(file *os.File, directoryHeader DirectoryHeader) {
|
|||||||
}
|
}
|
||||||
buffer[0x25] = byte(directoryHeader.ActiveFileCount & 0x00FF)
|
buffer[0x25] = byte(directoryHeader.ActiveFileCount & 0x00FF)
|
||||||
buffer[0x26] = byte(directoryHeader.ActiveFileCount >> 8)
|
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
|
package prodos
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func LoadFile(file *os.File, path string) ([]byte, error) {
|
// LoadFile loads in a file from a ProDOS volume into a byte array
|
||||||
fileEntry, err := getFileEntry(file, path)
|
func LoadFile(reader io.ReaderAt, path string) ([]byte, error) {
|
||||||
|
fileEntry, err := getFileEntry(reader, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
blockList, err := getDataBlocklist(file, fileEntry)
|
blockList, err := getDataBlocklist(reader, fileEntry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -21,7 +29,7 @@ func LoadFile(file *os.File, path string) ([]byte, error) {
|
|||||||
buffer := make([]byte, fileEntry.EndOfFile)
|
buffer := make([]byte, fileEntry.EndOfFile)
|
||||||
|
|
||||||
for i := 0; i < len(blockList); i++ {
|
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++ {
|
for j := 0; j < 512 && i*512+j < fileEntry.EndOfFile; j++ {
|
||||||
buffer[i*512+j] = block[j]
|
buffer[i*512+j] = block[j]
|
||||||
}
|
}
|
||||||
@ -30,36 +38,37 @@ func LoadFile(file *os.File, path string) ([]byte, error) {
|
|||||||
return buffer, nil
|
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)
|
directory, fileName := GetDirectoryAndFileNameFromPath(path)
|
||||||
|
|
||||||
existingFileEntry, _ := getFileEntry(file, path)
|
existingFileEntry, _ := getFileEntry(reader, path)
|
||||||
if existingFileEntry.StorageType != StorageDeleted {
|
if existingFileEntry.StorageType != StorageDeleted {
|
||||||
DeleteFile(file, path)
|
DeleteFile(writer, reader, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get list of blocks to write file to
|
// get list of blocks to write file to
|
||||||
blockList := createBlockList(file, len(buffer))
|
blockList := createBlockList(reader, len(buffer))
|
||||||
|
|
||||||
// seedling file
|
// seedling file
|
||||||
if len(buffer) <= 0x200 {
|
if len(buffer) <= 0x200 {
|
||||||
WriteBlock(file, blockList[0], buffer)
|
WriteBlock(writer, blockList[0], buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sapling file needs index block
|
// sapling file needs index block
|
||||||
if len(buffer) > 0x200 && len(buffer) <= 0x20000 {
|
if len(buffer) > 0x200 && len(buffer) <= 0x20000 {
|
||||||
writeSaplingFile(file, buffer, blockList)
|
writeSaplingFile(writer, buffer, blockList)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add tree file
|
// TODO: add tree file
|
||||||
if len(buffer) > 0x20000 {
|
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
|
// add file entry to directory
|
||||||
fileEntry, err := getFreeFileEntryInDirectory(file, directory)
|
fileEntry, err := getFreeFileEntryInDirectory(reader, directory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -80,59 +89,20 @@ func WriteFile(file *os.File, path string, fileType int, auxType int, buffer []b
|
|||||||
fileEntry.StorageType = StorageTree
|
fileEntry.StorageType = StorageTree
|
||||||
}
|
}
|
||||||
|
|
||||||
writeFileEntry(file, fileEntry)
|
writeFileEntry(writer, fileEntry)
|
||||||
|
|
||||||
// increment file count
|
// increment file count
|
||||||
directoryHeaderBlock := ReadBlock(file, fileEntry.HeaderPointer)
|
directoryHeaderBlock := ReadBlock(reader, fileEntry.HeaderPointer)
|
||||||
directoryHeader := parseDirectoryHeader(directoryHeaderBlock, fileEntry.HeaderPointer)
|
directoryHeader := parseDirectoryHeader(directoryHeaderBlock, fileEntry.HeaderPointer)
|
||||||
directoryHeader.ActiveFileCount++
|
directoryHeader.ActiveFileCount++
|
||||||
writeDirectoryHeader(file, directoryHeader)
|
writeDirectoryHeader(writer, reader, directoryHeader)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateVolumeBitmap(file *os.File, blockList []int) {
|
// DeleteFile deletes a file from a ProDOS volume
|
||||||
volumeBitmap := ReadVolumeBitmap(file)
|
func DeleteFile(writer io.WriterAt, reader io.ReaderAt, path string) error {
|
||||||
for i := 0; i < len(blockList); i++ {
|
fileEntry, err := getFileEntry(reader, path)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("File not found")
|
return errors.New("File not found")
|
||||||
}
|
}
|
||||||
@ -144,31 +114,32 @@ func DeleteFile(file *os.File, path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// free the blocks
|
// free the blocks
|
||||||
blocks, err := getBlocklist(file, fileEntry)
|
blocks, err := getBlocklist(reader, fileEntry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
volumeBitmap := ReadVolumeBitmap(file)
|
volumeBitmap := ReadVolumeBitmap(reader)
|
||||||
for i := 0; i < len(blocks); i++ {
|
for i := 0; i < len(blocks); i++ {
|
||||||
freeBlockInVolumeBitmap(volumeBitmap, blocks[i])
|
freeBlockInVolumeBitmap(volumeBitmap, blocks[i])
|
||||||
}
|
}
|
||||||
writeVolumeBitmap(file, volumeBitmap)
|
writeVolumeBitmap(writer, reader, volumeBitmap)
|
||||||
|
|
||||||
// decrement the directory entry count
|
// decrement the directory entry count
|
||||||
directoryBlock := ReadBlock(file, fileEntry.HeaderPointer)
|
directoryBlock := ReadBlock(reader, fileEntry.HeaderPointer)
|
||||||
directoryHeader := parseDirectoryHeader(directoryBlock, fileEntry.HeaderPointer)
|
directoryHeader := parseDirectoryHeader(directoryBlock, fileEntry.HeaderPointer)
|
||||||
|
|
||||||
directoryHeader.ActiveFileCount--
|
directoryHeader.ActiveFileCount--
|
||||||
writeDirectoryHeader(file, directoryHeader)
|
writeDirectoryHeader(writer, reader, directoryHeader)
|
||||||
|
|
||||||
// zero out directory entry
|
// zero out directory entry
|
||||||
fileEntry.StorageType = 0
|
fileEntry.StorageType = 0
|
||||||
fileEntry.FileName = ""
|
fileEntry.FileName = ""
|
||||||
writeFileEntry(file, fileEntry)
|
writeFileEntry(writer, fileEntry)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDirectoryAndFileNameFromPath gets the directory and filename from a path
|
||||||
func GetDirectoryAndFileNameFromPath(path string) (string, string) {
|
func GetDirectoryAndFileNameFromPath(path string) (string, string) {
|
||||||
path = strings.ToUpper(path)
|
path = strings.ToUpper(path)
|
||||||
paths := strings.Split(path, "/")
|
paths := strings.Split(path, "/")
|
||||||
@ -186,8 +157,48 @@ func GetDirectoryAndFileNameFromPath(path string) (string, string) {
|
|||||||
return directory, fileName
|
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
|
// 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)
|
blocks := make([]int, fileEntry.BlocksUsed)
|
||||||
|
|
||||||
switch fileEntry.StorageType {
|
switch fileEntry.StorageType {
|
||||||
@ -195,17 +206,17 @@ func getBlocklist(file *os.File, fileEntry FileEntry) ([]int, error) {
|
|||||||
blocks[0] = fileEntry.KeyPointer
|
blocks[0] = fileEntry.KeyPointer
|
||||||
return blocks, nil
|
return blocks, nil
|
||||||
case StorageSapling:
|
case StorageSapling:
|
||||||
index := ReadBlock(file, fileEntry.KeyPointer)
|
index := ReadBlock(reader, fileEntry.KeyPointer)
|
||||||
blocks[0] = fileEntry.KeyPointer
|
blocks[0] = fileEntry.KeyPointer
|
||||||
for i := 0; i < fileEntry.BlocksUsed-1; i++ {
|
for i := 0; i < fileEntry.BlocksUsed-1; i++ {
|
||||||
blocks[i+1] = int(index[i]) + int(index[i+256])*256
|
blocks[i+1] = int(index[i]) + int(index[i+256])*256
|
||||||
}
|
}
|
||||||
return blocks, nil
|
return blocks, nil
|
||||||
case StorageTree:
|
case StorageTree:
|
||||||
masterIndex := ReadBlock(file, fileEntry.KeyPointer)
|
masterIndex := ReadBlock(reader, fileEntry.KeyPointer)
|
||||||
blocks[0] = fileEntry.KeyPointer
|
blocks[0] = fileEntry.KeyPointer
|
||||||
for i := 0; i < 128; i++ {
|
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++ {
|
for j := 0; j < 256 && i*256+j < fileEntry.BlocksUsed; j++ {
|
||||||
if (int(index[j]) + int(index[j+256])*256) == 0 {
|
if (int(index[j]) + int(index[j+256])*256) == 0 {
|
||||||
return blocks, nil
|
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 {
|
switch fileEntry.StorageType {
|
||||||
case StorageSeedling:
|
case StorageSeedling:
|
||||||
blocks := make([]int, 1)
|
blocks := make([]int, 1)
|
||||||
@ -226,7 +237,7 @@ func getDataBlocklist(file *os.File, fileEntry FileEntry) ([]int, error) {
|
|||||||
return blocks, nil
|
return blocks, nil
|
||||||
case StorageSapling:
|
case StorageSapling:
|
||||||
blocks := make([]int, fileEntry.BlocksUsed-1)
|
blocks := make([]int, fileEntry.BlocksUsed-1)
|
||||||
index := ReadBlock(file, fileEntry.KeyPointer)
|
index := ReadBlock(reader, fileEntry.KeyPointer)
|
||||||
for i := 0; i < fileEntry.BlocksUsed-1; i++ {
|
for i := 0; i < fileEntry.BlocksUsed-1; i++ {
|
||||||
blocks[i] = int(index[i]) + int(index[i+256])*256
|
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")
|
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
|
numberOfBlocks := fileSize / 512
|
||||||
if fileSize%512 > 0 {
|
if fileSize%512 > 0 {
|
||||||
numberOfBlocks++
|
numberOfBlocks++
|
||||||
@ -254,15 +265,15 @@ func createBlockList(file *os.File, fileSize int) []int {
|
|||||||
numberOfBlocks++
|
numberOfBlocks++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
volumeBitmap := ReadVolumeBitmap(file)
|
volumeBitmap := ReadVolumeBitmap(reader)
|
||||||
blockList := findFreeBlocks(volumeBitmap, numberOfBlocks)
|
blockList := findFreeBlocks(volumeBitmap, numberOfBlocks)
|
||||||
|
|
||||||
return blockList
|
return blockList
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFileEntry(file *os.File, path string) (FileEntry, error) {
|
func getFileEntry(reader io.ReaderAt, path string) (FileEntry, error) {
|
||||||
directory, fileName := GetDirectoryAndFileNameFromPath(path)
|
directory, fileName := GetDirectoryAndFileNameFromPath(path)
|
||||||
_, _, fileEntries := ReadDirectory(file, directory)
|
_, _, fileEntries := ReadDirectory(reader, directory)
|
||||||
|
|
||||||
if fileEntries == nil || len(fileEntries) == 0 {
|
if fileEntries == nil || len(fileEntries) == 0 {
|
||||||
return FileEntry{}, errors.New("File entry not found")
|
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
|
package prodos
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"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 {
|
if numberOfBlocks > 65535 || numberOfBlocks < 64 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -20,7 +28,7 @@ func CreateVolume(file *os.File, volumeName string, numberOfBlocks int) {
|
|||||||
|
|
||||||
blankBlock := make([]byte, 512)
|
blankBlock := make([]byte, 512)
|
||||||
for i := 0; i < numberOfBlocks; i++ {
|
for i := 0; i < numberOfBlocks; i++ {
|
||||||
WriteBlockNoSync(file, i, blankBlock)
|
WriteBlock(writer, i, blankBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
volumeHeader := [43]byte{}
|
volumeHeader := [43]byte{}
|
||||||
@ -55,11 +63,10 @@ func CreateVolume(file *os.File, volumeName string, numberOfBlocks int) {
|
|||||||
volumeHeader[0x29] = byte(numberOfBlocks & 0xFF)
|
volumeHeader[0x29] = byte(numberOfBlocks & 0xFF)
|
||||||
volumeHeader[0x2A] = byte(numberOfBlocks >> 8)
|
volumeHeader[0x2A] = byte(numberOfBlocks >> 8)
|
||||||
|
|
||||||
file.WriteAt(volumeHeader[:], 1024)
|
writer.WriteAt(volumeHeader[:], 1024)
|
||||||
file.Sync()
|
|
||||||
|
|
||||||
// boot block 0
|
// boot block 0
|
||||||
WriteBlock(file, 0, getBootBlock())
|
WriteBlock(writer, 0, getBootBlock())
|
||||||
|
|
||||||
// pointers to volume directory blocks
|
// pointers to volume directory blocks
|
||||||
for i := 2; i < 6; i++ {
|
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[2] = byte(i + 1)
|
||||||
}
|
}
|
||||||
pointers[3] = 0x00
|
pointers[3] = 0x00
|
||||||
file.WriteAt(pointers, int64(i*512))
|
writer.WriteAt(pointers, int64(i*512))
|
||||||
}
|
}
|
||||||
|
|
||||||
// volume bit map starting at block 6
|
// volume bit map starting at block 6
|
||||||
volumeBitmap := createVolumeBitmap(numberOfBlocks)
|
volumeBitmap := createVolumeBitmap(numberOfBlocks)
|
||||||
writeVolumeBitmap(file, volumeBitmap)
|
writeVolumeBitmap(writer, reader, volumeBitmap)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBootBlock() []byte {
|
func getBootBlock() []byte {
|
||||||
|
@ -2,7 +2,6 @@ package prodos
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,17 +19,9 @@ func TestCreateVolume(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
testname := fmt.Sprintf("%d", tt.blocks)
|
testname := fmt.Sprintf("%d", tt.blocks)
|
||||||
t.Run(testname, func(t *testing.T) {
|
t.Run(testname, func(t *testing.T) {
|
||||||
fileName := os.TempDir() + "/test-volume.hdv"
|
file := NewMemoryFile(0x2000000)
|
||||||
defer os.Remove(fileName)
|
|
||||||
file, err := os.Create(fileName)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("failed to create file: %s\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer file.Close()
|
CreateVolume(file, file, tt.wantVolumeName, tt.blocks)
|
||||||
|
|
||||||
CreateVolume(file, tt.wantVolumeName, tt.blocks)
|
|
||||||
|
|
||||||
volumeHeader, _, fileEntries := ReadDirectory(file, "")
|
volumeHeader, _, fileEntries := ReadDirectory(file, "")
|
||||||
if volumeHeader.VolumeName != tt.wantVolumeName {
|
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
|
package prodos
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -6,6 +13,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TimeToString displays the date and time in ProDOS format
|
||||||
func TimeToString(printTime time.Time) string {
|
func TimeToString(printTime time.Time) string {
|
||||||
return fmt.Sprintf("%04d-%s-%02d %02d:%02d",
|
return fmt.Sprintf("%04d-%s-%02d %02d:%02d",
|
||||||
printTime.Year(),
|
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 {
|
func FileTypeToString(fileType int) string {
|
||||||
switch fileType {
|
switch fileType {
|
||||||
case 1:
|
case 1:
|
||||||
@ -78,6 +87,7 @@ func FileTypeToString(fileType int) string {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DumpFileEntry dumps the file entry values as text
|
||||||
func DumpFileEntry(fileEntry FileEntry) {
|
func DumpFileEntry(fileEntry FileEntry) {
|
||||||
fmt.Printf("FileName: %s\n", fileEntry.FileName)
|
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())
|
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")
|
fmt.Printf("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DumpVolumeHeader dumps the volume header values as text
|
||||||
func DumpVolumeHeader(volumeHeader VolumeHeader) {
|
func DumpVolumeHeader(volumeHeader VolumeHeader) {
|
||||||
fmt.Printf("Next block: %d\n", volumeHeader.NextBlock)
|
fmt.Printf("Next block: %d\n", volumeHeader.NextBlock)
|
||||||
fmt.Printf("Volume name: %s\n", volumeHeader.VolumeName)
|
fmt.Printf("Volume name: %s\n", volumeHeader.VolumeName)
|
||||||
@ -105,6 +116,7 @@ func DumpVolumeHeader(volumeHeader VolumeHeader) {
|
|||||||
fmt.Printf("Total blocks: %d\n", volumeHeader.TotalBlocks)
|
fmt.Printf("Total blocks: %d\n", volumeHeader.TotalBlocks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DumpDirectoryHeader dumps the directory header as text
|
||||||
func DumpDirectoryHeader(directoryHeader DirectoryHeader) {
|
func DumpDirectoryHeader(directoryHeader DirectoryHeader) {
|
||||||
fmt.Printf("Name: %s\n", directoryHeader.Name)
|
fmt.Printf("Name: %s\n", directoryHeader.Name)
|
||||||
fmt.Printf("File count: %d\n", directoryHeader.ActiveFileCount)
|
fmt.Printf("File count: %d\n", directoryHeader.ActiveFileCount)
|
||||||
@ -113,6 +125,7 @@ func DumpDirectoryHeader(directoryHeader DirectoryHeader) {
|
|||||||
fmt.Printf("Next block: %04X\n", directoryHeader.NextBlock)
|
fmt.Printf("Next block: %04X\n", directoryHeader.NextBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DumpBlock dumps the block as hexadecimal and text
|
||||||
func DumpBlock(buffer []byte) {
|
func DumpBlock(buffer []byte) {
|
||||||
for i := 0; i < len(buffer); i += 16 {
|
for i := 0; i < len(buffer); i += 16 {
|
||||||
fmt.Printf("%04X: ", i)
|
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) {
|
func DumpDirectory(blocksFree int, totalBlocks int, path string, fileEntries []FileEntry) {
|
||||||
fmt.Printf("%s\n\n", path)
|
fmt.Printf("%s\n\n", path)
|
||||||
fmt.Printf("NAME TYPE BLOCKS MODIFIED CREATED ENDFILE SUBTYPE\n\n")
|
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
|
package prodos
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
/* 49041 ($BF91) 49040 ($BF90)
|
// 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
|
//
|
||||||
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
|
||||||
DATE: | year | month | day |
|
// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
||||||
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
// DATE: | year | month | day |
|
||||||
|
// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
||||||
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
|
//
|
||||||
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
// 49043 ($BF93) 49042 ($BF92)
|
||||||
TIME: | hour | | minute |
|
//
|
||||||
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
|
||||||
|
// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
||||||
49043 ($BF93) 49042 ($BF92)
|
// TIME: | hour | | minute |
|
||||||
*/
|
// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
||||||
|
|
||||||
func DateTimeToProDOS(dateTime time.Time) []byte {
|
func DateTimeToProDOS(dateTime time.Time) []byte {
|
||||||
year := dateTime.Year() % 100
|
year := dateTime.Year() % 100
|
||||||
month := dateTime.Month()
|
month := dateTime.Month()
|
||||||
@ -35,6 +40,7 @@ func DateTimeToProDOS(dateTime time.Time) []byte {
|
|||||||
return buffer
|
return buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DateTimeToProDOS converts Time from ProDOS date time
|
||||||
func DateTimeFromProDOS(buffer []byte) time.Time {
|
func DateTimeFromProDOS(buffer []byte) time.Time {
|
||||||
if buffer[0] == 0 &&
|
if buffer[0] == 0 &&
|
||||||
buffer[1] == 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
|
package main
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
@ -8,7 +14,7 @@ func printReadme() {
|
|||||||
fmt.Println(`
|
fmt.Println(`
|
||||||
MIT License
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
Loading…
x
Reference in New Issue
Block a user