2024-03-12 02:16:02 +00:00
|
|
|
// Copyright Terence J. Boldt (c)2021-2024
|
2022-01-23 21:58:34 +00:00
|
|
|
// Use of this source code is governed by an MIT
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
// This file provides a command line utility to read, write and delete
|
|
|
|
// files and directories on a ProDOS drive image as well as format
|
|
|
|
// new volumes
|
|
|
|
|
2021-06-06 03:22:30 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2021-06-07 00:15:41 +00:00
|
|
|
"flag"
|
2021-06-06 03:22:30 +00:00
|
|
|
"fmt"
|
|
|
|
"os"
|
2021-06-29 11:29:48 +00:00
|
|
|
"strings"
|
2021-06-06 03:22:30 +00:00
|
|
|
|
|
|
|
"github.com/tjboldt/ProDOS-Utilities/prodos"
|
|
|
|
)
|
|
|
|
|
2024-07-18 16:57:18 +00:00
|
|
|
const version = "0.5.1"
|
2021-07-03 12:37:21 +00:00
|
|
|
|
2021-06-06 03:22:30 +00:00
|
|
|
func main() {
|
2021-06-07 00:15:41 +00:00
|
|
|
var fileName string
|
|
|
|
var pathName string
|
|
|
|
var command string
|
|
|
|
var outFileName string
|
2021-06-26 01:15:20 +00:00
|
|
|
var inFileName string
|
2024-03-11 13:05:23 +00:00
|
|
|
var blockNumber uint
|
|
|
|
var volumeSize uint
|
2021-06-09 12:23:18 +00:00
|
|
|
var volumeName string
|
2024-03-11 13:05:23 +00:00
|
|
|
var fileType uint
|
|
|
|
var auxType uint
|
2021-07-03 12:37:21 +00:00
|
|
|
flag.StringVar(&fileName, "d", "", "A ProDOS format drive image")
|
|
|
|
flag.StringVar(&pathName, "p", "", "Path name in ProDOS drive image (default is root of volume)")
|
2024-07-18 16:57:18 +00:00
|
|
|
flag.StringVar(&command, "c", "ls", "Command to execute: ls, create, rm, mkdir, get, getraw, put, putall, putallrecursive, readblock, writeblock")
|
2021-07-03 12:37:21 +00:00
|
|
|
flag.StringVar(&outFileName, "o", "", "Name of file to write")
|
|
|
|
flag.StringVar(&inFileName, "i", "", "Name of file to read")
|
2024-03-11 13:05:23 +00:00
|
|
|
flag.UintVar(&volumeSize, "s", 65535, "Number of blocks to create the volume with (default 65535, 64 to 65535, 0x0040 to 0xFFFF hex input accepted)")
|
2021-07-03 12:37:21 +00:00
|
|
|
flag.StringVar(&volumeName, "v", "NO.NAME", "Specifiy a name for the volume from 1 to 15 characters")
|
2024-03-11 13:05:23 +00:00
|
|
|
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")
|
2021-06-07 00:15:41 +00:00
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
if len(fileName) == 0 {
|
2021-07-03 12:37:21 +00:00
|
|
|
printReadme()
|
|
|
|
flag.PrintDefaults()
|
2021-06-06 12:00:20 +00:00
|
|
|
os.Exit(1)
|
2021-06-06 03:22:30 +00:00
|
|
|
}
|
2021-06-06 12:00:20 +00:00
|
|
|
|
2021-06-07 00:15:41 +00:00
|
|
|
switch command {
|
|
|
|
case "ls":
|
2023-01-20 01:56:35 +00:00
|
|
|
ls(fileName, pathName)
|
2021-06-07 00:15:41 +00:00
|
|
|
case "get":
|
2023-01-20 01:56:35 +00:00
|
|
|
get(fileName, pathName, outFileName)
|
2024-07-18 16:57:18 +00:00
|
|
|
case "getraw":
|
|
|
|
getRaw(fileName, pathName)
|
2021-06-26 01:15:20 +00:00
|
|
|
case "put":
|
2024-03-11 13:05:23 +00:00
|
|
|
put(fileName, pathName, uint8(fileType), uint16(auxType), inFileName)
|
2021-06-09 12:23:18 +00:00
|
|
|
case "readblock":
|
2024-03-11 13:05:23 +00:00
|
|
|
readBlock(uint16(blockNumber), fileName)
|
2021-12-12 15:43:30 +00:00
|
|
|
case "writeblock":
|
2024-03-11 13:05:23 +00:00
|
|
|
writeBlock(uint16(blockNumber), fileName, inFileName)
|
2021-07-03 12:37:21 +00:00
|
|
|
case "create":
|
2024-03-11 13:05:23 +00:00
|
|
|
create(fileName, volumeName, uint16(volumeSize))
|
2022-12-30 11:12:41 +00:00
|
|
|
case "putall":
|
2023-01-22 15:01:57 +00:00
|
|
|
putall(fileName, inFileName, pathName, false)
|
|
|
|
case "putallrecursive":
|
|
|
|
putall(fileName, inFileName, pathName, true)
|
2021-07-03 12:37:21 +00:00
|
|
|
case "rm":
|
2023-01-20 01:56:35 +00:00
|
|
|
rm(fileName, pathName)
|
2023-01-19 05:30:17 +00:00
|
|
|
case "mkdir":
|
2023-01-20 01:56:35 +00:00
|
|
|
mkdir(fileName, pathName)
|
2022-03-06 10:29:33 +00:00
|
|
|
case "dumpfile":
|
2023-01-20 01:56:35 +00:00
|
|
|
dumpFile(fileName, pathName)
|
2023-01-22 15:01:57 +00:00
|
|
|
case "dumpdirectory":
|
|
|
|
dumpDirectory(fileName, pathName)
|
2021-06-07 00:15:41 +00:00
|
|
|
default:
|
2021-07-03 12:37:21 +00:00
|
|
|
fmt.Printf("Invalid command: %s\n\n", command)
|
|
|
|
flag.PrintDefaults()
|
2021-06-07 00:15:41 +00:00
|
|
|
os.Exit(1)
|
2021-06-06 03:22:30 +00:00
|
|
|
}
|
|
|
|
}
|
2023-01-20 01:56:35 +00:00
|
|
|
|
|
|
|
func dumpFile(fileName string, pathName string) {
|
|
|
|
checkPathName(pathName)
|
2024-01-14 04:00:47 +00:00
|
|
|
file, err := os.OpenFile(fileName, os.O_RDONLY, 0755)
|
2023-01-20 01:56:35 +00:00
|
|
|
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)
|
2024-03-13 01:53:48 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Failed to path %s:\n %s", pathName, err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2023-01-20 01:56:35 +00:00
|
|
|
prodos.DumpFileEntry(fileEntry)
|
|
|
|
}
|
|
|
|
|
2023-01-22 15:01:57 +00:00
|
|
|
func dumpDirectory(fileName string, pathName string) {
|
|
|
|
checkPathName(pathName)
|
2024-01-14 04:00:47 +00:00
|
|
|
file, err := os.OpenFile(fileName, os.O_RDONLY, 0755)
|
2023-01-22 15:01:57 +00:00
|
|
|
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)
|
2024-03-13 01:53:48 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Failed to read directory %s:\n %s", pathName, err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2023-01-22 15:01:57 +00:00
|
|
|
prodos.DumpDirectoryHeader(directoryheader)
|
|
|
|
}
|
|
|
|
|
2023-01-20 01:56:35 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2023-01-22 15:01:57 +00:00
|
|
|
func putall(fileName string, inFileName string, pathName string, recursive bool) {
|
2023-01-20 01:56:35 +00:00
|
|
|
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()
|
2023-01-22 15:01:57 +00:00
|
|
|
err = prodos.AddFilesFromHostDirectory(file, inFileName, pathName, recursive)
|
2023-01-20 01:56:35 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("failed to add host files: %s\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-11 13:05:23 +00:00
|
|
|
func create(fileName string, volumeName string, volumeSize uint16) {
|
2023-01-20 01:56:35 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2024-03-11 13:05:23 +00:00
|
|
|
func writeBlock(blockNumber uint16, fileName string, inFileName string) {
|
2023-01-20 01:56:35 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2024-03-11 13:05:23 +00:00
|
|
|
func readBlock(blockNumber uint16, fileName string) {
|
2023-01-20 01:56:35 +00:00
|
|
|
fmt.Printf("Reading block 0x%04X (%d):\n\n", blockNumber, blockNumber)
|
2024-01-14 04:00:47 +00:00
|
|
|
file, err := os.OpenFile(fileName, os.O_RDONLY, 0755)
|
2023-01-20 01:56:35 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2024-03-11 13:05:23 +00:00
|
|
|
func put(fileName string, pathName string, fileType uint8, auxType uint16, inFileName string) {
|
2023-01-20 01:56:35 +00:00
|
|
|
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)
|
2024-03-13 01:53:48 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Failed get fileInfo for %s - %s", fileName, err)
|
|
|
|
}
|
2023-01-20 01:56:35 +00:00
|
|
|
|
2024-08-26 20:54:18 +00:00
|
|
|
err = prodos.WriteFileFromFile(file, pathName, fileType, auxType, fileInfo.ModTime(), inFileName, nil, false)
|
2023-01-20 01:56:35 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Failed to write file %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func get(fileName string, pathName string, outFileName string) {
|
|
|
|
checkPathName(pathName)
|
2024-01-14 04:00:47 +00:00
|
|
|
file, err := os.OpenFile(fileName, os.O_RDONLY, 0755)
|
2023-01-20 01:56:35 +00:00
|
|
|
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") {
|
2024-03-13 01:53:48 +00:00
|
|
|
fmt.Fprint(outFile, prodos.ConvertBasicToText(getFile))
|
2023-01-20 01:56:35 +00:00
|
|
|
} else {
|
|
|
|
outFile.Write(getFile)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-18 16:57:18 +00:00
|
|
|
func getRaw(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()
|
|
|
|
getFile, err := prodos.LoadFile(file, pathName)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Failed to read file %s: %s\n", pathName, err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
fileEntry, err := prodos.GetFileEntry(file, pathName)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Failed to get file entry %s: %s\n", pathName, err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
fileType := prodos.FileTypeToString(fileEntry.FileType)
|
|
|
|
outFileName := fmt.Sprintf("%s.%s$%04X", fileEntry.FileName, fileType, fileEntry.AuxType)
|
|
|
|
|
|
|
|
outFile, err := os.Create(outFileName)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Failed to create output file %s: %s\n", outFileName, err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
outFile.Write(getFile)
|
|
|
|
}
|
|
|
|
|
2023-01-20 01:56:35 +00:00
|
|
|
func ls(fileName string, pathName string) {
|
2024-01-14 04:00:47 +00:00
|
|
|
file, err := os.OpenFile(fileName, os.O_RDONLY, 0755)
|
2023-01-20 01:56:35 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|