mirror of
https://github.com/tjboldt/ProDOS-Utilities.git
synced 2024-11-28 12:51:35 +00:00
Add putallrecursive
This commit is contained in:
parent
1722e7b23a
commit
286964d3ee
24
main.go
24
main.go
@ -32,7 +32,7 @@ func main() {
|
|||||||
var auxType int
|
var auxType int
|
||||||
flag.StringVar(&fileName, "d", "", "A ProDOS format drive image")
|
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(&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(&outFileName, "o", "", "Name of file to write")
|
||||||
flag.StringVar(&inFileName, "i", "", "Name of file to read")
|
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.IntVar(&volumeSize, "s", 65535, "Number of blocks to create the volume with (default 65535, 64 to 65535, 0x0040 to 0xFFFF hex input accepted)")
|
||||||
@ -62,13 +62,17 @@ func main() {
|
|||||||
case "create":
|
case "create":
|
||||||
create(fileName, volumeName, volumeSize)
|
create(fileName, volumeName, volumeSize)
|
||||||
case "putall":
|
case "putall":
|
||||||
putall(fileName, inFileName)
|
putall(fileName, inFileName, pathName, false)
|
||||||
|
case "putallrecursive":
|
||||||
|
putall(fileName, inFileName, pathName, true)
|
||||||
case "rm":
|
case "rm":
|
||||||
rm(fileName, pathName)
|
rm(fileName, pathName)
|
||||||
case "mkdir":
|
case "mkdir":
|
||||||
mkdir(fileName, pathName)
|
mkdir(fileName, pathName)
|
||||||
case "dumpfile":
|
case "dumpfile":
|
||||||
dumpFile(fileName, pathName)
|
dumpFile(fileName, pathName)
|
||||||
|
case "dumpdirectory":
|
||||||
|
dumpDirectory(fileName, pathName)
|
||||||
default:
|
default:
|
||||||
fmt.Printf("Invalid command: %s\n\n", command)
|
fmt.Printf("Invalid command: %s\n\n", command)
|
||||||
flag.PrintDefaults()
|
flag.PrintDefaults()
|
||||||
@ -88,6 +92,18 @@ func dumpFile(fileName string, pathName string) {
|
|||||||
prodos.DumpFileEntry(fileEntry)
|
prodos.DumpFileEntry(fileEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dumpDirectory(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()
|
||||||
|
_, directoryheader, _, err := prodos.ReadDirectory(file, pathName)
|
||||||
|
prodos.DumpDirectoryHeader(directoryheader)
|
||||||
|
}
|
||||||
|
|
||||||
func mkdir(fileName string, pathName string) {
|
func mkdir(fileName string, pathName string) {
|
||||||
checkPathName(pathName)
|
checkPathName(pathName)
|
||||||
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
|
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
|
||||||
@ -114,7 +130,7 @@ func rm(fileName string, pathName string) {
|
|||||||
prodos.DeleteFile(file, pathName)
|
prodos.DeleteFile(file, pathName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func putall(fileName string, inFileName string) {
|
func putall(fileName string, inFileName string, pathName string, recursive bool) {
|
||||||
if len(inFileName) == 0 {
|
if len(inFileName) == 0 {
|
||||||
inFileName = "."
|
inFileName = "."
|
||||||
}
|
}
|
||||||
@ -124,7 +140,7 @@ func putall(fileName string, inFileName string) {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
err = prodos.AddFilesFromHostDirectory(file, inFileName)
|
err = prodos.AddFilesFromHostDirectory(file, inFileName, pathName, recursive)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("failed to add host files: %s\n", err)
|
fmt.Printf("failed to add host files: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -131,7 +131,6 @@ func createVolumeBitmap(numberOfBlocks int) []byte {
|
|||||||
markBlockInVolumeBitmap(volumeBitmap, i)
|
markBlockInVolumeBitmap(volumeBitmap, i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//DumpBlock(volumeBitmap)
|
|
||||||
|
|
||||||
return volumeBitmap
|
return volumeBitmap
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ type VolumeHeader struct {
|
|||||||
type DirectoryHeader struct {
|
type DirectoryHeader struct {
|
||||||
PreviousBlock int
|
PreviousBlock int
|
||||||
NextBlock int
|
NextBlock int
|
||||||
|
IsSubDirectory bool
|
||||||
Name string
|
Name string
|
||||||
CreationTime time.Time
|
CreationTime time.Time
|
||||||
Version int
|
Version int
|
||||||
@ -64,19 +65,20 @@ const (
|
|||||||
|
|
||||||
// FileEntry from ProDOS
|
// FileEntry from ProDOS
|
||||||
type FileEntry struct {
|
type FileEntry struct {
|
||||||
StorageType int
|
StorageType int
|
||||||
FileName string
|
FileName string
|
||||||
FileType int
|
FileType int
|
||||||
CreationTime time.Time
|
CreationTime time.Time
|
||||||
KeyPointer int
|
KeyPointer int
|
||||||
Version int
|
Version int
|
||||||
MinVersion int
|
MinVersion int
|
||||||
BlocksUsed int
|
BlocksUsed int
|
||||||
EndOfFile int
|
EndOfFile int
|
||||||
Access int
|
Access int
|
||||||
AuxType int
|
AuxType int
|
||||||
ModifiedTime time.Time
|
ModifiedTime time.Time
|
||||||
HeaderPointer int
|
HeaderPointer int
|
||||||
|
|
||||||
DirectoryBlock int
|
DirectoryBlock int
|
||||||
DirectoryOffset int
|
DirectoryOffset int
|
||||||
}
|
}
|
||||||
@ -95,6 +97,11 @@ func ReadDirectory(reader io.ReaderAt, path string) (VolumeHeader, DirectoryHead
|
|||||||
path = fmt.Sprintf("/%s", volumeHeader.VolumeName)
|
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)
|
path = strings.ToUpper(path)
|
||||||
paths := strings.Split(path, "/")
|
paths := strings.Split(path, "/")
|
||||||
|
|
||||||
@ -106,12 +113,23 @@ func ReadDirectory(reader io.ReaderAt, path string) (VolumeHeader, DirectoryHead
|
|||||||
return volumeHeader, directoryHeader, fileEntries, nil
|
return volumeHeader, directoryHeader, fileEntries, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateDirectory creates a directory information of a specified path
|
||||||
|
// on a ProDOS image
|
||||||
func CreateDirectory(readerWriter ReaderWriterAt, path string) error {
|
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)
|
parentPath, newDirectory := GetDirectoryAndFileNameFromPath(path)
|
||||||
|
|
||||||
existingFileEntry, _ := GetFileEntry(readerWriter, path)
|
existingFileEntry, _ := GetFileEntry(readerWriter, path)
|
||||||
if existingFileEntry.StorageType != StorageDeleted {
|
if existingFileEntry.StorageType != StorageDeleted {
|
||||||
//DeleteFile(readerWriter, path)
|
|
||||||
return errors.New("directory already exists")
|
return errors.New("directory already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,6 +158,8 @@ func CreateDirectory(readerWriter ReaderWriterAt, path string) error {
|
|||||||
fileEntry.KeyPointer = blockList[0]
|
fileEntry.KeyPointer = blockList[0]
|
||||||
fileEntry.Access = 0b11100011
|
fileEntry.Access = 0b11100011
|
||||||
fileEntry.StorageType = StorageDirectory
|
fileEntry.StorageType = StorageDirectory
|
||||||
|
fileEntry.Version = 0x24
|
||||||
|
fileEntry.MinVersion = 0x00
|
||||||
|
|
||||||
writeFileEntry(readerWriter, fileEntry)
|
writeFileEntry(readerWriter, fileEntry)
|
||||||
|
|
||||||
@ -152,6 +172,7 @@ func CreateDirectory(readerWriter ReaderWriterAt, path string) error {
|
|||||||
directoryEntry := DirectoryHeader{
|
directoryEntry := DirectoryHeader{
|
||||||
PreviousBlock: 0,
|
PreviousBlock: 0,
|
||||||
NextBlock: 0,
|
NextBlock: 0,
|
||||||
|
IsSubDirectory: true,
|
||||||
Name: newDirectory,
|
Name: newDirectory,
|
||||||
CreationTime: time.Now(),
|
CreationTime: time.Now(),
|
||||||
Version: 0x24,
|
Version: 0x24,
|
||||||
@ -162,7 +183,7 @@ func CreateDirectory(readerWriter ReaderWriterAt, path string) error {
|
|||||||
ActiveFileCount: 0,
|
ActiveFileCount: 0,
|
||||||
StartingBlock: blockList[0],
|
StartingBlock: blockList[0],
|
||||||
ParentBlock: fileEntry.DirectoryBlock,
|
ParentBlock: fileEntry.DirectoryBlock,
|
||||||
ParentEntry: fileEntry.DirectoryOffset,
|
ParentEntry: (fileEntry.DirectoryOffset - 0x04) / 0x27,
|
||||||
ParentEntryLength: 0x27,
|
ParentEntryLength: 0x27,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,14 +196,26 @@ func CreateDirectory(readerWriter ReaderWriterAt, path string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFreeFileEntryInDirectory(reader io.ReaderAt, directory string) (FileEntry, error) {
|
func makeFullPath(path string, reader io.ReaderAt) (string, error) {
|
||||||
_, directoryHeader, _, err := ReadDirectory(reader, directory)
|
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 {
|
if err != nil {
|
||||||
return FileEntry{}, err
|
return FileEntry{}, err
|
||||||
}
|
}
|
||||||
//DumpDirectoryHeader(directoryHeader)
|
|
||||||
blockNumber := directoryHeader.StartingBlock
|
blockNumber := directoryHeader.StartingBlock
|
||||||
buffer, err := ReadBlock(reader, blockNumber)
|
buffer, err := ReadBlock(readerWriter, blockNumber)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FileEntry{}, err
|
return FileEntry{}, err
|
||||||
}
|
}
|
||||||
@ -191,21 +224,28 @@ func getFreeFileEntryInDirectory(reader io.ReaderAt, directory string) (FileEntr
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
if entryNumber > 13 {
|
if entryNumber > 13 {
|
||||||
blockNumber = int(buffer[2]) + int(buffer[3])*256
|
nextBlockNumber := int(buffer[2]) + int(buffer[3])*256
|
||||||
// if we ran out of blocks in the directory, return empty
|
// if we ran out of blocks in the directory, expand directory or fail
|
||||||
// TODO: expand the directory to add more entries
|
if nextBlockNumber == 0 {
|
||||||
if blockNumber == 0 {
|
if !directoryHeader.IsSubDirectory {
|
||||||
return FileEntry{}, errors.New("no free file entries found")
|
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
|
// else read the next block in the directory
|
||||||
buffer, err = ReadBlock(reader, blockNumber)
|
buffer, err = ReadBlock(readerWriter, blockNumber)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FileEntry{}, nil
|
return FileEntry{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
entryOffset = 4
|
entryOffset = 4
|
||||||
entryNumber = 1
|
entryNumber = 1
|
||||||
}
|
}
|
||||||
fileEntry := parseFileEntry(buffer[entryOffset:entryOffset+40], blockNumber, entryOffset)
|
fileEntry := parseFileEntry(buffer[entryOffset:entryOffset+0x28], blockNumber, entryOffset)
|
||||||
|
|
||||||
if fileEntry.StorageType == StorageDeleted {
|
if fileEntry.StorageType == StorageDeleted {
|
||||||
fileEntry.DirectoryBlock = blockNumber
|
fileEntry.DirectoryBlock = blockNumber
|
||||||
@ -219,6 +259,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) {
|
func getFileEntriesInDirectory(reader io.ReaderAt, blockNumber int, currentPath int, paths []string) (DirectoryHeader, []FileEntry, error) {
|
||||||
buffer, err := ReadBlock(reader, blockNumber)
|
buffer, err := ReadBlock(reader, blockNumber)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -333,13 +418,14 @@ func writeFileEntry(writer io.WriterAt, fileEntry FileEntry) {
|
|||||||
buffer[0x1E] = byte(fileEntry.Access)
|
buffer[0x1E] = byte(fileEntry.Access)
|
||||||
buffer[0x1F] = byte(fileEntry.AuxType & 0x00FF)
|
buffer[0x1F] = byte(fileEntry.AuxType & 0x00FF)
|
||||||
buffer[0x20] = byte(fileEntry.AuxType >> 8)
|
buffer[0x20] = byte(fileEntry.AuxType >> 8)
|
||||||
modifiedTime := DateTimeToProDOS(fileEntry.CreationTime)
|
modifiedTime := DateTimeToProDOS(fileEntry.ModifiedTime)
|
||||||
for i := 0; i < 4; i++ {
|
for i := 0; i < 4; i++ {
|
||||||
buffer[0x21+i] = modifiedTime[i]
|
buffer[0x21+i] = modifiedTime[i]
|
||||||
}
|
}
|
||||||
buffer[0x25] = byte(fileEntry.HeaderPointer & 0x00FF)
|
buffer[0x25] = byte(fileEntry.HeaderPointer & 0x00FF)
|
||||||
buffer[0x26] = byte(fileEntry.HeaderPointer >> 8)
|
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+fileEntry.DirectoryOffset))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
@ -381,7 +467,8 @@ func parseVolumeHeader(buffer []byte) VolumeHeader {
|
|||||||
func parseDirectoryHeader(buffer []byte, blockNumber int) DirectoryHeader {
|
func parseDirectoryHeader(buffer []byte, blockNumber int) DirectoryHeader {
|
||||||
previousBlock := int(buffer[0x00]) + int(buffer[0x01])*256
|
previousBlock := int(buffer[0x00]) + int(buffer[0x01])*256
|
||||||
nextBlock := int(buffer[0x02]) + int(buffer[0x03])*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])
|
name := string(buffer[0x05 : filenameLength+0x05])
|
||||||
creationTime := DateTimeFromProDOS(buffer[0x1C:0x20])
|
creationTime := DateTimeFromProDOS(buffer[0x1C:0x20])
|
||||||
version := int(buffer[0x20])
|
version := int(buffer[0x20])
|
||||||
@ -398,6 +485,7 @@ func parseDirectoryHeader(buffer []byte, blockNumber int) DirectoryHeader {
|
|||||||
PreviousBlock: previousBlock,
|
PreviousBlock: previousBlock,
|
||||||
NextBlock: nextBlock,
|
NextBlock: nextBlock,
|
||||||
StartingBlock: blockNumber,
|
StartingBlock: blockNumber,
|
||||||
|
IsSubDirectory: isSubDirectory,
|
||||||
Name: name,
|
Name: name,
|
||||||
CreationTime: creationTime,
|
CreationTime: creationTime,
|
||||||
Version: version,
|
Version: version,
|
||||||
@ -415,6 +503,7 @@ func parseDirectoryHeader(buffer []byte, blockNumber int) DirectoryHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func writeDirectoryHeader(readerWriter ReaderWriterAt, directoryHeader DirectoryHeader) error {
|
func writeDirectoryHeader(readerWriter ReaderWriterAt, directoryHeader DirectoryHeader) error {
|
||||||
|
// Reading back the block preserves values including reserved fields
|
||||||
buffer, err := ReadBlock(readerWriter, directoryHeader.StartingBlock)
|
buffer, err := ReadBlock(readerWriter, directoryHeader.StartingBlock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -423,6 +512,11 @@ func writeDirectoryHeader(readerWriter ReaderWriterAt, directoryHeader Directory
|
|||||||
buffer[0x01] = byte(directoryHeader.PreviousBlock >> 8)
|
buffer[0x01] = byte(directoryHeader.PreviousBlock >> 8)
|
||||||
buffer[0x02] = byte(directoryHeader.NextBlock & 0x00FF)
|
buffer[0x02] = byte(directoryHeader.NextBlock & 0x00FF)
|
||||||
buffer[0x03] = byte(directoryHeader.NextBlock >> 8)
|
buffer[0x03] = byte(directoryHeader.NextBlock >> 8)
|
||||||
|
if directoryHeader.IsSubDirectory {
|
||||||
|
buffer[0x04] = 0xE0
|
||||||
|
} else {
|
||||||
|
buffer[0x04] = 0xF0
|
||||||
|
}
|
||||||
buffer[0x04] = buffer[0x04] | byte(len(directoryHeader.Name))
|
buffer[0x04] = buffer[0x04] | byte(len(directoryHeader.Name))
|
||||||
for i := 0; i < len(directoryHeader.Name); i++ {
|
for i := 0; i < len(directoryHeader.Name); i++ {
|
||||||
buffer[0x05+i] = directoryHeader.Name[i]
|
buffer[0x05+i] = directoryHeader.Name[i]
|
||||||
@ -431,6 +525,16 @@ func writeDirectoryHeader(readerWriter ReaderWriterAt, directoryHeader Directory
|
|||||||
for i := 0; i < 4; i++ {
|
for i := 0; i < 4; i++ {
|
||||||
buffer[0x1C+i] = creationTime[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[0x20] = byte(directoryHeader.Version)
|
||||||
buffer[0x21] = byte(directoryHeader.MinVersion)
|
buffer[0x21] = byte(directoryHeader.MinVersion)
|
||||||
buffer[0x22] = byte(directoryHeader.Access)
|
buffer[0x22] = byte(directoryHeader.Access)
|
||||||
|
@ -46,6 +46,10 @@ func LoadFile(reader io.ReaderAt, path string) ([]byte, error) {
|
|||||||
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 int, auxType int, createdTime time.Time, modifiedTime time.Time, buffer []byte) error {
|
||||||
directory, fileName := GetDirectoryAndFileNameFromPath(path)
|
directory, fileName := GetDirectoryAndFileNameFromPath(path)
|
||||||
|
|
||||||
|
if len(fileName) > 15 {
|
||||||
|
return errors.New("filename too long")
|
||||||
|
}
|
||||||
|
|
||||||
existingFileEntry, _ := GetFileEntry(readerWriter, path)
|
existingFileEntry, _ := GetFileEntry(readerWriter, path)
|
||||||
if existingFileEntry.StorageType != StorageDeleted {
|
if existingFileEntry.StorageType != StorageDeleted {
|
||||||
DeleteFile(readerWriter, path)
|
DeleteFile(readerWriter, path)
|
||||||
@ -91,6 +95,8 @@ func WriteFile(readerWriter ReaderWriterAt, path string, fileType int, auxType i
|
|||||||
fileEntry.EndOfFile = len(buffer)
|
fileEntry.EndOfFile = len(buffer)
|
||||||
fileEntry.FileType = fileType
|
fileEntry.FileType = fileType
|
||||||
fileEntry.KeyPointer = blockList[0]
|
fileEntry.KeyPointer = blockList[0]
|
||||||
|
fileEntry.Version = 0x24
|
||||||
|
fileEntry.MinVersion = 0x00
|
||||||
fileEntry.Access = 0b11100011
|
fileEntry.Access = 0b11100011
|
||||||
if len(blockList) == 1 {
|
if len(blockList) == 1 {
|
||||||
fileEntry.StorageType = StorageSeedling
|
fileEntry.StorageType = StorageSeedling
|
||||||
@ -135,6 +141,7 @@ func DeleteFile(readerWriter ReaderWriterAt, path string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
volumeBitmap, err := ReadVolumeBitmap(readerWriter)
|
volumeBitmap, err := ReadVolumeBitmap(readerWriter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -307,26 +314,27 @@ func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]int
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
blockOffset := 0
|
blockOffset := 1
|
||||||
if !dataOnly {
|
blocks[0] = fileEntry.KeyPointer
|
||||||
blocks[0] = fileEntry.KeyPointer
|
|
||||||
blockOffset = 1
|
|
||||||
}
|
|
||||||
for i := 0; i < fileEntry.BlocksUsed-1; i++ {
|
for i := 0; i < fileEntry.BlocksUsed-1; i++ {
|
||||||
blocks[i+blockOffset] = int(index[i]) + int(index[i+256])*256
|
blocks[i+blockOffset] = int(index[i]) + int(index[i+256])*256
|
||||||
}
|
}
|
||||||
|
if dataOnly {
|
||||||
|
return blocks[1:], nil
|
||||||
|
}
|
||||||
return blocks, nil
|
return blocks, nil
|
||||||
case StorageTree:
|
case StorageTree:
|
||||||
|
// this is actually too large
|
||||||
dataBlocks := make([]int, fileEntry.BlocksUsed)
|
dataBlocks := make([]int, fileEntry.BlocksUsed)
|
||||||
numberOfIndexBlocks := fileEntry.BlocksUsed/256 + 1
|
// this is also actually too large
|
||||||
if fileEntry.BlocksUsed%256 != 0 {
|
numberOfIndexBlocks := fileEntry.BlocksUsed/256 + 2
|
||||||
numberOfIndexBlocks++
|
|
||||||
}
|
|
||||||
indexBlocks := make([]int, numberOfIndexBlocks)
|
indexBlocks := make([]int, numberOfIndexBlocks)
|
||||||
masterIndex, err := ReadBlock(reader, fileEntry.KeyPointer)
|
masterIndex, err := ReadBlock(reader, fileEntry.KeyPointer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
numberOfDataBlocks := 0
|
||||||
|
|
||||||
indexBlocks[0] = fileEntry.KeyPointer
|
indexBlocks[0] = fileEntry.KeyPointer
|
||||||
indexBlockCount := 1
|
indexBlockCount := 1
|
||||||
|
|
||||||
@ -345,6 +353,7 @@ func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]int
|
|||||||
if (int(index[j]) + int(index[j+256])*256) == 0 {
|
if (int(index[j]) + int(index[j+256])*256) == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
numberOfDataBlocks++
|
||||||
dataBlocks[i*256+j] = int(index[j]) + int(index[j+256])*256
|
dataBlocks[i*256+j] = int(index[j]) + int(index[j+256])*256
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -353,7 +362,7 @@ func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]int
|
|||||||
return dataBlocks, nil
|
return dataBlocks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
blocks = append(indexBlocks, dataBlocks...)
|
blocks = append(indexBlocks[0:numberOfIndexBlocks], dataBlocks[0:numberOfDataBlocks]...)
|
||||||
return blocks, nil
|
return blocks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -392,7 +401,7 @@ func createBlockList(reader io.ReaderAt, fileSize int) ([]int, error) {
|
|||||||
|
|
||||||
blockList := findFreeBlocks(volumeBitmap, numberOfBlocks)
|
blockList := findFreeBlocks(volumeBitmap, numberOfBlocks)
|
||||||
|
|
||||||
return blockList, nil
|
return blockList[0:numberOfBlocks], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFileEntry returns a file entry for the given path
|
// GetFileEntry returns a file entry for the given path
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreatBlocklist(t *testing.T) {
|
func TestCreateBlocklist(t *testing.T) {
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
fileSize int
|
fileSize int
|
||||||
wantBlocks int
|
wantBlocks int
|
||||||
|
@ -8,6 +8,7 @@ package prodos
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -19,7 +20,15 @@ import (
|
|||||||
// from the specified host directory
|
// from the specified host directory
|
||||||
func AddFilesFromHostDirectory(
|
func AddFilesFromHostDirectory(
|
||||||
readerWriter ReaderWriterAt,
|
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)
|
files, err := os.ReadDir(directory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -33,37 +42,67 @@ func AddFilesFromHostDirectory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if file.Name()[0] != '.' && !file.IsDir() && info.Size() > 0 && info.Size() <= 0x1000000 {
|
if file.Name()[0] != '.' && !file.IsDir() && info.Size() > 0 && info.Size() <= 0x1000000 {
|
||||||
err = WriteFileFromFile(readerWriter, "", 0, 0, info.ModTime(), filepath.Join(directory, file.Name()))
|
err = WriteFileFromFile(readerWriter, path, 0, 0, info.ModTime(), filepath.Join(directory, file.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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())
|
||||||
|
CreateDirectory(readerWriter, newFullPath)
|
||||||
|
AddFilesFromHostDirectory(readerWriter, newHostDirectory, newFullPath+"/", recursive)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteFileFromFile writes a file to a ProDOS volume from a host file
|
// WriteFileFromFile writes a file to a ProDOS volume from a host file
|
||||||
func WriteFileFromFile(readerWriter ReaderWriterAt, pathName string, fileType int, auxType int, modifiedTime time.Time, inFileName string) error {
|
func WriteFileFromFile(
|
||||||
fmt.Printf("WriteFileFromFile: %s\n", inFileName)
|
readerWriter ReaderWriterAt,
|
||||||
|
pathName string,
|
||||||
|
fileType int,
|
||||||
|
auxType int,
|
||||||
|
modifiedTime time.Time,
|
||||||
|
inFileName string) error {
|
||||||
|
|
||||||
inFile, err := os.ReadFile(inFileName)
|
inFile, err := os.ReadFile(inFileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("failed to read file")
|
errString := fmt.Sprintf("write from file failed: %s", err)
|
||||||
return err
|
return errors.New(errString)
|
||||||
}
|
}
|
||||||
|
|
||||||
if auxType == 0 && fileType == 0 {
|
if auxType == 0 && fileType == 0 {
|
||||||
auxType, fileType, inFile, err = convertFileByType(inFileName, inFile)
|
auxType, fileType, inFile, err = convertFileByType(inFileName, inFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("failed to convert file")
|
errString := fmt.Sprintf("failed to convert file: %s", err)
|
||||||
return err
|
return errors.New(errString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trimExtensions := false
|
||||||
if len(pathName) == 0 {
|
if len(pathName) == 0 {
|
||||||
_, pathName = filepath.Split(inFileName)
|
_, pathName = filepath.Split(inFileName)
|
||||||
pathName = strings.ToUpper(pathName)
|
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)
|
ext := filepath.Ext(pathName)
|
||||||
|
|
||||||
if len(ext) > 0 {
|
if len(ext) > 0 {
|
||||||
switch ext {
|
switch ext {
|
||||||
case ".SYS", ".TXT", ".BAS", ".BIN":
|
case ".SYS", ".TXT", ".BAS", ".BIN":
|
||||||
@ -72,6 +111,13 @@ func WriteFileFromFile(readerWriter ReaderWriterAt, pathName string, fileType in
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Source: %s Destination: %s\n", inFileName, pathName)
|
||||||
return WriteFile(readerWriter, pathName, fileType, auxType, time.Now(), modifiedTime, inFile)
|
return WriteFile(readerWriter, pathName, fileType, auxType, time.Now(), modifiedTime, inFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,6 +100,8 @@ func DumpFileEntry(fileEntry FileEntry) {
|
|||||||
fmt.Printf("Storage type: %02X\n", fileEntry.StorageType)
|
fmt.Printf("Storage type: %02X\n", fileEntry.StorageType)
|
||||||
fmt.Printf("Header pointer: %04X\n", fileEntry.HeaderPointer)
|
fmt.Printf("Header pointer: %04X\n", fileEntry.HeaderPointer)
|
||||||
fmt.Printf("Access: %04X\n", fileEntry.Access)
|
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")
|
fmt.Printf("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,11 +121,22 @@ func DumpVolumeHeader(volumeHeader VolumeHeader) {
|
|||||||
|
|
||||||
// DumpDirectoryHeader dumps the directory header as text
|
// DumpDirectoryHeader dumps the directory header as text
|
||||||
func DumpDirectoryHeader(directoryHeader DirectoryHeader) {
|
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("Starting block: %04X\n", directoryHeader.StartingBlock)
|
||||||
fmt.Printf("Previous block: %04X\n", directoryHeader.PreviousBlock)
|
fmt.Printf("Previous block: %04X\n", directoryHeader.PreviousBlock)
|
||||||
fmt.Printf("Next block: %04X\n", directoryHeader.NextBlock)
|
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
|
// DumpBlock dumps the block as hexadecimal and text
|
||||||
|
Loading…
Reference in New Issue
Block a user