Compare commits

...

15 Commits
v0.4.3 ... main

Author SHA1 Message Date
Terence Boldt 0aa838ffe1 Update version 2024-01-14 08:58:26 -05:00
Terence Boldt 231197bd8f Update dependencies 2024-01-14 08:58:26 -05:00
Chris Pacejo 2477b4a8a3 open files O_RDONLY for read-only operations 2024-01-14 08:46:44 -05:00
Terence Boldt 30f0b03054 Update dependencies 2023-11-07 23:03:44 -05:00
Terence Boldt ef2b505500
Update dependencies (#31)
* Update dependencies

* Update to 0.4.7
2023-07-06 19:57:02 -04:00
Terence Boldt 7f8679cdb4
Update dependencies (#30) 2023-02-22 21:09:20 -05:00
Terence Boldt 98ab2d4f0a
Fixes #27 basic parser (#28)
* Fix #27 basic parsing

* Update version
2023-01-25 21:25:48 -05:00
Terence Boldt 9e6c2fcc22
Update README.md 2023-01-22 20:12:34 -05:00
Terence Boldt b80a835c72 Update version to 0.4.4 2023-01-22 20:08:27 -05:00
Terence Boldt 10f896399d
Fix codacy warnings in image (#24) 2023-01-22 12:25:11 -05:00
Terence Boldt 36c24e60ca Ignore duplicate files when recursive 2023-01-22 12:06:43 -05:00
Terence Boldt 286964d3ee Add putallrecursive 2023-01-22 12:06:43 -05:00
Terence Boldt 1722e7b23a Refactor main 2023-01-22 12:06:43 -05:00
Terence Boldt 876564d261 Add directory creation 2023-01-22 12:06:43 -05:00
Terence Boldt ee3d187fb3 Improve error handling and fix putall command 2023-01-22 12:06:43 -05:00
13 changed files with 626 additions and 191 deletions

View File

@ -8,7 +8,7 @@ Being a work in progress, be warned that this code is likely to corrupt drive im
There are binaries [here](https://github.com/tjboldt/ProDOS-Utilities/releases/latest)
## Current TODO list
1. Create/Delete directories
1. Delete directories
2. Add file/directory tests
3. Add rename
4. Add in-place file/directory moves

2
go.mod
View File

@ -2,4 +2,4 @@ module github.com/tjboldt/ProDOS-Utilities
go 1.16
require golang.org/x/image v0.3.0
require golang.org/x/image v0.15.0

13
go.sum
View File

@ -1,26 +1,33 @@
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/image v0.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg=
golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

331
main.go
View File

@ -17,7 +17,7 @@ import (
"github.com/tjboldt/ProDOS-Utilities/prodos"
)
const version = "0.4.3"
const version = "0.4.9"
func main() {
var fileName string
@ -32,7 +32,7 @@ func main() {
var auxType int
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")
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)")
@ -50,136 +50,225 @@ func main() {
switch command {
case "ls":
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
defer file.Close()
pathName = strings.ToUpper(pathName)
volumeHeader, _, fileEntries, err := prodos.ReadDirectory(file, pathName)
if err != nil {
fmt.Printf("Error: %s", err)
}
if len(pathName) == 0 {
pathName = "/" + volumeHeader.VolumeName
}
volumeBitmap, err := prodos.ReadVolumeBitmap(file)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
freeBlocks := prodos.GetFreeBlockCount(volumeBitmap, volumeHeader.TotalBlocks)
prodos.DumpDirectory(freeBlocks, volumeHeader.TotalBlocks, pathName, fileEntries)
ls(fileName, pathName)
case "get":
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
defer file.Close()
if len(pathName) == 0 {
fmt.Println("Missing pathname")
os.Exit(1)
}
getFile, err := prodos.LoadFile(file, pathName)
if err != nil {
fmt.Printf("Failed to read file %s: %s\n", pathName, err)
os.Exit(1)
}
if len(outFileName) == 0 {
_, outFileName = prodos.GetDirectoryAndFileNameFromPath(pathName)
}
outFile, err := os.Create(outFileName)
if err != nil {
fmt.Printf("Failed to create output file %s: %s\n", outFileName, err)
os.Exit(1)
}
if strings.HasSuffix(strings.ToLower(outFileName), ".bas") {
fmt.Fprintf(outFile, prodos.ConvertBasicToText(getFile))
} else {
outFile.Write(getFile)
}
get(fileName, pathName, outFileName)
case "put":
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
defer file.Close()
err = prodos.WriteFileFromFile(file, pathName, fileType, auxType, inFileName)
if err != nil {
fmt.Printf("Failed to write file %s", err)
}
put(fileName, pathName, fileType, auxType, inFileName)
case "readblock":
fmt.Printf("Reading block 0x%04X (%d):\n\n", blockNumber, blockNumber)
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
defer file.Close()
block, err := prodos.ReadBlock(file, blockNumber)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
prodos.DumpBlock(block)
readBlock(blockNumber, fileName)
case "writeblock":
fmt.Printf("Writing block 0x%04X (%d):\n\n", blockNumber, blockNumber)
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
defer file.Close()
inFile, err := os.ReadFile(inFileName)
if err != nil {
fmt.Printf("Failed to open input file %s: %s", inFileName, err)
os.Exit(1)
}
prodos.WriteBlock(file, blockNumber, inFile)
writeBlock(blockNumber, fileName, inFileName)
case "create":
file, err := os.Create(fileName)
if err != nil {
fmt.Printf("failed to create file: %s\n", err)
return
}
defer file.Close()
prodos.CreateVolume(file, volumeName, volumeSize)
create(fileName, volumeName, volumeSize)
case "putall":
file, err := os.Create(fileName)
if err != nil {
fmt.Printf("failed to create file: %s\n", err)
return
}
defer file.Close()
err = prodos.AddFilesFromHostDirectory(file, inFileName)
if err != nil {
fmt.Printf("failed to add host files: %s\n", err)
return
}
putall(fileName, inFileName, pathName, false)
case "putallrecursive":
putall(fileName, inFileName, pathName, true)
case "rm":
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
defer file.Close()
prodos.DeleteFile(file, pathName)
rm(fileName, pathName)
case "mkdir":
mkdir(fileName, pathName)
case "dumpfile":
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
defer file.Close()
fileEntry, err := prodos.GetFileEntry(file, pathName)
prodos.DumpFileEntry(fileEntry)
dumpFile(fileName, pathName)
case "dumpdirectory":
dumpDirectory(fileName, pathName)
default:
fmt.Printf("Invalid command: %s\n\n", command)
flag.PrintDefaults()
os.Exit(1)
}
}
func dumpFile(fileName string, pathName string) {
checkPathName(pathName)
file, err := os.OpenFile(fileName, os.O_RDONLY, 0755)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
defer file.Close()
fileEntry, err := prodos.GetFileEntry(file, pathName)
prodos.DumpFileEntry(fileEntry)
}
func dumpDirectory(fileName string, pathName string) {
checkPathName(pathName)
file, err := os.OpenFile(fileName, os.O_RDONLY, 0755)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
defer file.Close()
_, directoryheader, _, err := prodos.ReadDirectory(file, pathName)
prodos.DumpDirectoryHeader(directoryheader)
}
func mkdir(fileName string, pathName string) {
checkPathName(pathName)
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
defer file.Close()
err = prodos.CreateDirectory(file, pathName)
if err != nil {
fmt.Printf("failed to create directory %s: %s\n", pathName, err)
os.Exit(1)
}
}
func rm(fileName string, pathName string) {
checkPathName(pathName)
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
defer file.Close()
prodos.DeleteFile(file, pathName)
}
func putall(fileName string, inFileName string, pathName string, recursive bool) {
if len(inFileName) == 0 {
inFileName = "."
}
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
if err != nil {
fmt.Printf("failed to create file: %s\n", err)
os.Exit(1)
}
defer file.Close()
err = prodos.AddFilesFromHostDirectory(file, inFileName, pathName, recursive)
if err != nil {
fmt.Printf("failed to add host files: %s\n", err)
os.Exit(1)
}
}
func create(fileName string, volumeName string, volumeSize int) {
file, err := os.Create(fileName)
if err != nil {
fmt.Printf("failed to create file: %s\n", err)
os.Exit(1)
}
defer file.Close()
prodos.CreateVolume(file, volumeName, volumeSize)
}
func writeBlock(blockNumber int, 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)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
defer file.Close()
inFile, err := os.ReadFile(inFileName)
if err != nil {
fmt.Printf("Failed to open input file %s: %s", inFileName, err)
os.Exit(1)
}
prodos.WriteBlock(file, blockNumber, inFile)
}
func readBlock(blockNumber int, 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 {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
defer file.Close()
block, err := prodos.ReadBlock(file, blockNumber)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
prodos.DumpBlock(block)
}
func put(fileName string, pathName string, fileType int, auxType int, inFileName string) {
checkPathName(pathName)
checkInFileName(inFileName)
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
defer file.Close()
fileInfo, err := os.Stat(fileName)
err = prodos.WriteFileFromFile(file, pathName, fileType, auxType, fileInfo.ModTime(), inFileName, false)
if err != nil {
fmt.Printf("Failed to write file %s", err)
}
}
func get(fileName string, pathName string, outFileName string) {
checkPathName(pathName)
file, err := os.OpenFile(fileName, os.O_RDONLY, 0755)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
defer file.Close()
getFile, err := prodos.LoadFile(file, pathName)
if err != nil {
fmt.Printf("Failed to read file %s: %s\n", pathName, err)
os.Exit(1)
}
if len(outFileName) == 0 {
_, outFileName = prodos.GetDirectoryAndFileNameFromPath(pathName)
}
outFile, err := os.Create(outFileName)
if err != nil {
fmt.Printf("Failed to create output file %s: %s\n", outFileName, err)
os.Exit(1)
}
if strings.HasSuffix(strings.ToLower(outFileName), ".bas") {
fmt.Fprintf(outFile, prodos.ConvertBasicToText(getFile))
} else {
outFile.Write(getFile)
}
}
func ls(fileName string, pathName string) {
file, err := os.OpenFile(fileName, os.O_RDONLY, 0755)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
defer file.Close()
pathName = strings.ToUpper(pathName)
volumeHeader, _, fileEntries, err := prodos.ReadDirectory(file, pathName)
if err != nil {
fmt.Printf("Error: %s", err)
}
if len(pathName) == 0 {
pathName = "/" + volumeHeader.VolumeName
}
volumeBitmap, err := prodos.ReadVolumeBitmap(file)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
freeBlocks := prodos.GetFreeBlockCount(volumeBitmap, volumeHeader.TotalBlocks)
prodos.DumpDirectory(freeBlocks, volumeHeader.TotalBlocks, pathName, fileEntries)
}
func checkPathName(pathName string) {
if len(pathName) == 0 {
fmt.Printf("Missing path name (use -p PATHNAME)\n")
os.Exit(1)
}
}
func checkInFileName(inFileName string) {
if len(inFileName) == 0 {
fmt.Printf("Missing input file name (use -i FILENAME)\n")
os.Exit(1)
}
}

View File

@ -190,6 +190,9 @@ func ConvertTextToBasic(text string) ([]byte, error) {
// skip initial whitespace and look for the start of a line number
if starting {
if c == '\n' { // skip blank lines
continue
}
if c == ' ' {
continue
}
@ -230,6 +233,7 @@ func ConvertTextToBasic(text string) ([]byte, error) {
parsingRem = false
parsingString = false
foundToken = false
skipChars = 0
currentByte += basicLine.Len()
currentByte += 3
// write address of next line

View File

@ -131,7 +131,6 @@ func createVolumeBitmap(numberOfBlocks int) []byte {
markBlockInVolumeBitmap(volumeBitmap, i)
}
}
//DumpBlock(volumeBitmap)
return volumeBitmap
}

View File

@ -8,6 +8,8 @@
package prodos
import (
"errors"
"fmt"
"io"
)
@ -16,6 +18,10 @@ func ReadBlock(reader io.ReaderAt, block int) ([]byte, error) {
buffer := make([]byte, 512)
_, err := reader.ReadAt(buffer, int64(block)*512)
if err != nil {
errString := fmt.Sprintf("failed to read block %04X: %s", block, err.Error())
err = errors.New(errString)
}
return buffer, err
}
@ -23,5 +29,10 @@ 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 {
_, err := writer.WriteAt(buffer, int64(block)*512)
if err != nil {
errString := fmt.Sprintf("failed to write block %04X: %s", block, err.Error())
err = errors.New(errString)
}
return err
}

View File

@ -31,11 +31,21 @@ type VolumeHeader struct {
// DirectoryHeader from ProDOS
type DirectoryHeader struct {
Name string
ActiveFileCount int
StartingBlock int
PreviousBlock int
NextBlock int
PreviousBlock int
NextBlock int
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
}
const (
@ -55,19 +65,20 @@ const (
// FileEntry from ProDOS
type FileEntry struct {
StorageType int
FileName string
FileType int
CreationTime time.Time
KeyPointer int
Version int
MinVersion int
BlocksUsed int
EndOfFile int
Access int
AuxType int
ModifiedTime time.Time
HeaderPointer int
StorageType int
FileName string
FileType int
CreationTime time.Time
KeyPointer int
Version int
MinVersion int
BlocksUsed int
EndOfFile int
Access int
AuxType int
ModifiedTime time.Time
HeaderPointer int
DirectoryBlock int
DirectoryOffset int
}
@ -86,6 +97,11 @@ func ReadDirectory(reader io.ReaderAt, path string) (VolumeHeader, DirectoryHead
path = fmt.Sprintf("/%s", volumeHeader.VolumeName)
}
// add volume name if not full path
if !strings.HasPrefix(path, "/") {
path = fmt.Sprintf("/%s/%s", volumeHeader.VolumeName, path)
}
path = strings.ToUpper(path)
paths := strings.Split(path, "/")
@ -97,14 +113,109 @@ func ReadDirectory(reader io.ReaderAt, path string) (VolumeHeader, DirectoryHead
return volumeHeader, directoryHeader, fileEntries, nil
}
func getFreeFileEntryInDirectory(reader io.ReaderAt, directory string) (FileEntry, error) {
_, directoryHeader, _, err := ReadDirectory(reader, directory)
// CreateDirectory creates a directory information of a specified path
// on a ProDOS image
func CreateDirectory(readerWriter ReaderWriterAt, path string) error {
if len(path) == 0 {
return errors.New("cannot create directory with path")
}
// add volume name if not full path
path, err := makeFullPath(path, readerWriter)
if err != nil {
return err
}
parentPath, newDirectory := GetDirectoryAndFileNameFromPath(path)
existingFileEntry, _ := GetFileEntry(readerWriter, path)
if existingFileEntry.StorageType != StorageDeleted {
return errors.New("directory already exists")
}
fileEntry, err := getFreeFileEntryInDirectory(readerWriter, parentPath)
if err != nil {
errString := fmt.Sprintf("failed to create directory: %s", err)
return errors.New(errString)
}
// get list of blocks to write file to
blockList, err := createBlockList(readerWriter, 512)
if err != nil {
errString := fmt.Sprintf("failed to create directory: %s", err)
return errors.New(errString)
}
updateVolumeBitmap(readerWriter, blockList)
fileEntry.FileName = newDirectory
fileEntry.BlocksUsed = 1
fileEntry.CreationTime = time.Now()
fileEntry.ModifiedTime = time.Now()
fileEntry.AuxType = 0
fileEntry.EndOfFile = 0x200
fileEntry.FileType = 0x0F
fileEntry.KeyPointer = blockList[0]
fileEntry.Access = 0b11100011
fileEntry.StorageType = StorageDirectory
fileEntry.Version = 0x24
fileEntry.MinVersion = 0x00
writeFileEntry(readerWriter, fileEntry)
err = incrementFileCount(readerWriter, fileEntry)
if err != nil {
errString := fmt.Sprintf("failed to create directory: %s", err)
return errors.New(errString)
}
directoryEntry := DirectoryHeader{
PreviousBlock: 0,
NextBlock: 0,
IsSubDirectory: true,
Name: newDirectory,
CreationTime: time.Now(),
Version: 0x24,
MinVersion: 0,
Access: 0xE3,
EntryLength: 0x27,
EntriesPerBlock: 0x0D,
ActiveFileCount: 0,
StartingBlock: blockList[0],
ParentBlock: fileEntry.DirectoryBlock,
ParentEntry: (fileEntry.DirectoryOffset - 0x04) / 0x27,
ParentEntryLength: 0x27,
}
err = writeDirectoryHeader(readerWriter, directoryEntry)
if err != nil {
errString := fmt.Sprintf("failed to create directory: %s", err)
return errors.New(errString)
}
return nil
}
func makeFullPath(path string, reader io.ReaderAt) (string, error) {
if !strings.HasPrefix(path, "/") {
buffer, err := ReadBlock(reader, 0x0002)
if err != nil {
return "", err
}
volumeHeader := parseVolumeHeader(buffer)
path = fmt.Sprintf("/%s/%s", volumeHeader.VolumeName, path)
}
return path, nil
}
func getFreeFileEntryInDirectory(readerWriter ReaderWriterAt, directory string) (FileEntry, error) {
_, directoryHeader, _, err := ReadDirectory(readerWriter, directory)
if err != nil {
return FileEntry{}, err
}
//DumpDirectoryHeader(directoryHeader)
blockNumber := directoryHeader.StartingBlock
buffer, err := ReadBlock(reader, blockNumber)
buffer, err := ReadBlock(readerWriter, blockNumber)
if err != nil {
return FileEntry{}, err
}
@ -113,23 +224,31 @@ func getFreeFileEntryInDirectory(reader io.ReaderAt, directory string) (FileEntr
for {
if entryNumber > 13 {
blockNumber = int(buffer[2]) + int(buffer[3])*256
// if we ran out of blocks in the directory, return empty
// TODO: expand the directory to add more entries
if blockNumber == 0 {
return FileEntry{}, errors.New("no free file entries found")
nextBlockNumber := int(buffer[2]) + int(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)
if err != nil {
return FileEntry{}, err
}
}
blockNumber = nextBlockNumber
// else read the next block in the directory
buffer, err = ReadBlock(reader, blockNumber)
buffer, err = ReadBlock(readerWriter, blockNumber)
if err != nil {
return FileEntry{}, nil
}
entryOffset = 4
entryNumber = 1
}
fileEntry := parseFileEntry(buffer[entryOffset:entryOffset+40], blockNumber, entryOffset)
fileEntry := parseFileEntry(buffer[entryOffset:entryOffset+0x28], blockNumber, entryOffset)
if fileEntry.StorageType == StorageDeleted {
fileEntry = FileEntry{}
fileEntry.DirectoryBlock = blockNumber
fileEntry.DirectoryOffset = entryOffset
fileEntry.HeaderPointer = directoryHeader.StartingBlock
@ -141,6 +260,51 @@ func getFreeFileEntryInDirectory(reader io.ReaderAt, directory string) (FileEntr
}
}
func expandDirectory(readerWriter ReaderWriterAt, nextBlockNumber int, buffer []byte, blockNumber int, directoryHeader DirectoryHeader) (int, error) {
volumeBitMap, err := ReadVolumeBitmap(readerWriter)
if err != nil {
errString := fmt.Sprintf("failed to get volume bitmap to expand directory: %s", err)
return 0, errors.New(errString)
}
blockList := findFreeBlocks(volumeBitMap, 1)
if len(blockList) != 1 {
return 0, errors.New("failed to get free block to expand directory")
}
nextBlockNumber = blockList[0]
buffer[0x02] = byte(nextBlockNumber & 0x00FF)
buffer[0x03] = byte(nextBlockNumber >> 8)
WriteBlock(readerWriter, blockNumber, buffer)
if err != nil {
errString := fmt.Sprintf("failed to write block to expand directory: %s", err)
return 0, errors.New(errString)
}
buffer = make([]byte, 0x200)
buffer[0x00] = byte(blockNumber & 0x00FF)
buffer[0x01] = byte(blockNumber >> 8)
err = WriteBlock(readerWriter, nextBlockNumber, buffer)
if err != nil {
errString := fmt.Sprintf("failed to write new block to expand directory: %s", err)
return 0, errors.New(errString)
}
updateVolumeBitmap(readerWriter, blockList)
buffer, err = ReadBlock(readerWriter, directoryHeader.ParentBlock)
if err != nil {
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)
directoryFileEntry.BlocksUsed++
directoryFileEntry.EndOfFile += 0x200
writeFileEntry(readerWriter, directoryFileEntry)
return nextBlockNumber, nil
}
func getFileEntriesInDirectory(reader io.ReaderAt, blockNumber int, currentPath int, paths []string) (DirectoryHeader, []FileEntry, error) {
buffer, err := ReadBlock(reader, blockNumber)
if err != nil {
@ -255,13 +419,14 @@ func writeFileEntry(writer io.WriterAt, fileEntry FileEntry) {
buffer[0x1E] = byte(fileEntry.Access)
buffer[0x1F] = byte(fileEntry.AuxType & 0x00FF)
buffer[0x20] = byte(fileEntry.AuxType >> 8)
modifiedTime := DateTimeToProDOS(fileEntry.CreationTime)
modifiedTime := DateTimeToProDOS(fileEntry.ModifiedTime)
for i := 0; i < 4; i++ {
buffer[0x21+i] = modifiedTime[i]
}
buffer[0x25] = byte(fileEntry.HeaderPointer & 0x00FF)
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))
if err != nil {
@ -303,22 +468,43 @@ func parseVolumeHeader(buffer []byte) VolumeHeader {
func parseDirectoryHeader(buffer []byte, blockNumber int) DirectoryHeader {
previousBlock := int(buffer[0x00]) + int(buffer[0x01])*256
nextBlock := int(buffer[0x02]) + int(buffer[0x03])*256
filenameLength := buffer[0x04] & 15
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])
directoryEntry := DirectoryHeader{
PreviousBlock: previousBlock,
NextBlock: nextBlock,
StartingBlock: blockNumber,
Name: name,
ActiveFileCount: fileCount,
PreviousBlock: previousBlock,
NextBlock: nextBlock,
StartingBlock: blockNumber,
IsSubDirectory: isSubDirectory,
Name: name,
CreationTime: creationTime,
Version: version,
MinVersion: minVersion,
Access: access,
EntryLength: entryLength,
EntriesPerBlock: entriesPerBlock,
ActiveFileCount: fileCount,
ParentBlock: parentBlock,
ParentEntry: parentEntry,
ParentEntryLength: parentEntryLength,
}
return directoryEntry
}
func writeDirectoryHeader(readerWriter ReaderWriterAt, directoryHeader DirectoryHeader) error {
// Reading back the block preserves values including reserved fields
buffer, err := ReadBlock(readerWriter, directoryHeader.StartingBlock)
if err != nil {
return err
@ -327,12 +513,40 @@ func writeDirectoryHeader(readerWriter ReaderWriterAt, directoryHeader Directory
buffer[0x01] = byte(directoryHeader.PreviousBlock >> 8)
buffer[0x02] = byte(directoryHeader.NextBlock & 0x00FF)
buffer[0x03] = byte(directoryHeader.NextBlock >> 8)
if directoryHeader.IsSubDirectory {
buffer[0x04] = 0xE0
} else {
buffer[0x04] = 0xF0
}
buffer[0x04] = buffer[0x04] | byte(len(directoryHeader.Name))
for i := 0; i < len(directoryHeader.Name); i++ {
buffer[0x05+i] = directoryHeader.Name[i]
}
creationTime := DateTimeToProDOS(directoryHeader.CreationTime)
for i := 0; i < 4; i++ {
buffer[0x1C+i] = creationTime[i]
}
// Without these reserved bytes, reading the directory causes I/O ERROR
buffer[0x14] = 0x75
buffer[0x15] = byte(directoryHeader.Version)
buffer[0x16] = byte(directoryHeader.MinVersion)
buffer[0x17] = 0xC3
buffer[0x18] = 0x0D
buffer[0x19] = 0x27
buffer[0x1A] = 0x00
buffer[0x1B] = 0x00
buffer[0x20] = byte(directoryHeader.Version)
buffer[0x21] = byte(directoryHeader.MinVersion)
buffer[0x22] = byte(directoryHeader.Access)
buffer[0x23] = byte(directoryHeader.EntryLength)
buffer[0x24] = byte(directoryHeader.EntriesPerBlock)
buffer[0x25] = byte(directoryHeader.ActiveFileCount & 0x00FF)
buffer[0x26] = byte(directoryHeader.ActiveFileCount >> 8)
buffer[0x27] = byte(directoryHeader.ParentBlock & 0x00FF)
buffer[0x28] = byte(directoryHeader.ParentBlock >> 8)
buffer[0x29] = byte(directoryHeader.ParentEntry)
buffer[0x2A] = byte(directoryHeader.ParentEntryLength)
WriteBlock(readerWriter, directoryHeader.StartingBlock, buffer)
return nil

View File

@ -43,12 +43,16 @@ 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, buffer []byte) error {
func WriteFile(readerWriter ReaderWriterAt, path string, fileType int, auxType int, createdTime time.Time, modifiedTime time.Time, buffer []byte) error {
directory, fileName := GetDirectoryAndFileNameFromPath(path)
if len(fileName) > 15 {
return errors.New("filename too long")
}
existingFileEntry, _ := GetFileEntry(readerWriter, path)
if existingFileEntry.StorageType != StorageDeleted {
DeleteFile(readerWriter, path)
return errors.New(("file already exists"))
}
// get list of blocks to write file to
@ -85,12 +89,14 @@ func WriteFile(readerWriter ReaderWriterAt, path string, fileType int, auxType i
}
fileEntry.FileName = fileName
fileEntry.BlocksUsed = len(blockList)
fileEntry.CreationTime = time.Now()
fileEntry.ModifiedTime = time.Now()
fileEntry.CreationTime = createdTime
fileEntry.ModifiedTime = modifiedTime
fileEntry.AuxType = auxType
fileEntry.EndOfFile = len(buffer)
fileEntry.FileType = fileType
fileEntry.KeyPointer = blockList[0]
fileEntry.Version = 0x24
fileEntry.MinVersion = 0x00
fileEntry.Access = 0b11100011
if len(blockList) == 1 {
fileEntry.StorageType = StorageSeedling
@ -102,7 +108,10 @@ func WriteFile(readerWriter ReaderWriterAt, path string, fileType int, auxType i
writeFileEntry(readerWriter, fileEntry)
// increment file count
return incrementFileCount(readerWriter, fileEntry)
}
func incrementFileCount(readerWriter ReaderWriterAt, fileEntry FileEntry) error {
directoryHeaderBlock, err := ReadBlock(readerWriter, fileEntry.HeaderPointer)
if err != nil {
return err
@ -117,6 +126,10 @@ func WriteFile(readerWriter ReaderWriterAt, path string, fileType int, auxType i
// DeleteFile deletes a file from a ProDOS volume
func DeleteFile(readerWriter ReaderWriterAt, path string) error {
fileEntry, err := GetFileEntry(readerWriter, path)
// DumpFileEntry(fileEntry)
// oldDirectoryBlock, _ := ReadBlock(readerWriter, fileEntry.DirectoryBlock)
// DumpBlock(oldDirectoryBlock)
if err != nil {
return errors.New("file not found")
}
@ -132,6 +145,7 @@ func DeleteFile(readerWriter ReaderWriterAt, path string) error {
if err != nil {
return err
}
volumeBitmap, err := ReadVolumeBitmap(readerWriter)
if err != nil {
return err
@ -159,6 +173,12 @@ func DeleteFile(readerWriter ReaderWriterAt, path string) error {
return nil
}
// FileExists return true if the file exists
func FileExists(reader io.ReaderAt, path string) (bool, error) {
fileEntry, _ := GetFileEntry(reader, path)
return fileEntry.StorageType != StorageDeleted, nil
}
// GetDirectoryAndFileNameFromPath gets the directory and filename from a path
func GetDirectoryAndFileNameFromPath(path string) (string, string) {
path = strings.ToUpper(path)
@ -304,26 +324,27 @@ func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]int
if err != nil {
return nil, err
}
blockOffset := 0
if !dataOnly {
blocks[0] = fileEntry.KeyPointer
blockOffset = 1
}
blockOffset := 1
blocks[0] = fileEntry.KeyPointer
for i := 0; i < fileEntry.BlocksUsed-1; i++ {
blocks[i+blockOffset] = int(index[i]) + int(index[i+256])*256
}
if dataOnly {
return blocks[1:], nil
}
return blocks, nil
case StorageTree:
// this is actually too large
dataBlocks := make([]int, fileEntry.BlocksUsed)
numberOfIndexBlocks := fileEntry.BlocksUsed/256 + 1
if fileEntry.BlocksUsed%256 != 0 {
numberOfIndexBlocks++
}
// this is also actually too large
numberOfIndexBlocks := fileEntry.BlocksUsed/256 + 2
indexBlocks := make([]int, numberOfIndexBlocks)
masterIndex, err := ReadBlock(reader, fileEntry.KeyPointer)
if err != nil {
return nil, err
}
numberOfDataBlocks := 0
indexBlocks[0] = fileEntry.KeyPointer
indexBlockCount := 1
@ -342,6 +363,7 @@ func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]int
if (int(index[j]) + int(index[j+256])*256) == 0 {
break
}
numberOfDataBlocks++
dataBlocks[i*256+j] = int(index[j]) + int(index[j+256])*256
}
}
@ -350,7 +372,7 @@ func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]int
return dataBlocks, nil
}
blocks = append(indexBlocks, dataBlocks...)
blocks = append(indexBlocks[0:numberOfIndexBlocks], dataBlocks[0:numberOfDataBlocks]...)
return blocks, nil
}
@ -389,7 +411,7 @@ func createBlockList(reader io.ReaderAt, fileSize int) ([]int, error) {
blockList := findFreeBlocks(volumeBitmap, numberOfBlocks)
return blockList, nil
return blockList[0:numberOfBlocks], nil
}
// GetFileEntry returns a file entry for the given path

View File

@ -9,7 +9,7 @@ import (
"testing"
)
func TestCreatBlocklist(t *testing.T) {
func TestCreateBlocklist(t *testing.T) {
var tests = []struct {
fileSize int
wantBlocks int

View File

@ -8,16 +8,28 @@ package prodos
import (
"encoding/binary"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"time"
)
// AddFilesFromHostDirectory fills the root volume with files
// from the specified host directory
func AddFilesFromHostDirectory(
readerWriter ReaderWriterAt,
directory string) error {
directory string,
path string,
recursive bool,
) error {
path, err := makeFullPath(path, readerWriter)
if !strings.HasSuffix(path, "/") {
path = path + "/"
}
files, err := os.ReadDir(directory)
if err != nil {
@ -30,8 +42,26 @@ func AddFilesFromHostDirectory(
return err
}
if !file.IsDir() && info.Size() > 0 && info.Size() <= 0x1000000 {
err = WriteFileFromFile(readerWriter, "", 0, 0, filepath.Join(directory, file.Name()))
if file.Name()[0] != '.' && !file.IsDir() && info.Size() > 0 && info.Size() <= 0x1000000 {
err = WriteFileFromFile(readerWriter, path, 0, 0, info.ModTime(), filepath.Join(directory, file.Name()), true)
if err != nil {
return err
}
}
if file.Name()[0] != '.' && recursive && file.IsDir() {
newPath := file.Name()
if len(newPath) > 15 {
newPath = newPath[0:15]
}
newFullPath := strings.ToUpper(path + newPath)
newHostDirectory := filepath.Join(directory, file.Name())
err = CreateDirectory(readerWriter, newFullPath)
if err != nil {
return err
}
err = AddFilesFromHostDirectory(readerWriter, newHostDirectory, newFullPath+"/", recursive)
if err != nil {
return err
}
@ -42,23 +72,46 @@ func AddFilesFromHostDirectory(
}
// WriteFileFromFile writes a file to a ProDOS volume from a host file
func WriteFileFromFile(readerWriter ReaderWriterAt, pathName string, fileType int, auxType int, inFileName string) error {
func WriteFileFromFile(
readerWriter ReaderWriterAt,
pathName string,
fileType int,
auxType int,
modifiedTime time.Time,
inFileName string,
ignoreDuplicates bool,
) error {
inFile, err := os.ReadFile(inFileName)
if err != nil {
return err
errString := fmt.Sprintf("write from file failed: %s", err)
return errors.New(errString)
}
if auxType == 0 && fileType == 0 {
auxType, fileType, inFile, err = convertFileByType(inFileName, inFile)
if err != nil {
return err
errString := fmt.Sprintf("failed to convert file: %s", err)
return errors.New(errString)
}
}
trimExtensions := false
if len(pathName) == 0 {
_, pathName = filepath.Split(inFileName)
pathName = strings.ToUpper(pathName)
trimExtensions = true
}
if strings.HasSuffix(pathName, "/") {
trimExtensions = true
_, fileName := filepath.Split(inFileName)
pathName = strings.ToUpper(pathName + fileName)
}
if trimExtensions {
ext := filepath.Ext(pathName)
if len(ext) > 0 {
switch ext {
case ".SYS", ".TXT", ".BAS", ".BIN":
@ -67,7 +120,24 @@ func WriteFileFromFile(readerWriter ReaderWriterAt, pathName string, fileType in
}
}
return WriteFile(readerWriter, pathName, fileType, auxType, inFile)
paths := strings.SplitAfter(pathName, "/")
if len(paths[len(paths)-1]) > 15 {
paths[len(paths)-1] = paths[len(paths)-1][0:15]
pathName = strings.Join(paths, "")
}
// skip if file already exists and ignoring duplicates
if ignoreDuplicates {
exists, err := FileExists(readerWriter, pathName)
if err != nil {
return err
}
if exists {
return nil
}
}
return WriteFile(readerWriter, pathName, fileType, auxType, time.Now(), modifiedTime, inFile)
}
func convertFileByType(inFileName string, inFile []byte) (int, int, []byte, error) {

View File

@ -12,7 +12,10 @@ import (
"fmt"
"image"
"image/color"
// force import jpeg support by init only
_ "image/jpeg"
// force import png support by init only
_ "image/png"
"golang.org/x/image/draw"
@ -28,9 +31,10 @@ import (
// 80 NEXT
// 90 PRINT "}"
// 100 PRINT CHR$ (4)"close offsets"
var offsets []int = []int{0, 1024, 2048, 3072, 4096, 5120, 6144, 7168, 128, 1152, 2176, 3200, 4224, 5248, 6272, 7296, 256, 1280, 2304, 3328, 4352, 5376, 6400, 7424, 384, 1408, 2432, 3456, 4480, 5504, 6528, 7552, 512, 1536, 2560, 3584, 4608, 5632, 6656, 7680, 640, 1664, 2688, 3712, 4736, 5760, 6784, 7808, 768, 1792, 2816, 3840, 4864, 5888, 6912, 7936, 896, 1920, 2944, 3968, 4992, 6016, 7040, 8064, 40, 1064, 2088, 3112, 4136, 5160, 6184, 7208, 168, 1192, 2216, 3240, 4264, 5288, 6312, 7336, 296, 1320, 2344, 3368, 4392, 5416, 6440, 7464, 424, 1448, 2472, 3496, 4520, 5544, 6568, 7592, 552, 1576, 2600, 3624, 4648, 5672, 6696, 7720, 680, 1704, 2728, 3752, 4776, 5800, 6824, 7848, 808, 1832, 2856, 3880, 4904, 5928, 6952, 7976, 936, 1960, 2984, 4008, 5032, 6056, 7080, 8104, 80, 1104, 2128, 3152, 4176, 5200, 6224, 7248, 208, 1232, 2256, 3280, 4304, 5328, 6352, 7376, 336, 1360, 2384, 3408, 4432, 5456, 6480, 7504, 464, 1488, 2512, 3536, 4560, 5584, 6608, 7632, 592, 1616, 2640, 3664, 4688, 5712, 6736, 7760, 720, 1744, 2768, 3792, 4816, 5840, 6864, 7888, 848, 1872, 2896, 3920, 4944, 5968, 6992, 8016, 976, 2000, 3024, 4048, 5072, 6096, 7120, 8144}
var pixel []byte = []byte{1, 2, 4, 8, 16, 32, 64}
var offsets = []int{0, 1024, 2048, 3072, 4096, 5120, 6144, 7168, 128, 1152, 2176, 3200, 4224, 5248, 6272, 7296, 256, 1280, 2304, 3328, 4352, 5376, 6400, 7424, 384, 1408, 2432, 3456, 4480, 5504, 6528, 7552, 512, 1536, 2560, 3584, 4608, 5632, 6656, 7680, 640, 1664, 2688, 3712, 4736, 5760, 6784, 7808, 768, 1792, 2816, 3840, 4864, 5888, 6912, 7936, 896, 1920, 2944, 3968, 4992, 6016, 7040, 8064, 40, 1064, 2088, 3112, 4136, 5160, 6184, 7208, 168, 1192, 2216, 3240, 4264, 5288, 6312, 7336, 296, 1320, 2344, 3368, 4392, 5416, 6440, 7464, 424, 1448, 2472, 3496, 4520, 5544, 6568, 7592, 552, 1576, 2600, 3624, 4648, 5672, 6696, 7720, 680, 1704, 2728, 3752, 4776, 5800, 6824, 7848, 808, 1832, 2856, 3880, 4904, 5928, 6952, 7976, 936, 1960, 2984, 4008, 5032, 6056, 7080, 8104, 80, 1104, 2128, 3152, 4176, 5200, 6224, 7248, 208, 1232, 2256, 3280, 4304, 5328, 6352, 7376, 336, 1360, 2384, 3408, 4432, 5456, 6480, 7504, 464, 1488, 2512, 3536, 4560, 5584, 6608, 7632, 592, 1616, 2640, 3664, 4688, 5712, 6736, 7760, 720, 1744, 2768, 3792, 4816, 5840, 6864, 7888, 848, 1872, 2896, 3920, 4944, 5968, 6992, 8016, 976, 2000, 3024, 4048, 5072, 6096, 7120, 8144}
var pixel = []byte{1, 2, 4, 8, 16, 32, 64}
// ConvertImageToHiResMonochrome converts jpeg and png images to Apple II hi-res monochrome
func ConvertImageToHiResMonochrome(imageBytes []byte) []byte {
img, _, err := image.Decode(bytes.NewReader(imageBytes))
@ -66,6 +70,7 @@ func ConvertImageToHiResMonochrome(imageBytes []byte) []byte {
return hires
}
// ConvertImageToHiResColour converts jpeg and png images to Apple II hi-res colour
func ConvertImageToHiResColour(imageBytes []byte) []byte {
img, _, err := image.Decode(bytes.NewReader(imageBytes))

View File

@ -99,6 +99,9 @@ func DumpFileEntry(fileEntry FileEntry) {
fmt.Printf("File type: %02X\n", fileEntry.FileType)
fmt.Printf("Storage type: %02X\n", fileEntry.StorageType)
fmt.Printf("Header pointer: %04X\n", fileEntry.HeaderPointer)
fmt.Printf("Access: %04X\n", fileEntry.Access)
fmt.Printf("Directory block: %04X\n", fileEntry.DirectoryBlock)
fmt.Printf("Directory offset: %04X\n", fileEntry.DirectoryOffset)
fmt.Printf("\n")
}
@ -118,11 +121,22 @@ func DumpVolumeHeader(volumeHeader VolumeHeader) {
// DumpDirectoryHeader dumps the directory header as text
func DumpDirectoryHeader(directoryHeader DirectoryHeader) {
fmt.Printf("Name: %s\n", directoryHeader.Name)
fmt.Printf("File count: %d\n", directoryHeader.ActiveFileCount)
fmt.Printf("Starting block: %04X\n", directoryHeader.StartingBlock)
fmt.Printf("Previous block: %04X\n", directoryHeader.PreviousBlock)
fmt.Printf("Next block: %04X\n", directoryHeader.NextBlock)
fmt.Printf("Is subdirectory: %t\n", directoryHeader.IsSubDirectory)
fmt.Printf("Name: %s\n", directoryHeader.Name)
fmt.Printf("Creation time: %s\n", TimeToString(directoryHeader.CreationTime))
fmt.Printf("Version: %02X\n", directoryHeader.Version)
fmt.Printf("MinVersion: %02X\n", directoryHeader.MinVersion)
fmt.Printf("Access: %02X\n", directoryHeader.Access)
fmt.Printf("Entry length: %02X\n", directoryHeader.EntryLength)
fmt.Printf("Entries per block: %02X\n", directoryHeader.EntriesPerBlock)
fmt.Printf("File count: %d\n", directoryHeader.ActiveFileCount)
fmt.Printf("Active file count: %04X\n", directoryHeader.ActiveFileCount)
fmt.Printf("Parent block: %04X\n", directoryHeader.ParentBlock)
fmt.Printf("Parent entry: %02X\n", directoryHeader.ParentEntry)
fmt.Printf("Parent entry length: %02X\n", directoryHeader.ParentEntryLength)
}
// DumpBlock dumps the block as hexadecimal and text