Skip to content

Commit

Permalink
File operations OSFIND, OSARGS, OSBPUT, OSBGET
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanizag committed Aug 9, 2021
1 parent 6fa2f88 commit 0f07394
Show file tree
Hide file tree
Showing 12 changed files with 318 additions and 45 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ References:
bbz [flags] [filename]
```

This first arguments is the filename of the ROM to run. With no arguments it
`filename` is the filename of the ROM to run. With no arguments it
runs the BBC Basic ROM in `BASIC.ROM`.

Avaliable flags (to put before the ROM filename if present):
Expand Down Expand Up @@ -72,9 +72,10 @@ HEY
>X
Mistake
>^Csignal: interrupt
$ ls -l TEST
>*HOST ls -l TEST
-rw-r--r-- 1 casa casa 14 jul 30 20:04 TEST
>^Csignal: interrupt
```

Log of the MOS calls (excluding the most verbose output API calls):
Expand Down
71 changes: 48 additions & 23 deletions bbz.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"bufio"
"fmt"
"io"
"os"
"time"
"unicode"
Expand Down Expand Up @@ -77,33 +78,57 @@ func RunMOSEnvironment(romFilename string, cpuLog bool, apiLog bool, apiLogIO bo

env.log(fmt.Sprintf("BREAK(ERR=%02x, '%s')", faultNumber, faultString))

case 0xffda: // OSARGS
case 0xffce: // OSFIND
execOSFIND(&env)

case 0xffd4: // OSBPUT
/*
Call address &FFDA Indirected through &214
This routine reads or writes an open file's attributes.
On entry, X points to a four byte zero page control block.
Y contains the file handle as provided by OSFIND, or zero.
The accumulator contains a number specifying the action required.
Write a single byte to an open file
On entry, Y contains the file handle, as provided by OSFIND. A contains the
byte to be written. The byte is placed at the point in the file designated by
the sequential pointer.
*/
if y == 0 {
switch a {
case 0:
// Returns the current filing system in A
filingSystem := 9 // Host filing system
env.log(fmt.Sprintf("OSARGS('Get filing system',A=%02x,Y=%02x)= %v", a, y, filingSystem))
case 0xff:
/*
Update all files onto the media, ie ensure that the latest copy
of the memory buffer is saved.
We will do nothing.
*/
env.log("OSARGS('Update all files onto the media')")
default:
env.notImplemented(fmt.Sprintf("OSARGS(A=%02x,Y=%02x)", a, y))
file := env.getFile(y)
if file != nil {
buf := []uint8{a}
_, err := file.Write(buf)
if err != nil {
env.raiseError(errorTodo, err.Error())
}
} else {
env.notImplemented(fmt.Sprintf("OSARGS(A=%02x,Y=%02x)", a, y))
}
env.log(fmt.Sprintf("OSBPUT(FILE=%v,VAL=0x%02x)", y, a))

case 0xffd7: // OSBGET
/*
Get one byte from an open file
This routine reads a single byte from a file.
On entry, Y contains the file handle, as provided by OSFIND. The byte is
obtained from the point in the file designated by the sequential pointer.
On exit, A contains the byte read. C is set if the end of the file has been
reached, and indicates that the byte obtained is invalid.
*/
value := uint8(0)
eof := false
file := env.getFile(y)
if file != nil {
buf := make([]uint8, 1)
_, err := file.Read(buf)
if err == io.EOF {
// EOF, set C
eof = true
env.cpu.SetAXYP(a, x, y, p|1)
} else if err != nil {
env.raiseError(errorTodo, err.Error())
} else {
// Valid, clear C
value = buf[0]
env.cpu.SetAXYP(buf[0], x, y, p&0xfe)
}
}
env.log(fmt.Sprintf("OSBGET(FILE=%v)=0x%02x,EOF=%v", y, value, eof))

case 0xffda: // OSARGSexecOSARGS
execOSARGS(&env)

case 0xffdd: // OSFILE: Load or save a complete file. BPUG page 446
execOSFILE(&env)
Expand Down
39 changes: 33 additions & 6 deletions environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ const (
breakEntryPoint uint16 = 0xffb0 // Invented as there is not an address defined
vectorReset uint16 = 0xfffc
vectorBreak uint16 = 0xfffe

maxFiles = 5
errorTodo uint8 = 123 // TODO: find proper error number
)

type environment struct {
Expand All @@ -42,6 +45,9 @@ type environment struct {
timer uint64 // Only 40 bits are used
lastTimerUpdate time.Time

// files
file [maxFiles]*os.File

// behaviour
stop bool

Expand Down Expand Up @@ -120,6 +126,23 @@ func (env *environment) loadMos() {

}

///////////////////////////
// File handling
///////////////////////////
func (env *environment) getFile(handle uint8) *os.File {
i := handle - 1
if i < maxFiles && env.file[i] != nil {
return env.file[i]
}

env.raiseError(222, "Channel")
return nil
}

///////////////////////////
// Errors and logs
///////////////////////////

func (env *environment) raiseError(code uint8, msg string) {
/*
The BBC microcomputer adopts a standard pattern of bytes
Expand Down Expand Up @@ -160,6 +183,10 @@ func (env *environment) notImplemented(feature string) {
env.log(msg)
}

///////////////////////////
// Memory Access
///////////////////////////

func (env *environment) putStringInMem(address uint16, s string, terminator uint8, maxLength uint8) {
// maxLength not including terminator
var i int
Expand Down Expand Up @@ -208,18 +235,18 @@ func (env *environment) pokeWord(address uint16, value uint16) {
env.mem.Poke(address+1, uint8(value>>8))
}

func (env *environment) peek5bytes(address uint16) uint64 {
func (env *environment) peeknbytes(address uint16, n int) uint64 {
ticks := uint64(0)
for i := uint16(0); i < 5; i++ {
for i := n - 1; i >= 0; i-- {
ticks <<= 8
ticks += uint64(env.mem.Peek(address + i))
ticks += uint64(env.mem.Peek(address + uint16(i)))
}
return ticks
}

func (env *environment) poke5bytes(address uint16, value uint64) {
for i := uint16(0); i < 5; i++ {
env.mem.Poke(address+i, uint8(value&0xff))
func (env *environment) pokenbytes(address uint16, n int, value uint64) {
for i := 0; i < n; i++ {
env.mem.Poke(address+uint16(i), uint8(value&0xff))
value >>= 8
}
}
77 changes: 77 additions & 0 deletions osArgs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package main

import (
"fmt"
"io"
)

func execOSARGS(env *environment) {
a, x, y, _ := env.cpu.GetAXYP()

/*
Call address &FFDA Indirected through &214
This routine reads or writes an open file's attributes.
On entry, X points to a four byte zero page control block.
Y contains the file handle as provided by OSFIND, or zero.
The accumulator contains a number specifying the action required.
*/

if y == 0 {
// Operations without file handle
switch a {
case 0: // Returns the current filing system in A

filingSystem := 9 // Host filing system
env.log(fmt.Sprintf("OSARGS('Get filing system',A=%02x,Y=%02x)= %v", a, y, filingSystem))

case 0xff: // Update all files onto the media
// Do nothing.
env.log("OSARGS('Update all files onto the media')")

default:
env.notImplemented(fmt.Sprintf("OSARGS(A=%02x,Y=%02x)", a, y))
}

return
}

file := env.getFile(y)
if file == nil {
env.log(fmt.Sprintf("OSARGS(A=%02x,FILE=%v)='bad handler'", a, y))
}

switch a {
case 0: // Read sequential pointer of file (BASIC PTR#)
pos, err := file.Seek(0, io.SeekCurrent)
if err != nil {
env.raiseError(errorTodo, err.Error())
} else {
env.pokenbytes(uint16(x), 4, uint64(pos))
}
env.log(fmt.Sprintf("OSARGS('Get PTR#',FILE=%v)=%v", y, pos))

case 1: // Write sequential pointer of file
pos := int64(env.peeknbytes(uint16(x), 4))
_, err := file.Seek(pos, io.SeekStart)
if err != nil {
env.raiseError(errorTodo, err.Error())
}
env.log(fmt.Sprintf("OSARGS('Set PTR#',FILE=%v,PTR=%v)", y, pos))

case 2: // Read length of file (BASIC EXT#)
info, err := file.Stat()
if err != nil {
env.raiseError(errorTodo, err.Error())
} else {
env.pokenbytes(uint16(x), 4, uint64(info.Size()))
}
env.log(fmt.Sprintf("OSARGS('Get EXT#',FILE=%v)=%v", y, info.Size()))

case 0xff: // Update this file to media
// Do nothing.
env.log(fmt.Sprintf("OSARGS('Update file to media',FILE=%v)", y))

default:
env.notImplemented(fmt.Sprintf("OSARGS(A=%02x,FILE=%v)", a, y))
}
}
28 changes: 28 additions & 0 deletions osByte.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"fmt"
"io"
"time"
)

Expand Down Expand Up @@ -41,6 +42,33 @@ func execOSBYTE(env *environment) {
*/
newX = 0

case 0x7f:
option = "Check for end-of-file on an opened file"
/*
Entry parameters: X contains file handle
On exit,
X<>0 If end-of-file has been reached
X=0 If end-of-file has not been reached
*/
file := env.getFile(x)
if file != nil {
pos, err := file.Seek(0, io.SeekCurrent)
if err != nil {
env.raiseError(errorTodo, err.Error())
} else {
info, err := file.Stat()
if err != nil {
env.raiseError(errorTodo, err.Error())
} else {
if pos >= info.Size() {
newX = 1 // EOF
} else {
newX = 0 // Not EOF
}
}
}
}

case 0x80:
option = "Read ADC channel"
/*
Expand Down
6 changes: 3 additions & 3 deletions osCLI.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ func execOSCLI(env *environment) {

case "*HOST":
if len(params) == 0 {
env.raiseError(1, "Command missing for *HOST")
env.raiseError(errorTodo, "Command missing for *HOST")
} else {
cmd := exec.Command(params[0], params[1:]...)
stdout, err := cmd.Output()
if err != nil {
env.raiseError(1, err.Error())
env.raiseError(errorTodo, err.Error())
}
fmt.Println(string(stdout))
}
Expand Down Expand Up @@ -117,7 +117,7 @@ func execOSCLI(env *environment) {
execOSBYTE(env)

default:
env.raiseError(1 /* todo */, "Bad command")
env.raiseError(254, "Bad command")
}

if msg != "" {
Expand Down
4 changes: 2 additions & 2 deletions osFile.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func execOSFILE(env *environment) {
data := env.getMemSlice(startAddress, filesize)
err := ioutil.WriteFile(filename, data, 0644)
if err != nil {
env.raiseError(1, err.Error())
env.raiseError(errorTodo, err.Error())
}
case 0xff: // Load file into memory
/*
Expand All @@ -45,7 +45,7 @@ func execOSFILE(env *environment) {
}
data, err := ioutil.ReadFile(filename)
if err != nil {
env.raiseError(1, err.Error())
env.raiseError(errorTodo, err.Error())
}
// NOTE: There is no maxLength?
env.storeSliceinMem(loadAddress, uint16(len(data)), data)
Expand Down
Loading

0 comments on commit 0f07394

Please sign in to comment.