ProDOS-Utilities/main.go

318 lines
9.1 KiB
Go
Raw Permalink Normal View History

2024-03-12 02:16:02 +00:00
// 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 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"
"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-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
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")
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)")
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 {
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)
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)
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)
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:
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)
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)
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)
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)
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) {
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)
}
}