This commit is contained in:
Terence Boldt 2024-03-16 17:18:27 +00:00 committed by GitHub
commit cce095fc70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 392 additions and 306 deletions

View File

@ -1,4 +1,9 @@
#!/bin/bash
# Copyright Terence J. Boldt (c)2021-2024
# Use of this source code is governed by an MIT
# license that can be found in the LICENSE file.
GOOS=darwin GOARCH=arm64 go build -o binaries/macos/apple-silicon/ProDOS-Utilities
GOOS=darwin GOARCH=amd64 go build -o binaries/macos/intel/ProDOS-Utilities
GOOS=windows GOARCH=amd64 go build -o binaries/windows/intel/ProDOS-Utilities.exe

49
main.go
View File

@ -1,4 +1,4 @@
// Copyright Terence J. Boldt (c)2021-2023
// Copyright Terence J. Boldt (c)2021-2024
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
@ -17,7 +17,7 @@ import (
"github.com/tjboldt/ProDOS-Utilities/prodos"
)
const version = "0.4.9"
const version = "0.5.0"
func main() {
var fileName string
@ -25,21 +25,21 @@ func main() {
var command string
var outFileName string
var inFileName string
var blockNumber int
var volumeSize int
var blockNumber uint
var volumeSize uint
var volumeName string
var fileType int
var auxType int
var fileType uint
var auxType uint
flag.StringVar(&fileName, "d", "", "A ProDOS format drive image")
flag.StringVar(&pathName, "p", "", "Path name in ProDOS drive image (default is root of volume)")
flag.StringVar(&command, "c", "ls", "Command to execute: ls, get, put, rm, mkdir, readblock, writeblock, create, putall, putallrecursive")
flag.StringVar(&outFileName, "o", "", "Name of file to write")
flag.StringVar(&inFileName, "i", "", "Name of file to read")
flag.IntVar(&volumeSize, "s", 65535, "Number of blocks to create the volume with (default 65535, 64 to 65535, 0x0040 to 0xFFFF hex input accepted)")
flag.UintVar(&volumeSize, "s", 65535, "Number of blocks to create the volume with (default 65535, 64 to 65535, 0x0040 to 0xFFFF hex input accepted)")
flag.StringVar(&volumeName, "v", "NO.NAME", "Specifiy a name for the volume from 1 to 15 characters")
flag.IntVar(&blockNumber, "b", 0, "A block number to read/write from 0 to 65535 (0x0000 to 0xFFFF hex input accepted)")
flag.IntVar(&fileType, "t", 0, "ProDOS FileType: 0x04 for TXT, 0x06 for BIN, 0xFC for BAS, 0xFF for SYS etc., omit to autodetect")
flag.IntVar(&auxType, "a", 0, "ProDOS AuxType from 0 to 65535 (0x0000 to 0xFFFF hex input accepted), omit to autodetect")
flag.UintVar(&blockNumber, "b", 0, "A block number to read/write from 0 to 65535 (0x0000 to 0xFFFF hex input accepted)")
flag.UintVar(&fileType, "t", 0, "ProDOS FileType: 0x04 for TXT, 0x06 for BIN, 0xFC for BAS, 0xFF for SYS etc., omit to autodetect")
flag.UintVar(&auxType, "a", 0, "ProDOS AuxType from 0 to 65535 (0x0000 to 0xFFFF hex input accepted), omit to autodetect")
flag.Parse()
if len(fileName) == 0 {
@ -54,13 +54,13 @@ func main() {
case "get":
get(fileName, pathName, outFileName)
case "put":
put(fileName, pathName, fileType, auxType, inFileName)
put(fileName, pathName, uint8(fileType), uint16(auxType), inFileName)
case "readblock":
readBlock(blockNumber, fileName)
readBlock(uint16(blockNumber), fileName)
case "writeblock":
writeBlock(blockNumber, fileName, inFileName)
writeBlock(uint16(blockNumber), fileName, inFileName)
case "create":
create(fileName, volumeName, volumeSize)
create(fileName, volumeName, uint16(volumeSize))
case "putall":
putall(fileName, inFileName, pathName, false)
case "putallrecursive":
@ -89,6 +89,10 @@ func dumpFile(fileName string, pathName string) {
}
defer file.Close()
fileEntry, err := prodos.GetFileEntry(file, pathName)
if err != nil {
fmt.Printf("Failed to path %s:\n %s", pathName, err)
os.Exit(1)
}
prodos.DumpFileEntry(fileEntry)
}
@ -101,6 +105,10 @@ func dumpDirectory(fileName string, pathName string) {
}
defer file.Close()
_, directoryheader, _, err := prodos.ReadDirectory(file, pathName)
if err != nil {
fmt.Printf("Failed to read directory %s:\n %s", pathName, err)
os.Exit(1)
}
prodos.DumpDirectoryHeader(directoryheader)
}
@ -147,7 +155,7 @@ func putall(fileName string, inFileName string, pathName string, recursive bool)
}
}
func create(fileName string, volumeName string, volumeSize int) {
func create(fileName string, volumeName string, volumeSize uint16) {
file, err := os.Create(fileName)
if err != nil {
fmt.Printf("failed to create file: %s\n", err)
@ -157,7 +165,7 @@ func create(fileName string, volumeName string, volumeSize int) {
prodos.CreateVolume(file, volumeName, volumeSize)
}
func writeBlock(blockNumber int, fileName string, inFileName string) {
func writeBlock(blockNumber uint16, fileName string, inFileName string) {
checkInFileName(inFileName)
fmt.Printf("Writing block 0x%04X (%d):\n\n", blockNumber, blockNumber)
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
@ -174,7 +182,7 @@ func writeBlock(blockNumber int, fileName string, inFileName string) {
prodos.WriteBlock(file, blockNumber, inFile)
}
func readBlock(blockNumber int, fileName string) {
func readBlock(blockNumber uint16, fileName string) {
fmt.Printf("Reading block 0x%04X (%d):\n\n", blockNumber, blockNumber)
file, err := os.OpenFile(fileName, os.O_RDONLY, 0755)
if err != nil {
@ -190,7 +198,7 @@ func readBlock(blockNumber int, fileName string) {
prodos.DumpBlock(block)
}
func put(fileName string, pathName string, fileType int, auxType int, inFileName string) {
func put(fileName string, pathName string, fileType uint8, auxType uint16, inFileName string) {
checkPathName(pathName)
checkInFileName(inFileName)
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
@ -200,6 +208,9 @@ func put(fileName string, pathName string, fileType int, auxType int, inFileName
}
defer file.Close()
fileInfo, err := os.Stat(fileName)
if err != nil {
fmt.Printf("Failed get fileInfo for %s - %s", fileName, err)
}
err = prodos.WriteFileFromFile(file, pathName, fileType, auxType, fileInfo.ModTime(), inFileName, false)
if err != nil {
@ -229,7 +240,7 @@ func get(fileName string, pathName string, outFileName string) {
os.Exit(1)
}
if strings.HasSuffix(strings.ToLower(outFileName), ".bas") {
fmt.Fprintf(outFile, prodos.ConvertBasicToText(getFile))
fmt.Fprint(outFile, prodos.ConvertBasicToText(getFile))
} else {
outFile.Write(getFile)
}

View File

@ -1,4 +1,4 @@
// Copyright Terence J. Boldt (c)2021-2023
// Copyright Terence J. Boldt (c)2021-2024
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.

View File

@ -1,4 +1,4 @@
// Copyright Terence J. Boldt (c)2022-2023
// Copyright Terence J. Boldt (c)2022-2024
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
@ -74,7 +74,7 @@ func TestConvertTextToBasic(t *testing.T) {
testname := tt.name
t.Run(testname, func(t *testing.T) {
basic, _ := ConvertTextToBasic(tt.basicText)
if bytes.Compare(basic, tt.want) != 0 {
if !bytes.Equal(basic, tt.want) {
t.Errorf("%s\ngot '%#v'\nwant '%#v'\n", testname, basic, tt.want)
}
})

View File

@ -1,4 +1,4 @@
// Copyright Terence J. Boldt (c)2021-2023
// Copyright Terence J. Boldt (c)2021-2024
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
@ -33,13 +33,13 @@ func ReadVolumeBitmap(reader io.ReaderAt) ([]byte, error) {
totalBitmapBlocks++
}
for i := 0; i < totalBitmapBlocks; i++ {
for i := uint16(0); i < totalBitmapBlocks; i++ {
bitmapBlock, err := ReadBlock(reader, i+volumeHeader.BitmapStartBlock)
if err != nil {
return nil, err
}
for j := 0; j < 512 && i*512+j < totalBitmapBytes; j++ {
for j := uint16(0); j < 512 && i*512+j < totalBitmapBytes; j++ {
bitmap[i*512+j] = bitmapBlock[j]
}
}
@ -48,10 +48,10 @@ func ReadVolumeBitmap(reader io.ReaderAt) ([]byte, error) {
}
// GetFreeBlockCount gets the number of free blocks on a ProDOS image
func GetFreeBlockCount(volumeBitmap []byte, totalBlocks int) int {
freeBlockCount := 0
func GetFreeBlockCount(volumeBitmap []byte, totalBlocks uint16) uint16 {
freeBlockCount := uint16(0)
for i := 0; i < totalBlocks; i++ {
for i := uint16(0); i < totalBlocks; i++ {
if checkFreeBlockInVolumeBitmap(volumeBitmap, i) {
freeBlockCount++
}
@ -77,13 +77,13 @@ func writeVolumeBitmap(readerWriter ReaderWriterAt, bitmap []byte) error {
totalBitmapBlocks++
}
for i := 0; i < totalBitmapBlocks; i++ {
for i := uint16(0); i < totalBitmapBlocks; i++ {
bitmapBlock, err := ReadBlock(readerWriter, i+volumeHeader.BitmapStartBlock)
if err != nil {
return err
}
for j := 0; j < 512 && i*512+j < totalBitmapBytes; j++ {
for j := uint16(0); j < 512 && i*512+j < totalBitmapBytes; j++ {
bitmapBlock[j] = bitmap[i*512+j]
}
@ -96,9 +96,10 @@ func writeVolumeBitmap(readerWriter ReaderWriterAt, bitmap []byte) error {
return nil
}
func createVolumeBitmap(numberOfBlocks int) []byte {
volumeBitmapBlocks := numberOfBlocks / 512 / 8
if volumeBitmapBlocks*8*512 < numberOfBlocks {
func createVolumeBitmap(numberOfBlocks uint16) []byte {
// needs to be > uint16 because it's multiplying by a uint16
volumeBitmapBlocks := uint32(numberOfBlocks / 512 / 8)
if volumeBitmapBlocks*8*512 < uint32(numberOfBlocks) {
volumeBitmapBlocks++
}
@ -119,30 +120,31 @@ func createVolumeBitmap(numberOfBlocks int) []byte {
markBlockInVolumeBitmap(volumeBitmap, 5)
// volume bitmap blocks
for i := 0; i < volumeBitmapBlocks; i++ {
markBlockInVolumeBitmap(volumeBitmap, 6+i)
for i := uint32(0); i < volumeBitmapBlocks; i++ {
markBlockInVolumeBitmap(volumeBitmap, uint16(6+i))
}
// blocks beyond the volume
totalBlocksInBitmap := volumeBitmapBlocks * 512 * 8
blocksBeyondEnd := totalBlocksInBitmap - numberOfBlocks
blocksBeyondEnd := totalBlocksInBitmap - uint32(numberOfBlocks)
if blocksBeyondEnd > 0 {
for i := totalBlocksInBitmap - blocksBeyondEnd; i < totalBlocksInBitmap; i++ {
markBlockInVolumeBitmap(volumeBitmap, i)
markBlockInVolumeBitmap(volumeBitmap, uint16(i))
}
}
return volumeBitmap
}
func findFreeBlocks(volumeBitmap []byte, numberOfBlocks int) []int {
blocks := make([]int, numberOfBlocks)
func findFreeBlocks(volumeBitmap []byte, numberOfBlocks uint16) []uint16 {
blocks := make([]uint16, numberOfBlocks)
blocksFound := 0
blocksFound := uint16(0)
for i := 0; i < len(volumeBitmap)*8; i++ {
if checkFreeBlockInVolumeBitmap(volumeBitmap, i) {
blocks[blocksFound] = i
// needs to be > uint16 because it's multiplying by a uint16
for i := uint32(0); i < uint32(len(volumeBitmap))*8; i++ {
if checkFreeBlockInVolumeBitmap(volumeBitmap, uint16(i)) {
blocks[blocksFound] = uint16(i)
blocksFound++
if blocksFound == numberOfBlocks {
return blocks
@ -153,87 +155,29 @@ func findFreeBlocks(volumeBitmap []byte, numberOfBlocks int) []int {
return nil
}
func markBlockInVolumeBitmap(volumeBitmap []byte, blockNumber int) {
func markBlockInVolumeBitmap(volumeBitmap []byte, blockNumber uint16) {
bitToChange := blockNumber % 8
byteToChange := blockNumber / 8
byteToAnd := 0b11111111
byteToAnd := (uint8(0b10000000) >> uint8(bitToChange)) ^ 0b11111111
switch bitToChange {
case 0:
byteToAnd = 0b01111111
case 1:
byteToAnd = 0b10111111
case 2:
byteToAnd = 0b11011111
case 3:
byteToAnd = 0b11101111
case 4:
byteToAnd = 0b11110111
case 5:
byteToAnd = 0b11111011
case 6:
byteToAnd = 0b11111101
case 7:
byteToAnd = 0b11111110
}
//fmt.Printf("blockNumber: $%04X byteToWrite: 0b%08b volumeBitmap: $%02X byteToChange: $%04X\n", blockNumber, byteToWrite, volumeBitmap[byteToChange], byteToChange)
volumeBitmap[byteToChange] &= byte(byteToAnd)
}
func freeBlockInVolumeBitmap(volumeBitmap []byte, blockNumber int) {
func freeBlockInVolumeBitmap(volumeBitmap []byte, blockNumber uint16) {
bitToChange := blockNumber % 8
byteToChange := blockNumber / 8
byteToOr := 0b00000000
switch bitToChange {
case 0:
byteToOr = 0b10000000
case 1:
byteToOr = 0b01000000
case 2:
byteToOr = 0b00100000
case 3:
byteToOr = 0b00010000
case 4:
byteToOr = 0b00001000
case 5:
byteToOr = 0b00000100
case 6:
byteToOr = 0b00000010
case 7:
byteToOr = 0b00000001
}
byteToOr := uint8(0b10000000) >> uint8(bitToChange)
volumeBitmap[byteToChange] |= byte(byteToOr)
}
func checkFreeBlockInVolumeBitmap(volumeBitmap []byte, blockNumber int) bool {
func checkFreeBlockInVolumeBitmap(volumeBitmap []byte, blockNumber uint16) bool {
bitToCheck := blockNumber % 8
byteToCheck := blockNumber / 8
byteToAnd := 0b00000000
switch bitToCheck {
case 0:
byteToAnd = 0b10000000
case 1:
byteToAnd = 0b01000000
case 2:
byteToAnd = 0b00100000
case 3:
byteToAnd = 0b00010000
case 4:
byteToAnd = 0b00001000
case 5:
byteToAnd = 0b00000100
case 6:
byteToAnd = 0b00000010
case 7:
byteToAnd = 0b00000001
}
byteToAnd := uint8(0b10000000) >> uint8(bitToCheck)
return (volumeBitmap[byteToCheck] & byte(byteToAnd)) > 0
}

View File

@ -1,8 +1,8 @@
// Copyright Terence J. Boldt (c)2021-2023
// Copyright Terence J. Boldt (c)2021-2024
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
// This file provides tests for access to volum bitmap on
// This file provides tests for access to volume bitmap on
// a ProDOS drive image
package prodos
@ -14,10 +14,10 @@ import (
func TestCreateVolumeBitmap(t *testing.T) {
var tests = []struct {
blocks int
want int
blocks uint16
want uint16
}{
{65536, 8192},
{65535, 8192},
{65533, 8192},
{140, 512},
}
@ -26,7 +26,7 @@ func TestCreateVolumeBitmap(t *testing.T) {
testname := fmt.Sprintf("%d", tt.blocks)
t.Run(testname, func(t *testing.T) {
volumeBitMap := createVolumeBitmap(tt.blocks)
ans := len(volumeBitMap)
ans := uint16(len(volumeBitMap))
if ans != tt.want {
t.Errorf("got %d, want %d", ans, tt.want)
}
@ -36,7 +36,7 @@ func TestCreateVolumeBitmap(t *testing.T) {
func TestCheckFreeBlockInVolumeBitmap(t *testing.T) {
var tests = []struct {
blocks int
blocks uint16
want bool
}{
{0, false}, // boot block
@ -63,7 +63,7 @@ func TestCheckFreeBlockInVolumeBitmap(t *testing.T) {
func TestMarkBlockInVolumeBitmap(t *testing.T) {
var tests = []struct {
blocks int
blocks uint16
want bool
}{
{0, false}, // boot block
@ -89,3 +89,26 @@ func TestMarkBlockInVolumeBitmap(t *testing.T) {
})
}
}
func TestUpdateVolumeBitmap(t *testing.T) {
blockList := []uint16{10, 11, 12, 100, 120}
virtualDisk := NewMemoryFile(0x2000000)
CreateVolume(virtualDisk, "VIRTUAL.DISK", 0xFFFE)
updateVolumeBitmap(virtualDisk, blockList)
for _, tt := range blockList {
testname := fmt.Sprintf("%d", tt)
t.Run(testname, func(t *testing.T) {
volumeBitmap, err := ReadVolumeBitmap(virtualDisk)
if err != nil {
t.Error("got error, want nil")
}
free := checkFreeBlockInVolumeBitmap(volumeBitmap, tt)
if free {
t.Errorf("got true, want false")
}
})
}
}

View File

@ -1,4 +1,4 @@
// Copyright Terence J. Boldt (c)2021-2023
// Copyright Terence J. Boldt (c)2021-2024
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
@ -14,7 +14,7 @@ import (
)
// ReadBlock reads a block from a ProDOS volume into a byte array
func ReadBlock(reader io.ReaderAt, block int) ([]byte, error) {
func ReadBlock(reader io.ReaderAt, block uint16) ([]byte, error) {
buffer := make([]byte, 512)
_, err := reader.ReadAt(buffer, int64(block)*512)
@ -27,7 +27,7 @@ func ReadBlock(reader io.ReaderAt, block int) ([]byte, error) {
}
// WriteBlock writes a block to a ProDOS volume from a byte array
func WriteBlock(writer io.WriterAt, block int, buffer []byte) error {
func WriteBlock(writer io.WriterAt, block uint16, buffer []byte) error {
_, err := writer.WriteAt(buffer, int64(block)*512)
if err != nil {
errString := fmt.Sprintf("failed to write block %04X: %s", block, err.Error())

View File

@ -1,9 +1,9 @@
// Copyright Terence J. Boldt (c)2021-2023
// Copyright Terence J. Boldt (c)2021-2024
// 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
// and parse directories on a ProDOS drive image
package prodos
@ -19,33 +19,33 @@ import (
type VolumeHeader struct {
VolumeName string
CreationTime time.Time
ActiveFileCount int
BitmapStartBlock int
TotalBlocks int
NextBlock int
EntryLength int
EntriesPerBlock int
MinVersion int
Version int
ActiveFileCount uint16
BitmapStartBlock uint16
TotalBlocks uint16
NextBlock uint16
EntryLength uint8
EntriesPerBlock uint8
MinVersion uint8
Version uint8
}
// DirectoryHeader from ProDOS
type DirectoryHeader struct {
PreviousBlock int
NextBlock int
PreviousBlock uint16
NextBlock uint16
IsSubDirectory bool
Name string
CreationTime time.Time
Version int
MinVersion int
Access int
EntryLength int
EntriesPerBlock int
ActiveFileCount int
StartingBlock int
ParentBlock int
ParentEntry int
ParentEntryLength int
Version uint8
MinVersion uint8
Access uint8
EntryLength uint8
EntriesPerBlock uint8
ActiveFileCount uint16
StartingBlock uint16
ParentBlock uint16
ParentEntry uint16
ParentEntryLength uint8
}
const (
@ -65,22 +65,22 @@ const (
// FileEntry from ProDOS
type FileEntry struct {
StorageType int
StorageType uint8
FileName string
FileType int
FileType uint8
CreationTime time.Time
KeyPointer int
Version int
MinVersion int
BlocksUsed int
EndOfFile int
Access int
AuxType int
KeyPointer uint16
Version uint8
MinVersion uint8
BlocksUsed uint16
EndOfFile uint32
Access uint8
AuxType uint16
ModifiedTime time.Time
HeaderPointer int
HeaderPointer uint16
DirectoryBlock int
DirectoryOffset int
DirectoryBlock uint16
DirectoryOffset uint16
}
// ReadDirectory reads the directory information from a specified path
@ -117,7 +117,7 @@ func ReadDirectory(reader io.ReaderAt, path string) (VolumeHeader, DirectoryHead
// on a ProDOS image
func CreateDirectory(readerWriter ReaderWriterAt, path string) error {
if len(path) == 0 {
return errors.New("cannot create directory with path")
return errors.New("cannot create directory without path")
}
// add volume name if not full path
@ -161,7 +161,10 @@ func CreateDirectory(readerWriter ReaderWriterAt, path string) error {
fileEntry.Version = 0x24
fileEntry.MinVersion = 0x00
writeFileEntry(readerWriter, fileEntry)
err = writeFileEntry(readerWriter, fileEntry)
if err != nil {
return err
}
err = incrementFileCount(readerWriter, fileEntry)
if err != nil {
@ -183,7 +186,7 @@ func CreateDirectory(readerWriter ReaderWriterAt, path string) error {
ActiveFileCount: 0,
StartingBlock: blockList[0],
ParentBlock: fileEntry.DirectoryBlock,
ParentEntry: (fileEntry.DirectoryOffset - 0x04) / 0x27,
ParentEntry: uint16(fileEntry.DirectoryOffset-0x04) / 0x27,
ParentEntryLength: 0x27,
}
@ -219,18 +222,18 @@ func getFreeFileEntryInDirectory(readerWriter ReaderWriterAt, directory string)
if err != nil {
return FileEntry{}, err
}
entryOffset := 43 // start at offset after header
entryNumber := 2 // header is essentially the first entry so start at 2
entryOffset := uint16(43) // start at offset after header
entryNumber := 2 // header is essentially the first entry so start at 2
for {
if entryNumber > 13 {
nextBlockNumber := int(buffer[2]) + int(buffer[3])*256
nextBlockNumber := uint16(buffer[2]) + uint16(buffer[3])*256
// if we ran out of blocks in the directory, expand directory or fail
if nextBlockNumber == 0 {
if !directoryHeader.IsSubDirectory {
return FileEntry{}, errors.New("no free file entries found")
}
nextBlockNumber, err = expandDirectory(readerWriter, nextBlockNumber, buffer, blockNumber, directoryHeader)
nextBlockNumber, err = expandDirectory(readerWriter, buffer, blockNumber, directoryHeader)
if err != nil {
return FileEntry{}, err
}
@ -260,7 +263,7 @@ func getFreeFileEntryInDirectory(readerWriter ReaderWriterAt, directory string)
}
}
func expandDirectory(readerWriter ReaderWriterAt, nextBlockNumber int, buffer []byte, blockNumber int, directoryHeader DirectoryHeader) (int, error) {
func expandDirectory(readerWriter ReaderWriterAt, buffer []byte, blockNumber uint16, directoryHeader DirectoryHeader) (uint16, error) {
volumeBitMap, err := ReadVolumeBitmap(readerWriter)
if err != nil {
errString := fmt.Sprintf("failed to get volume bitmap to expand directory: %s", err)
@ -271,10 +274,10 @@ func expandDirectory(readerWriter ReaderWriterAt, nextBlockNumber int, buffer []
return 0, errors.New("failed to get free block to expand directory")
}
nextBlockNumber = blockList[0]
nextBlockNumber := blockList[0]
buffer[0x02] = byte(nextBlockNumber & 0x00FF)
buffer[0x03] = byte(nextBlockNumber >> 8)
WriteBlock(readerWriter, blockNumber, buffer)
err = WriteBlock(readerWriter, blockNumber, buffer)
if err != nil {
errString := fmt.Sprintf("failed to write block to expand directory: %s", err)
return 0, errors.New(errString)
@ -296,16 +299,19 @@ func expandDirectory(readerWriter ReaderWriterAt, nextBlockNumber int, buffer []
errString := fmt.Sprintf("failed to read parent block to expand directory: %s", err)
return 0, errors.New(errString)
}
directoryEntryOffset := directoryHeader.ParentEntry*directoryHeader.EntryLength + 0x04
directoryFileEntry := parseFileEntry(buffer[directoryEntryOffset:directoryEntryOffset+0x28], directoryHeader.ParentBlock, directoryHeader.ParentEntry*directoryHeader.EntryLength+0x04)
directoryEntryOffset := directoryHeader.ParentEntry*uint16(directoryHeader.EntryLength) + 0x04
directoryFileEntry := parseFileEntry(buffer[directoryEntryOffset:directoryEntryOffset+0x28], directoryHeader.ParentBlock, directoryHeader.ParentEntry*uint16(directoryHeader.EntryLength)+0x04)
directoryFileEntry.BlocksUsed++
directoryFileEntry.EndOfFile += 0x200
writeFileEntry(readerWriter, directoryFileEntry)
err = writeFileEntry(readerWriter, directoryFileEntry)
if err != nil {
return 0, err
}
return nextBlockNumber, nil
}
func getFileEntriesInDirectory(reader io.ReaderAt, blockNumber int, currentPath int, paths []string) (DirectoryHeader, []FileEntry, error) {
func getFileEntriesInDirectory(reader io.ReaderAt, blockNumber uint16, currentPath int, paths []string) (DirectoryHeader, []FileEntry, error) {
buffer, err := ReadBlock(reader, blockNumber)
if err != nil {
return DirectoryHeader{}, nil, err
@ -314,9 +320,9 @@ func getFileEntriesInDirectory(reader io.ReaderAt, blockNumber int, currentPath
directoryHeader := parseDirectoryHeader(buffer, blockNumber)
fileEntries := make([]FileEntry, directoryHeader.ActiveFileCount)
entryOffset := 43 // start at offset after header
activeEntries := 0
entryNumber := 2 // header is essentially the first entry so start at 2
entryOffset := uint16(43) // start at offset after header
activeEntries := uint16(0)
entryNumber := uint8(2) // header is essentially the first entry so start at 2
nextBlock := directoryHeader.NextBlock
@ -338,7 +344,7 @@ func getFileEntriesInDirectory(reader io.ReaderAt, blockNumber int, currentPath
if err != nil {
return DirectoryHeader{}, nil, err
}
nextBlock = int(buffer[2]) + int(buffer[3])*256
nextBlock = uint16(buffer[2]) + uint16(buffer[3])*256
}
fileEntry := parseFileEntry(buffer[entryOffset:entryOffset+40], blockNumber, entryOffset)
@ -359,21 +365,21 @@ func getFileEntriesInDirectory(reader io.ReaderAt, blockNumber int, currentPath
}
}
func parseFileEntry(buffer []byte, blockNumber int, entryOffset int) FileEntry {
storageType := int(buffer[0] >> 4)
fileNameLength := int(buffer[0] & 15)
func parseFileEntry(buffer []byte, blockNumber uint16, entryOffset uint16) FileEntry {
storageType := buffer[0] >> 4
fileNameLength := buffer[0] & 15
fileName := string(buffer[1 : fileNameLength+1])
fileType := int(buffer[16])
startingBlock := int(buffer[17]) + int(buffer[18])*256
blocksUsed := int(buffer[19]) + int(buffer[20])*256
endOfFile := int(buffer[21]) + int(buffer[22])*256 + int(buffer[23])*65536
fileType := buffer[16]
startingBlock := uint16(buffer[17]) + uint16(buffer[18])*256
blocksUsed := uint16(buffer[19]) + uint16(buffer[20])*256
endOfFile := uint32(buffer[21]) + uint32(buffer[22])*256 + uint32(buffer[23])*65536
creationTime := DateTimeFromProDOS(buffer[24:28])
version := int(buffer[28])
minVersion := int(buffer[29])
access := int(buffer[30])
auxType := int(buffer[31]) + int(buffer[32])*256
version := buffer[28]
minVersion := buffer[29]
access := buffer[30]
auxType := uint16(buffer[31]) + uint16(buffer[32])*256
modifiedTime := DateTimeFromProDOS((buffer[33:37]))
headerPointer := int(buffer[0x25]) + int(buffer[0x26])*256
headerPointer := uint16(buffer[0x25]) + uint16(buffer[0x26])*256
fileEntry := FileEntry{
StorageType: storageType,
@ -396,7 +402,7 @@ func parseFileEntry(buffer []byte, blockNumber int, entryOffset int) FileEntry {
return fileEntry
}
func writeFileEntry(writer io.WriterAt, fileEntry FileEntry) {
func writeFileEntry(writer io.WriterAt, fileEntry FileEntry) error {
buffer := make([]byte, 39)
buffer[0] = byte(fileEntry.StorageType)<<4 + byte(len(fileEntry.FileName))
for i := 0; i < len(fileEntry.FileName); i++ {
@ -427,24 +433,26 @@ func writeFileEntry(writer io.WriterAt, fileEntry FileEntry) {
buffer[0x26] = byte(fileEntry.HeaderPointer >> 8)
//fmt.Printf("Writing file entry at block: %04X offset: %04X\n", fileEntry.DirectoryBlock, fileEntry.DirectoryOffset)
_, err := writer.WriteAt(buffer, int64(fileEntry.DirectoryBlock*512+fileEntry.DirectoryOffset))
_, err := writer.WriteAt(buffer, int64(fileEntry.DirectoryBlock)*512+int64(fileEntry.DirectoryOffset))
if err != nil {
return err
}
return nil
}
func parseVolumeHeader(buffer []byte) VolumeHeader {
nextBlock := int(buffer[2]) + int(buffer[3])*256
nextBlock := uint16(buffer[2]) + uint16(buffer[3])*256
filenameLength := buffer[4] & 15
volumeName := string(buffer[5 : filenameLength+5])
creationTime := DateTimeFromProDOS(buffer[28:32])
version := int(buffer[32])
minVersion := int(buffer[33])
entryLength := int(buffer[35])
entriesPerBlock := int(buffer[36])
fileCount := int(buffer[37]) + int(buffer[38])*256
bitmapBlock := int(buffer[39]) + int(buffer[40])*256
totalBlocks := int(buffer[41]) + int(buffer[42])*256
version := buffer[32]
minVersion := buffer[33]
entryLength := buffer[35]
entriesPerBlock := buffer[36]
fileCount := uint16(buffer[37]) + uint16(buffer[38])*256
bitmapBlock := uint16(buffer[39]) + uint16(buffer[40])*256
totalBlocks := uint16(buffer[41]) + uint16(buffer[42])*256
if minVersion > 0 {
panic("Unsupported ProDOS version")
@ -465,22 +473,22 @@ func parseVolumeHeader(buffer []byte) VolumeHeader {
return volumeHeader
}
func parseDirectoryHeader(buffer []byte, blockNumber int) DirectoryHeader {
previousBlock := int(buffer[0x00]) + int(buffer[0x01])*256
nextBlock := int(buffer[0x02]) + int(buffer[0x03])*256
func parseDirectoryHeader(buffer []byte, blockNumber uint16) DirectoryHeader {
previousBlock := uint16(buffer[0x00]) + uint16(buffer[0x01])*256
nextBlock := uint16(buffer[0x02]) + uint16(buffer[0x03])*256
isSubDirectory := (buffer[0x04] & 0xF0) == 0xE0
filenameLength := buffer[0x04] & 0x0F
name := string(buffer[0x05 : filenameLength+0x05])
creationTime := DateTimeFromProDOS(buffer[0x1C:0x20])
version := int(buffer[0x20])
minVersion := int(buffer[0x21])
access := int(buffer[0x22])
entryLength := int(buffer[0x23])
entriesPerBlock := int(buffer[0x24])
fileCount := int(buffer[0x25]) + int(buffer[0x26])*256
parentBlock := int(buffer[0x27]) + int(buffer[0x28])*256
parentEntry := int(buffer[0x29])
parentEntryLength := int(buffer[0x2A])
version := buffer[0x20]
minVersion := buffer[0x21]
access := buffer[0x22]
entryLength := buffer[0x23]
entriesPerBlock := buffer[0x24]
fileCount := uint16(buffer[0x25]) + uint16(buffer[0x26])*256
parentBlock := uint16(buffer[0x27]) + uint16(buffer[0x28])*256
parentEntry := uint16(buffer[0x29])
parentEntryLength := buffer[0x2A]
directoryEntry := DirectoryHeader{
PreviousBlock: previousBlock,

79
prodos/directory_test.go Normal file
View File

@ -0,0 +1,79 @@
// Copyright Terence J. Boldt (c)2024
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
// This file provides tests for directories on
// a ProDOS drive image
package prodos
import (
"testing"
)
func TestCreateDirectoryWithoutPathFails(t *testing.T) {
t.Run("TestCreateDirectoryWithoutPathFails", func(t *testing.T) {
file := NewMemoryFile(0x2000000)
CreateVolume(file, "test.volume", 1024)
err := CreateDirectory(file, "")
if err == nil {
t.Errorf("got nil, want non-nil")
}
})
}
func TestCreateDuplicateDirectoryFails(t *testing.T) {
t.Run("TestCreateDuplicateDirectoryFails", func(t *testing.T) {
file := NewMemoryFile(0x2000000)
CreateVolume(file, "test.volume", 1024)
err := CreateDirectory(file, "duplicate")
if err != nil {
t.Errorf("failed to create directory: %s", err)
}
err = CreateDirectory(file, "duplicate")
if err == nil {
t.Error("got nil, want non-nil")
}
})
}
func TestCreateAndReadDirectory(t *testing.T) {
var tests = []struct {
testName string
createPath string
readPath string
expectedCount int
}{
{"checkRoot", "", "/test", 0},
{"checkCreateInRoot", "one", "/test", 1},
{"checkRootCreateInSub", "/test/one/two", "/test", 1},
{"checkSub", "", "/test/one", 1},
}
file := NewMemoryFile(0x2000000)
CreateVolume(file, "test", 1024)
for _, tt := range tests {
t.Run(tt.testName, func(t *testing.T) {
if len(tt.createPath) > 0 {
CreateDirectory(file, tt.createPath)
}
_, _, fileEntries, err := ReadDirectory(file, tt.readPath)
if err != nil {
t.Errorf("got error %s", err)
}
got := len(fileEntries)
if got != int(tt.expectedCount) {
t.Errorf("got %d, want %d", got, tt.expectedCount)
}
})
}
}

View File

@ -1,4 +1,4 @@
// Copyright Terence J. Boldt (c)2021-2023
// Copyright Terence J. Boldt (c)2021-2024
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.

View File

@ -1,4 +1,4 @@
// Copyright Terence J. Boldt (c)2021-2023
// Copyright Terence J. Boldt (c)2021-2024
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
@ -29,12 +29,12 @@ func LoadFile(reader io.ReaderAt, path string) ([]byte, error) {
buffer := make([]byte, fileEntry.EndOfFile)
for i := 0; i < len(blockList); i++ {
for i := uint16(0); i < uint16(len(blockList)); i++ {
block, err := ReadBlock(reader, blockList[i])
if err != nil {
return nil, err
}
for j := 0; j < 512 && i*512+j < fileEntry.EndOfFile; j++ {
for j := uint16(0); j < 512 && uint32(i)*512+uint32(j) < fileEntry.EndOfFile; j++ {
buffer[i*512+j] = block[j]
}
}
@ -43,7 +43,7 @@ func LoadFile(reader io.ReaderAt, path string) ([]byte, error) {
}
// WriteFile writes a file to a ProDOS volume from a byte array
func WriteFile(readerWriter ReaderWriterAt, path string, fileType int, auxType int, createdTime time.Time, modifiedTime time.Time, buffer []byte) error {
func WriteFile(readerWriter ReaderWriterAt, path string, fileType uint8, auxType uint16, createdTime time.Time, modifiedTime time.Time, buffer []byte) error {
directory, fileName := GetDirectoryAndFileNameFromPath(path)
if len(fileName) > 15 {
@ -56,7 +56,7 @@ func WriteFile(readerWriter ReaderWriterAt, path string, fileType int, auxType i
}
// get list of blocks to write file to
blockList, err := createBlockList(readerWriter, len(buffer))
blockList, err := createBlockList(readerWriter, uint32(len(buffer)))
if err != nil {
return err
}
@ -88,11 +88,11 @@ func WriteFile(readerWriter ReaderWriterAt, path string, fileType int, auxType i
return err
}
fileEntry.FileName = fileName
fileEntry.BlocksUsed = len(blockList)
fileEntry.BlocksUsed = uint16(len(blockList))
fileEntry.CreationTime = createdTime
fileEntry.ModifiedTime = modifiedTime
fileEntry.AuxType = auxType
fileEntry.EndOfFile = len(buffer)
fileEntry.EndOfFile = uint32(len(buffer))
fileEntry.FileType = fileType
fileEntry.KeyPointer = blockList[0]
fileEntry.Version = 0x24
@ -106,8 +106,10 @@ func WriteFile(readerWriter ReaderWriterAt, path string, fileType int, auxType i
fileEntry.StorageType = StorageTree
}
writeFileEntry(readerWriter, fileEntry)
err = writeFileEntry(readerWriter, fileEntry)
if err != nil {
return err
}
return incrementFileCount(readerWriter, fileEntry)
}
@ -168,7 +170,10 @@ func DeleteFile(readerWriter ReaderWriterAt, path string) error {
// zero out directory entry
fileEntry.StorageType = 0
fileEntry.FileName = ""
writeFileEntry(readerWriter, fileEntry)
err = writeFileEntry(readerWriter, fileEntry)
if err != nil {
return err
}
return nil
}
@ -197,24 +202,24 @@ func GetDirectoryAndFileNameFromPath(path string) (string, string) {
return directory, fileName
}
func updateVolumeBitmap(readerWriter ReaderWriterAt, blockList []int) error {
func updateVolumeBitmap(readerWriter ReaderWriterAt, blockList []uint16) error {
volumeBitmap, err := ReadVolumeBitmap(readerWriter)
if err != nil {
fmt.Printf("%s", err)
return err
}
for i := 0; i < len(blockList); i++ {
for i := uint16(0); i < uint16(len(blockList)); i++ {
markBlockInVolumeBitmap(volumeBitmap, blockList[i])
}
return writeVolumeBitmap(readerWriter, volumeBitmap)
}
func writeSeedlingFile(writer io.WriterAt, buffer []byte, blockList []int) {
func writeSeedlingFile(writer io.WriterAt, buffer []byte, blockList []uint16) {
WriteBlock(writer, blockList[0], buffer)
}
func writeSaplingFile(writer io.WriterAt, buffer []byte, blockList []int) {
func writeSaplingFile(writer io.WriterAt, buffer []byte, blockList []uint16) {
// write index block with pointers to data blocks
indexBuffer := make([]byte, 512)
for i := 0; i < 256; i++ {
@ -249,7 +254,7 @@ func writeSaplingFile(writer io.WriterAt, buffer []byte, blockList []int) {
}
}
func writeTreeFile(writer io.WriterAt, buffer []byte, blockList []int) {
func writeTreeFile(writer io.WriterAt, buffer []byte, blockList []uint16) {
// write master index block with pointers to index blocks
indexBuffer := make([]byte, 512)
numberOfIndexBlocks := len(blockList)/256 + 1
@ -303,17 +308,17 @@ func writeTreeFile(writer io.WriterAt, buffer []byte, blockList []int) {
}
}
func getDataBlocklist(reader io.ReaderAt, fileEntry FileEntry) ([]int, error) {
func getDataBlocklist(reader io.ReaderAt, fileEntry FileEntry) ([]uint16, error) {
return getBlocklist(reader, fileEntry, true)
}
func getAllBlockList(reader io.ReaderAt, fileEntry FileEntry) ([]int, error) {
func getAllBlockList(reader io.ReaderAt, fileEntry FileEntry) ([]uint16, error) {
return getBlocklist(reader, fileEntry, false)
}
// Returns all blocks, including index blocks
func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]int, error) {
blocks := make([]int, fileEntry.BlocksUsed)
func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]uint16, error) {
blocks := make([]uint16, fileEntry.BlocksUsed)
switch fileEntry.StorageType {
case StorageSeedling:
@ -324,10 +329,10 @@ func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]int
if err != nil {
return nil, err
}
blockOffset := 1
blockOffset := uint16(1)
blocks[0] = fileEntry.KeyPointer
for i := 0; i < fileEntry.BlocksUsed-1; i++ {
blocks[i+blockOffset] = int(index[i]) + int(index[i+256])*256
for i := uint16(0); i < fileEntry.BlocksUsed-1; i++ {
blocks[i+blockOffset] = uint16(index[i]) + uint16(index[i+256])*256
}
if dataOnly {
return blocks[1:], nil
@ -335,10 +340,10 @@ func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]int
return blocks, nil
case StorageTree:
// this is actually too large
dataBlocks := make([]int, fileEntry.BlocksUsed)
dataBlocks := make([]uint16, fileEntry.BlocksUsed)
// this is also actually too large
numberOfIndexBlocks := fileEntry.BlocksUsed/256 + 2
indexBlocks := make([]int, numberOfIndexBlocks)
indexBlocks := make([]uint16, numberOfIndexBlocks)
masterIndex, err := ReadBlock(reader, fileEntry.KeyPointer)
if err != nil {
return nil, err
@ -348,8 +353,8 @@ func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]int
indexBlocks[0] = fileEntry.KeyPointer
indexBlockCount := 1
for i := 0; i < 128; i++ {
indexBlock := int(masterIndex[i]) + int(masterIndex[i+256])*256
for i := uint16(0); i < 128; i++ {
indexBlock := uint16(masterIndex[i]) + uint16(masterIndex[i+256])*256
if indexBlock == 0 {
break
}
@ -359,12 +364,12 @@ func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]int
if err != nil {
return nil, err
}
for j := 0; j < 256 && i*256+j < fileEntry.BlocksUsed; j++ {
if (int(index[j]) + int(index[j+256])*256) == 0 {
for j := uint16(0); j < 256 && i*256+j < fileEntry.BlocksUsed; j++ {
if (uint16(index[j]) + uint16(index[j+256])*256) == 0 {
break
}
numberOfDataBlocks++
dataBlocks[i*256+j] = int(index[j]) + int(index[j+256])*256
dataBlocks[i*256+j] = uint16(index[j]) + uint16(index[j+256])*256
}
}
@ -379,8 +384,8 @@ func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]int
return nil, errors.New("unsupported file storage type")
}
func createBlockList(reader io.ReaderAt, fileSize int) ([]int, error) {
numberOfBlocks := fileSize / 512
func createBlockList(reader io.ReaderAt, fileSize uint32) ([]uint16, error) {
numberOfBlocks := uint16(fileSize / 512)
if fileSize%512 > 0 {
numberOfBlocks++
@ -422,7 +427,7 @@ func GetFileEntry(reader io.ReaderAt, path string) (FileEntry, error) {
return FileEntry{}, err
}
if fileEntries == nil || len(fileEntries) == 0 {
if len(fileEntries) == 0 {
return FileEntry{}, errors.New("file entry not found")
}

View File

@ -1,4 +1,4 @@
// Copyright Terence J. Boldt (c)2021-2023
// Copyright Terence J. Boldt (c)2021-2024
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
@ -11,8 +11,8 @@ import (
func TestCreateBlocklist(t *testing.T) {
var tests = []struct {
fileSize int
wantBlocks int
fileSize uint32
wantBlocks uint16
}{
{1, 1},
{512, 1},
@ -33,32 +33,9 @@ func TestCreateBlocklist(t *testing.T) {
if err != nil {
t.Error("got error, want nil")
}
if len(blockList) != tt.wantBlocks {
if uint16(len(blockList)) != tt.wantBlocks {
t.Errorf("got %d blocks, want %d", len(blockList), tt.wantBlocks)
}
})
}
}
func TestUpdateVolumeBitmap(t *testing.T) {
blockList := []int{10, 11, 12, 100, 120}
virtualDisk := NewMemoryFile(0x2000000)
CreateVolume(virtualDisk, "VIRTUAL.DISK", 0xFFFE)
updateVolumeBitmap(virtualDisk, blockList)
for _, tt := range blockList {
testname := fmt.Sprintf("%d", tt)
t.Run(testname, func(t *testing.T) {
volumeBitmap, err := ReadVolumeBitmap(virtualDisk)
if err != nil {
t.Error("got error, want nil")
}
free := checkFreeBlockInVolumeBitmap(volumeBitmap, tt)
if free {
t.Errorf("got true, want false")
}
})
}
}

View File

@ -1,4 +1,4 @@
// Copyright Terence J. Boldt (c)2021-2023
// Copyright Terence J. Boldt (c)2021-2024
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
@ -14,7 +14,7 @@ import (
// CreateVolume formats a new ProDOS volume including boot block,
// volume bitmap and empty directory
func CreateVolume(readerWriter ReaderWriterAt, volumeName string, numberOfBlocks int) {
func CreateVolume(readerWriter ReaderWriterAt, volumeName string, numberOfBlocks uint16) {
if numberOfBlocks > 65535 || numberOfBlocks < 64 {
return
}
@ -26,7 +26,7 @@ func CreateVolume(readerWriter ReaderWriterAt, volumeName string, numberOfBlocks
}
blankBlock := make([]byte, 512)
for i := 0; i < numberOfBlocks; i++ {
for i := uint16(0); i < numberOfBlocks; i++ {
WriteBlock(readerWriter, i, blankBlock)
}

View File

@ -1,4 +1,4 @@
// Copyright Terence J. Boldt (c)2021-2023
// Copyright Terence J. Boldt (c)2021-2024
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
@ -13,9 +13,9 @@ import (
func TestCreateVolume(t *testing.T) {
var tests = []struct {
blocks int
blocks uint16
wantVolumeName string
wantFreeBlocks int
wantFreeBlocks uint16
}{
{65535, "MAX", 65513},
{65500, "ALMOST.MAX", 65478},

View File

@ -1,4 +1,4 @@
// Copyright Terence J. Boldt (c)2022-2023
// Copyright Terence J. Boldt (c)2022-2024
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
@ -12,6 +12,8 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
)
@ -26,6 +28,9 @@ func AddFilesFromHostDirectory(
) error {
path, err := makeFullPath(path, readerWriter)
if err != nil {
return err
}
if !strings.HasSuffix(path, "/") {
path = path + "/"
@ -75,8 +80,8 @@ func AddFilesFromHostDirectory(
func WriteFileFromFile(
readerWriter ReaderWriterAt,
pathName string,
fileType int,
auxType int,
fileType uint8,
auxType uint16,
modifiedTime time.Time,
inFileName string,
ignoreDuplicates bool,
@ -117,6 +122,11 @@ func WriteFileFromFile(
case ".SYS", ".TXT", ".BAS", ".BIN":
pathName = strings.TrimSuffix(pathName, ext)
}
match, err := regexp.MatchString("^\\.(BIN|SYS|TXT|BAS)\\$[0-9]{4}", ext)
if err == nil && match {
pathName = strings.TrimSuffix(pathName, ext)
}
}
}
@ -140,9 +150,12 @@ func WriteFileFromFile(
return WriteFile(readerWriter, pathName, fileType, auxType, time.Now(), modifiedTime, inFile)
}
func convertFileByType(inFileName string, inFile []byte) (int, int, []byte, error) {
fileType := 0x06 // default to BIN
auxType := 0x2000 // default to $2000
func convertFileByType(inFileName string, inFile []byte) (uint16, uint8, []byte, error) {
var auxType uint16
var fileType uint8
fileType = 0x06 // default to BIN
auxType = 0x2000 // default to $2000
var err error
@ -166,36 +179,57 @@ func convertFileByType(inFileName string, inFile []byte) (int, int, []byte, erro
// Length
binary.BigEndian.Uint32(inFile[0x2E:]) == 0x00000008 {
fileType = int(binary.BigEndian.Uint16(inFile[0x34:]))
auxType = int(binary.BigEndian.Uint32(inFile[0x36:]))
fileType = uint8(binary.BigEndian.Uint16(inFile[0x34:]))
auxType = uint16(binary.BigEndian.Uint32(inFile[0x36:]))
inFile = inFile[0x3A:]
} else {
// use extension to determine file type
ext := strings.ToUpper(filepath.Ext(inFileName))
switch ext {
case ".BAS":
inFile, err = ConvertTextToBasic(string(inFile))
fileType = 0xFC
auxType = 0x0801
match, err := regexp.MatchString("^\\.(BIN|SYS|TXT|BAS)\\$[0-9]{4}", ext)
if err == nil && match {
parts := strings.Split(ext, "$")
extAuxType, err := strconv.ParseUint(parts[1], 16, 16)
if err != nil {
return 0, 0, nil, err
}
case ".SYS":
fileType = 0xFF
auxType = 0x2000
case ".BIN":
fileType = 0x06
auxType = 0x2000
case ".TXT":
inFile = []byte(strings.ReplaceAll(strings.ReplaceAll(string(inFile), "\r\n", "r"), "\n", "\r"))
fileType = 0x04
auxType = 0x0000
case ".JPG", ".PNG":
inFile = ConvertImageToHiResMonochrome(inFile)
fileType = 0x06
auxType = 0x2000
auxType = uint16(extAuxType)
switch parts[0] {
case ".BAS":
fileType = 0xFC
case ".SYS":
fileType = 0xFF
case ".BIN":
fileType = 0x06
case ".TXT":
fileType = 0x04
}
} else {
switch ext {
case ".BAS":
inFile, err = ConvertTextToBasic(string(inFile))
fileType = 0xFC
auxType = 0x0801
if err != nil {
return 0, 0, nil, err
}
case ".SYS":
fileType = 0xFF
auxType = 0x2000
case ".BIN":
fileType = 0x06
auxType = 0x2000
case ".TXT":
inFile = []byte(strings.ReplaceAll(strings.ReplaceAll(string(inFile), "\r\n", "r"), "\n", "\r"))
fileType = 0x04
auxType = 0x0000
case ".JPG", ".PNG":
inFile = ConvertImageToHiResMonochrome(inFile)
fileType = 0x06
auxType = 0x2000
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright Terence J. Boldt (c)2021-2023
// Copyright Terence J. Boldt (c)2021-2024
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.

View File

@ -1,4 +1,4 @@
// Copyright Terence J. Boldt (c)2021-2023
// Copyright Terence J. Boldt (c)2021-2024
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.

View File

@ -1,4 +1,4 @@
// Copyright Terence J. Boldt (c)2021-2023
// Copyright Terence J. Boldt (c)2021-2024
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
@ -25,7 +25,7 @@ func TimeToString(printTime time.Time) string {
}
// FileTypeToString display the file type as a string
func FileTypeToString(fileType int) string {
func FileTypeToString(fileType uint8) string {
switch fileType {
case 1:
return "BAD"
@ -159,7 +159,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 uint16, totalBlocks uint16, path string, fileEntries []FileEntry) {
fmt.Printf("%s\n\n", path)
fmt.Printf("NAME TYPE BLOCKS MODIFIED CREATED ENDFILE SUBTYPE\n\n")

View File

@ -1,4 +1,4 @@
// Copyright Terence J. Boldt (c)2021-2023
// Copyright Terence J. Boldt (c)2021-2024
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.

View File

@ -1,4 +1,4 @@
// Copyright Terence J. Boldt (c)2021-2023
// Copyright Terence J. Boldt (c)2021-2024
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.

View File

@ -14,7 +14,7 @@ func printReadme() {
fmt.Println(`
MIT License
Copyright (c)2021-2023 Terence Boldt
Copyright (c)2021-2024 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