mirror of
https://github.com/tjboldt/ProDOS-Utilities.git
synced 2024-11-24 16:31:28 +00:00
Change to reader/writer instead of file
This commit is contained in:
parent
2a595f0a5c
commit
fb9621ff09
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 (
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
|
// 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 {
|
func ReadVolumeBitmap(reader io.ReaderAt) []byte {
|
||||||
headerBlock := ReadBlock(file, 2)
|
headerBlock := ReadBlock(reader, 2)
|
||||||
|
|
||||||
volumeHeader := parseVolumeHeader(headerBlock)
|
volumeHeader := parseVolumeHeader(headerBlock)
|
||||||
|
|
||||||
@ -23,7 +30,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 +40,13 @@ func ReadVolumeBitmap(file *os.File) []byte {
|
|||||||
return bitmap
|
return bitmap
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeVolumeBitmap(file *os.File, bitmap []byte) {
|
func writeVolumeBitmap(writer io.WriterAt, reader io.ReaderAt, bitmap []byte) {
|
||||||
headerBlock := ReadBlock(file, 2)
|
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])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,22 +1,24 @@
|
|||||||
|
// 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 {
|
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) {
|
func WriteBlock(writer io.WriterAt, block int, buffer []byte) {
|
||||||
WriteBlockNoSync(file, block, buffer)
|
writer.WriteAt(buffer, int64(block)*512)
|
||||||
file.Sync()
|
|
||||||
}
|
|
||||||
|
|
||||||
func WriteBlockNoSync(file *os.File, block int, buffer []byte) {
|
|
||||||
file.WriteAt(buffer, int64(block)*512)
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
|
// 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"
|
||||||
)
|
)
|
||||||
@ -56,8 +63,8 @@ type FileEntry struct {
|
|||||||
DirectoryOffset int
|
DirectoryOffset int
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadDirectory(file *os.File, path string) (VolumeHeader, DirectoryHeader, []FileEntry) {
|
func ReadDirectory(reader io.ReaderAt, path string) (VolumeHeader, DirectoryHeader, []FileEntry) {
|
||||||
buffer := ReadBlock(file, 2)
|
buffer := ReadBlock(reader, 2)
|
||||||
|
|
||||||
volumeHeader := parseVolumeHeader(buffer)
|
volumeHeader := parseVolumeHeader(buffer)
|
||||||
|
|
||||||
@ -68,16 +75,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 +98,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 +116,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 +142,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 +154,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 +201,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 +231,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 +287,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 +299,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
|
158
prodos/file.go
158
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,25 +38,25 @@ 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 {
|
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
|
||||||
@ -56,10 +64,10 @@ func WriteFile(file *os.File, path string, fileType int, auxType int, buffer []b
|
|||||||
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 +88,19 @@ 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) {
|
func DeleteFile(writer io.WriterAt, reader io.ReaderAt, path string) error {
|
||||||
volumeBitmap := ReadVolumeBitmap(file)
|
fileEntry, err := getFileEntry(reader, path)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("File not found")
|
return errors.New("File not found")
|
||||||
}
|
}
|
||||||
@ -144,27 +112,27 @@ 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
|
||||||
}
|
}
|
||||||
@ -186,8 +154,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 +203,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 +223,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 +234,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 +244,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 +262,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 (
|
||||||
|
@ -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 to and from ProDOS time format
|
||||||
|
|
||||||
package prodos
|
package prodos
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -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…
Reference in New Issue
Block a user