diff --git a/osCLI.go b/osCLI.go index eab7598..06ed777 100644 --- a/osCLI.go +++ b/osCLI.go @@ -162,20 +162,16 @@ func execOSCLI(env *environment) { break } filename := params[0] - loadAddress := uint16(0x3000) // TODO: retrieve from the metadata + loadAddress := loadAddressNull if len(params) >= 2 { - i, err := strconv.ParseInt(params[1], 16, 16) + i, err := strconv.ParseInt(params[1], 16, 32) if err != nil { env.raiseError(252, "Bad address") break } - loadAddress = uint16(i) - } - res, _ := loadFile(env, filename, loadAddress) - if res != osFileFound { - env.raiseError(214, "File not found") - break + loadAddress = uint32(i) } + loadFile(env, filename, loadAddress) // case "LINE": case "MOTOR": @@ -191,14 +187,14 @@ func execOSCLI(env *environment) { break } filename := params[0] - loadAddress := uint16(0x3000) // TODO: retrieve from the metadata - execAddress := uint16(0x3100) - res, _ := loadFile(env, filename, loadAddress) - if res != osFileFound { - env.raiseError(214, "File not found") - break + attr := loadFile(env, filename, loadAddressNull) + if attr.fileType == osFileFound { + if attr.hasMetadata { + env.cpu.SetPC(uint16(attr.executionAddress)) + } else { + env.raiseError(errorTodo, "Missing metadata file") + } } - env.cpu.SetPC(execAddress) case "ROM": execOSCLIfx(env, 0x8d, strings.Split(args, ",")) diff --git a/osFile.go b/osFile.go index 7c1991d..d02f047 100644 --- a/osFile.go +++ b/osFile.go @@ -5,24 +5,42 @@ import ( "fmt" "io/ioutil" "os" + "strconv" + "strings" ) const ( osNotFound uint8 = 0 osFileFound uint8 = 1 osDirectoryFound uint8 = 2 + + cbLoadAddress uint16 = 0x2 + cbExecutionAddress uint16 = 0x6 + cbStartAddressOrSize uint16 = 0xa + cbEndAddressOrAttributes uint16 = 0xe + + loadAddressNull uint32 = ^uint32(0) + executionAddressNull uint32 = ^uint32(0) ) +type fileAttributes struct { + fileType uint8 + fileSize uint32 + hasMetadata bool + loadAddress uint32 + executionAddress uint32 +} + func execOSFILE(env *environment) { a, x, y, p := env.cpu.GetAXYP() // See: http://beebwiki.mdfs.net/OSFILE controlBlock := uint16(x) + uint16(y)<<8 filenameAddress := env.mem.peekWord(controlBlock) - loadAddress := env.mem.peeknbytes(controlBlock+0x2, 4) - executionAddress := env.mem.peeknbytes(controlBlock+0x6, 4) - startAddress := env.mem.peeknbytes(controlBlock+0xa, 4) - endAddress := env.mem.peeknbytes(controlBlock+0xe, 4) + loadAddress := uint32(env.mem.peeknbytes(controlBlock+cbLoadAddress, 4)) + executionAddress := uint32(env.mem.peeknbytes(controlBlock+cbExecutionAddress, 4)) + startAddress := uint32(env.mem.peeknbytes(controlBlock+cbStartAddressOrSize, 4)) + endAddress := uint32(env.mem.peeknbytes(controlBlock+cbEndAddressOrAttributes, 4)) filename := env.mem.getString(filenameAddress, 0x0d) filesize := endAddress - startAddress @@ -40,8 +58,10 @@ func execOSFILE(env *environment) { err := ioutil.WriteFile(filename, data, 0644) if err != nil { env.raiseError(errorTodo, err.Error()) + newA = osNotFound + } else { + newA = osFileFound } - newA = 1 // File found case 5: option = "File info" /* @@ -49,16 +69,15 @@ func execOSFILE(env *environment) { type returned in the accumulator. The information is written to the parameter block. */ - info, err := os.Stat(filename) - - if err == nil { - filesize = uint64(info.Size()) - if info.IsDir() { - newA = 2 // Directory found - } else { - newA = 1 // File found + attr := getFileAttributes(env, filename) + if attr.fileType != osNotFound { + env.mem.pokenbytes(controlBlock+cbStartAddressOrSize, 4, uint64(attr.fileSize)) + if attr.hasMetadata { + env.mem.pokenbytes(controlBlock+cbLoadAddress, 4, uint64(attr.loadAddress)) + env.mem.pokenbytes(controlBlock+cbExecutionAddress, 4, uint64(attr.executionAddress)) } } + newA = attr.fileType case 0xff: // Load file into memory option = "Load file" @@ -72,36 +91,105 @@ func execOSFILE(env *environment) { */ useLoadAddress := (executionAddress & 0xff) == 0 if !useLoadAddress { - env.notImplemented("Loading files on their own load address") + loadAddress = loadAddressNull } - newA, filesize = loadFile(env, filename, uint16(loadAddress)) + + attr := loadFile(env, filename, loadAddress) + env.mem.pokenbytes(controlBlock+cbStartAddressOrSize, 4, uint64(attr.fileSize)) + newA = attr.fileType default: env.notImplemented(fmt.Sprintf("OSFILE(A=%02x)", a)) } - env.mem.pokenbytes(controlBlock+0xa, 4, filesize) - env.mem.pokeWord(controlBlock+0x3, 0x00 /*attributes*/) + env.mem.pokeWord(controlBlock+cbEndAddressOrAttributes, 0x00 /*attributes*/) env.cpu.SetAXYP(newA, x, y, p) - env.log(fmt.Sprintf("OSFILE('%s',A=%02x,FCB=%04x,FILE=%s,SIZE=%v) => (A=%v,SIZE=%v)", - option, a, controlBlock, filename, filesize, newA, filesize)) + env.log(fmt.Sprintf("OSFILE('%s',A=%02x,FCB=%04x,FILE=%s) => %v", + option, a, controlBlock, filename, newA)) } -func loadFile(env *environment, filename string, loadAddress uint16) (uint8, uint64) { +func loadFile(env *environment, filename string, loadAddress uint32) fileAttributes { + attr := getFileAttributes(env, filename) + if attr.fileType == osNotFound { + return attr + } + if attr.fileType == osDirectoryFound { + env.raiseError(errorTodo, "Directory found") + return attr + } + + if loadAddress == loadAddressNull { + if !attr.hasMetadata { + env.raiseError(errorTodo, "Missing metadata file") + attr.fileType = osNotFound + return attr + } + loadAddress = uint32(attr.loadAddress) + } data, err := os.ReadFile(filename) + if err != nil { + env.raiseError(errorTodo, err.Error()) + attr.fileType = osNotFound + return attr + } + + // NOTE: There is no maxLength? + env.mem.storeSlice(uint16(loadAddress), uint16(len(data)), data) + return attr +} + +func getFileAttributes(env *environment, filename string) fileAttributes { + var attr fileAttributes + + fileInfo, err := os.Stat(filename) if errors.Is(err, os.ErrNotExist) { + attr.fileType = osNotFound env.raiseError(214, "File not found") - return osNotFound, 0 + return attr } if err != nil { + attr.fileType = osNotFound env.raiseError(errorTodo, err.Error()) - return osNotFound, 0 + return attr } - // NOTE: There is no maxLength? - env.mem.storeSlice(uint16(loadAddress), uint16(len(data)), data) - filesize := uint64(len(data)) - return osFileFound, filesize + attr.fileSize = uint32(fileInfo.Size()) + attr.fileType = osFileFound + if fileInfo.IsDir() { + attr.fileType = osDirectoryFound + } + + /* + Search metadata file "{filename}.inf" looking like: + $.BasObj 003000 003100 005000 00 CRC32=614721E1 + */ + attr.hasMetadata = false + data, err := os.ReadFile(filename + ".inf") + if errors.Is(err, os.ErrNotExist) { + return attr + } + parts := strings.Fields(string(data)) + if len(parts) < 3 { + env.log(fmt.Sprintf("Invalid format for metadata file %s.inf, missing fields", filename)) + return attr + } + + i, err := strconv.ParseInt(parts[1], 16, 32) + if err != nil { + env.log(fmt.Sprintf("Invalid format for metadata file %s.inf, bad load address", filename)) + return attr + } + attr.loadAddress = uint32(i) + + i, err = strconv.ParseInt(parts[2], 16, 32) + if err != nil { + env.log(fmt.Sprintf("Invalid format for metadata file %s.inf, bad exec address", filename)) + return attr + } + attr.executionAddress = uint32(i) + + attr.hasMetadata = true + return attr }