diff --git a/prodos/directory.go b/prodos/directory.go index 5fae6e2..13098b9 100644 --- a/prodos/directory.go +++ b/prodos/directory.go @@ -281,7 +281,7 @@ func parseVolumeHeader(buffer []byte) VolumeHeader { bitmapBlock := int(buffer[39]) + int(buffer[40])*256 totalBlocks := int(buffer[41]) + int(buffer[42])*256 - if version > 0 || minVersion > 0 { + if minVersion > 0 { panic("Unsupported ProDOS version") } diff --git a/prodos/file.go b/prodos/file.go index 77a84ff..f8bba50 100644 --- a/prodos/file.go +++ b/prodos/file.go @@ -8,12 +8,9 @@ package prodos import ( - "encoding/binary" "errors" "fmt" "io" - "os" - "path/filepath" "strings" "time" ) @@ -45,93 +42,6 @@ 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) @@ -218,7 +128,7 @@ func DeleteFile(readerWriter ReaderWriterAt, path string) error { } // free the blocks - blocks, err := getBlocklist(readerWriter, fileEntry) + blocks, err := getAllBlockList(readerWriter, fileEntry) if err != nil { return err } @@ -313,11 +223,18 @@ func writeSaplingFile(writer io.WriterAt, buffer []byte, blockList []int) { } func writeTreeFile(writer io.WriterAt, buffer []byte, blockList []int) { +} +func getDataBlocklist(reader io.ReaderAt, fileEntry FileEntry) ([]int, error) { + return getBlocklist(reader, fileEntry, true) +} + +func getAllBlockList(reader io.ReaderAt, fileEntry FileEntry) ([]int, error) { + return getBlocklist(reader, fileEntry, false) } // Returns all blocks, including index blocks -func getBlocklist(reader io.ReaderAt, fileEntry FileEntry) ([]int, error) { +func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]int, error) { blocks := make([]int, fileEntry.BlocksUsed) switch fileEntry.StorageType { @@ -329,58 +246,52 @@ func getBlocklist(reader io.ReaderAt, fileEntry FileEntry) ([]int, error) { if err != nil { return nil, err } - blocks[0] = fileEntry.KeyPointer + blockOffset := 0 + if !dataOnly { + blocks[0] = fileEntry.KeyPointer + blockOffset = 1 + } for i := 0; i < fileEntry.BlocksUsed-1; i++ { - blocks[i+1] = int(index[i]) + int(index[i+256])*256 + blocks[i+blockOffset] = int(index[i]) + int(index[i+256])*256 } return blocks, nil case StorageTree: + dataBlocks := make([]int, fileEntry.BlocksUsed) masterIndex, err := ReadBlock(reader, fileEntry.KeyPointer) if err != nil { return nil, err } - blocks[0] = fileEntry.KeyPointer + blockOffset := 0 + if !dataOnly { + blocks[0] = fileEntry.KeyPointer + blockOffset = 1 + } for i := 0; i < 128; i++ { - index, err := ReadBlock(reader, int(masterIndex[i])+int(masterIndex[i+256])*256) + indexBlock := int(masterIndex[i]) + int(masterIndex[i+256])*256 + if indexBlock == 0 { + break + } + if !dataOnly { + blockOffset++ + } + index, err := ReadBlock(reader, indexBlock) if err != nil { return nil, err } for j := 0; j < 256 && i*256+j < fileEntry.BlocksUsed; j++ { if (int(index[j]) + int(index[j+256])*256) == 0 { - return blocks, nil + break } - blocks[i*256+j] = int(index[j]) + int(index[j+256])*256 + dataBlocks[i*256+j] = int(index[j]) + int(index[j+256])*256 } } - } - return nil, errors.New("unsupported file storage type") -} - -func getDataBlocklist(reader io.ReaderAt, fileEntry FileEntry) ([]int, error) { - switch fileEntry.StorageType { - case StorageSeedling: - blocks := make([]int, 1) - blocks[0] = fileEntry.KeyPointer - return blocks, nil - case StorageSapling: - blocks := make([]int, fileEntry.BlocksUsed-1) - index, err := ReadBlock(reader, fileEntry.KeyPointer) - if err != nil { - return nil, err + if dataOnly { + return dataBlocks, nil } - for i := 0; i < fileEntry.BlocksUsed-1; i++ { - blocks[i] = int(index[i]) + int(index[i+256])*256 - } - return blocks, nil - // case StorageTree: - // blocks := make([]int, fileEntry.BlocksUsed-fileEntry.BlocksUsed/256-1) - // masterIndex := ReadBlock(reader, fileEntry.KeyPointer) - // for i := 0; i < 128; i++ { - // blockNumber := 0 - // blocks[j] = int(index[i]) + int(index[i+256])*256 - // } + blocks = append(blocks[blockOffset:], dataBlocks...) + return blocks, nil } return nil, errors.New("unsupported file storage type") @@ -388,20 +299,16 @@ func getDataBlocklist(reader io.ReaderAt, fileEntry FileEntry) ([]int, error) { func createBlockList(reader io.ReaderAt, fileSize int) ([]int, error) { numberOfBlocks := fileSize / 512 - //fmt.Printf("Number of blocks %d\n", numberOfBlocks) if fileSize%512 > 0 { - //fmt.Printf("Adding block for partial usage\n") numberOfBlocks++ } if fileSize > 0x200 && fileSize <= 0x20000 { - //fmt.Printf("Adding index block for sapling file\n") numberOfBlocks++ // add index block } if fileSize > 0x20000 && fileSize <= 0x1000000 { - //fmt.Printf("Tree file\n") // add index blocks for each 256 blocks numberOfBlocks += numberOfBlocks / 256 // add index block for any remaining blocks @@ -420,7 +327,6 @@ func createBlockList(reader io.ReaderAt, fileSize int) ([]int, error) { return nil, err } - //fmt.Printf("findFreeBlocks %d\n", numberOfBlocks) blockList := findFreeBlocks(volumeBitmap, numberOfBlocks) return blockList, nil diff --git a/prodos/host.go b/prodos/host.go index f463569..77365d1 100644 --- a/prodos/host.go +++ b/prodos/host.go @@ -7,8 +7,10 @@ package prodos import ( + "encoding/binary" "os" "path/filepath" + "strings" ) // AddFilesFromHostDirectory fills the root volume with files @@ -38,3 +40,90 @@ func AddFilesFromHostDirectory( return 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 +}