diff --git a/README.md b/README.md index 577dbaf..d546017 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,11 @@ BLOCKS FREE: 15 BLOCKS USED: 65520 TOTAL BLOCKS: 65535 ProDOS-Utilities -d new.hdv -c create -b 65535 ``` +### Add all files from a host directory +``` +ProDOS-Utilities -d new.hdv -c putall -i . +``` + ### Hex dump a block with command readblock and block number (both decimal and hexadecimal input work) ``` ProDOS-Utilities -d new.hdv -c readblock -b 0 @@ -118,31 +123,6 @@ Block 0x0000 (0): ### Export files (using .bas file extension coverts Applesoft to text file) ``` -ProDOS-Utilities -d ../Apple2-IO-RPi/RaspberryPi/Apple2-IO-RPi.hdv -c get -o Update.Firmware.bas -p /APPLE2.IO.RPI/UPDATE.FIRMWARE; cat Update.Firmware.bas -10 HOME -100 PRINT CHR$ (4)"BLOAD AT28C64B.BIN,A$2000" -200 PRINT "Program the firmare in slot #" -300 INPUT SL -400 FW = 8192 + 256 * SL: REM Firmware source -500 PS = 49287 + SL * 16: REM Firmware page selection -600 EP = 49152 + SL * 256: REM EPROM location -900 HOME -1000 FOR PG = 0 TO 3 -1004 PRINT : PRINT -1005 PRINT "Writing page "PG" to slot "SL" -1006 PRINT "_______________________________________" -1007 PRINT "_______________________________________"; -1008 HTAB 1 -1010 POKE PS,PG * 16 + 15: REM Set firmware page -1020 FOR X = 0 TO 255 -1030 A = PEEK (FW + PG * 2048 + X) -1040 POKE EP + X,A -1041 B = PEEK (EP + X) -1042 IF (B < > A) THEN GOTO 1041 -1045 HTAB (X / 256) * 39 + 1 -1046 INVERSE : PRINT " ";: NORMAL -1050 NEXT X -1060 NEXT PG -1900 PRINT -2000 PRINT "Firmware Update Complete" +ProDOS-Utilities -d example.hdv -c get -o Startup.bas -p /EXAMPLE/STARTUP; cat Startup.bas +10 PRINT "HELLO WORLD" ``` diff --git a/main.go b/main.go index 5629a87..3051c5f 100644 --- a/main.go +++ b/main.go @@ -9,7 +9,6 @@ package main import ( - "encoding/binary" "flag" "fmt" "os" @@ -18,7 +17,7 @@ import ( "github.com/tjboldt/ProDOS-Utilities/prodos" ) -const version = "0.3.2" +const version = "0.4.0" func main() { var fileName string @@ -33,14 +32,14 @@ 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") + flag.StringVar(&command, "c", "ls", "Command to execute: ls, get, put, rm, mkdir, readblock, writeblock, create, putall") 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.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", 6, "ProDOS FileType: 0x04 for TXT, 0x06 for BIN, 0xFC for BAS, 0xFF for SYS etc.") - flag.IntVar(&auxType, "a", 0x2000, "ProDOS AuxType 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.Parse() if len(fileName) == 0 { @@ -108,49 +107,10 @@ func main() { os.Exit(1) } defer file.Close() - if len(pathName) == 0 { - fmt.Println("Missing pathname") - os.Exit(1) - } - inFile, err := os.ReadFile(inFileName) - if err != nil { - fmt.Printf("Failed to open input file %s: %s", inFileName, err) - os.Exit(1) - } - if strings.HasSuffix(strings.ToLower(inFileName), ".bas") { - inFile, err = prodos.ConvertTextToBasic(string(inFile)) - fileType = 0xFC - auxType = 0x0801 - } - // Check for an AppleSingle file as produced by cc65 - if // Magic number - binary.BigEndian.Uint32(inFile[0x00:]) == 0x00051600 && - // Version number - binary.BigEndian.Uint32(inFile[0x04:]) == 0x00020000 && - // Number of entries - binary.BigEndian.Uint16(inFile[0x18:]) == 0x0002 && - // Data Fork ID - binary.BigEndian.Uint32(inFile[0x1A:]) == 0x00000001 && - // Offset - binary.BigEndian.Uint32(inFile[0x1E:]) == 0x0000003A && - // Length - binary.BigEndian.Uint32(inFile[0x22:]) == uint32(len(inFile))-0x3A && - // ProDOS File Info ID - binary.BigEndian.Uint32(inFile[0x26:]) == 0x0000000B && - // Offset - binary.BigEndian.Uint32(inFile[0x2A:]) == 0x00000032 && - // Length - binary.BigEndian.Uint32(inFile[0x2E:]) == 0x00000008 { - - fileType = int(binary.BigEndian.Uint16(inFile[0x34:])) - auxType = int(binary.BigEndian.Uint32(inFile[0x36:])) - inFile = inFile[0x3A:] - fmt.Printf("AppleSingle (File type: %02X, AuxType: %04X) detected\n", fileType, auxType) - } - err = prodos.WriteFile(file, pathName, fileType, auxType, inFile) + err = prodos.WriteFileFromFile(file, pathName, fileType, auxType, inFileName) if err != nil { - fmt.Printf("Failed to write file %s: %s", pathName, err) + fmt.Printf("Failed to write file %s", err) } case "readblock": fmt.Printf("Reading block 0x%04X (%d):\n\n", blockNumber, blockNumber) @@ -188,6 +148,18 @@ func main() { } defer file.Close() prodos.CreateVolume(file, 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, volumeName, volumeSize) + if err != nil { + fmt.Printf("failed to add host files: %s\n", err) + return + } case "rm": file, err := os.OpenFile(fileName, os.O_RDWR, 0755) if err != nil { diff --git a/prodos/bitmap.go b/prodos/bitmap.go index ef2b298..07e5bf0 100644 --- a/prodos/bitmap.go +++ b/prodos/bitmap.go @@ -8,7 +8,6 @@ package prodos import ( - "fmt" "io" ) @@ -147,10 +146,6 @@ func findFreeBlocks(volumeBitmap []byte, numberOfBlocks int) []int { blocks[blocksFound] = i blocksFound++ if blocksFound == numberOfBlocks { - for i := 0; i < len(blocks); i++ { - fmt.Printf("%04X ", blocks[i]) - } - fmt.Printf("\n") return blocks } } diff --git a/prodos/file.go b/prodos/file.go index 752d8ad..c2b27ed 100644 --- a/prodos/file.go +++ b/prodos/file.go @@ -8,9 +8,12 @@ package prodos import ( + "encoding/binary" "errors" "fmt" "io" + "os" + "path/filepath" "strings" "time" ) @@ -42,6 +45,93 @@ func LoadFile(reader io.ReaderAt, path string) ([]byte, error) { return buffer, nil } +// 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 { + inFile, err := os.ReadFile(inFileName) + if err != nil { + return err + } + + if auxType == 0 && fileType == 0 { + auxType, fileType, inFile, err = convertFileByType(inFileName, inFile) + if err != nil { + return err + } + } + + if len(pathName) == 0 { + _, pathName = filepath.Split(inFileName) + pathName = strings.ToUpper(pathName) + ext := filepath.Ext(pathName) + if len(ext) > 0 { + switch ext { + case ".SYS", ".TXT", ".BAS", ".BIN": + pathName = strings.TrimSuffix(pathName, ext) + } + } + } + + return WriteFile(readerWriter, pathName, fileType, auxType, inFile) +} + +func convertFileByType(inFileName string, inFile []byte) (int, int, []byte, error) { + fileType := 0x06 // default to BIN + auxType := 0x2000 // default to $2000 + + var err error + + // Check for an AppleSingle file as produced by cc65 + if // Magic number + binary.BigEndian.Uint32(inFile[0x00:]) == 0x00051600 && + // Version number + binary.BigEndian.Uint32(inFile[0x04:]) == 0x00020000 && + // Number of entries + binary.BigEndian.Uint16(inFile[0x18:]) == 0x0002 && + // Data Fork ID + binary.BigEndian.Uint32(inFile[0x1A:]) == 0x00000001 && + // Offset + binary.BigEndian.Uint32(inFile[0x1E:]) == 0x0000003A && + // Length + binary.BigEndian.Uint32(inFile[0x22:]) == uint32(len(inFile))-0x3A && + // ProDOS File Info ID + binary.BigEndian.Uint32(inFile[0x26:]) == 0x0000000B && + // Offset + binary.BigEndian.Uint32(inFile[0x2A:]) == 0x00000032 && + // Length + binary.BigEndian.Uint32(inFile[0x2E:]) == 0x00000008 { + + fileType = int(binary.BigEndian.Uint16(inFile[0x34:])) + auxType = int(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 + + 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 + } + } + + return auxType, fileType, inFile, err +} + // 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 { directory, fileName := GetDirectoryAndFileNameFromPath(path) @@ -57,11 +147,6 @@ func WriteFile(readerWriter ReaderWriterAt, path string, fileType int, auxType i return err } - for i := 0; i < len(blockList); i++ { - fmt.Printf("%04X ", blockList[i]) - } - fmt.Printf("\n") - // seedling file if len(buffer) <= 0x200 { WriteBlock(readerWriter, blockList[0], buffer) @@ -183,10 +268,6 @@ func GetDirectoryAndFileNameFromPath(path string) (string, string) { } func updateVolumeBitmap(readerWriter ReaderWriterAt, blockList []int) error { - for i := 0; i < len(blockList); i++ { - fmt.Printf("%04X ", blockList[i]) - } - fmt.Printf("\n") volumeBitmap, err := ReadVolumeBitmap(readerWriter) if err != nil { @@ -319,7 +400,7 @@ func createBlockList(reader io.ReaderAt, fileSize int) ([]int, error) { numberOfBlocks++ // add index block } - if fileSize > 0x20000 && fileSize < 0x1000000 { + if fileSize > 0x20000 && fileSize <= 0x1000000 { //fmt.Printf("Tree file\n") // add index blocks for each 256 blocks numberOfBlocks += numberOfBlocks / 256 diff --git a/prodos/host.go b/prodos/host.go new file mode 100644 index 0000000..f1851f7 --- /dev/null +++ b/prodos/host.go @@ -0,0 +1,43 @@ +// Copyright Terence J. Boldt (c)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 generate a ProDOS drive image from a host directory + +package prodos + +import ( + "os" + "path/filepath" +) + +// AddFilesFromHostDirectory fills the root volume with files +// from the specified host directory +func AddFilesFromHostDirectory( + readerWriter ReaderWriterAt, + directory string, + volumeName string, + numberOfBlocks int) error { + CreateVolume(readerWriter, volumeName, numberOfBlocks) + + files, err := os.ReadDir(directory) + if err != nil { + return err + } + + for _, file := range files { + info, err := file.Info() + if err != nil { + return err + } + + if !file.IsDir() && info.Size() > 0 && info.Size() <= 0x20000 { + err = WriteFileFromFile(readerWriter, "", 0, 0, filepath.Join(directory, file.Name())) + if err != nil { + return err + } + } + } + + return nil +}