/*

     This is a simulation of the Pandora SVC calls (and the BBC MOS functions which it calls) that
   allows us to emulate 32016 Pandora binaries without needing to also emulate the tube and the
   BBC Micro host.  HOWEVER it is primarily a skeleton with only a few essential functions 
   currently filled in. (The ones I needed for my own purposes :-) )

     It would be a significant job to pre-emptively implement the entire BBC MOS, so I am
   uploading this to Github in this draft state (at least as of 20220809) and I expect anyone
   who needs to run the 32016 version of Bas32 to add any missing functionality that they
   need.  I'll collate independent changes and merge them here.

   This initial version at least allows writing of files from Basic and loading/saving basic
   files.  That'll get you started.  Easiest way to see what is missing is to build with
   diagnostic info enabled, and run (on Linux) as: ./bas 2> second-screen.log
   while viewing the debug output in another screen or window (I use 'screen' myself,
   I don't have a lot of time for graphical user interfaces) by something like: tail -99f second-screen.log

   Graham Toal  9 Aug 2022

   $Log: Pandora.c,v $
   Revision 1.2  2022/08/09 22:32:59  gtoal
   Added standard RCS keywords


 */

const char *rcs_version = "$Id: Pandora.c,v 1.2 2022/08/09 22:32:59 gtoal Exp gtoal $";

#include <ctype.h>
#include <inttypes.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>

#include <sys/xattr.h>

#include "32016.h"
#include "Decode.h"
#include "Trap.h"
#include "defs.h"

#include "mem32016.h"

#include "bas32.h"
#include "Pandora.h"

#ifndef TRUE
#define TRUE (0 == 0)
#endif

#ifndef FALSE
#define FALSE (0 != 0)
#endif

#ifdef TRACE_TO_FILE
int debug_emulation = TRUE;
#else
int debug_emulation = TRUE;
//int debug_emulation = FALSE;
#endif

// these are all for the code that came with bbz and are probably unused. Needs review:
int isIO = FALSE;
int zpEscapeFlag = 0, zpA = 0, zpX = 0, zpY = 0;
int userMemBottom = 0;
int env_vdu_mode = 0;
int sheilaStart = 0;
int escape = 0, pos = 0, err = 0;
int time_Millisecond = 0, timeLimitMs = 0;
int romStartAddress = 0;
FILE *file = NULL;

enum bzzerrors { errorTodo };

int OSByte(int A, int X, int Y, int *Valid);
int OSWord(unsigned char r1, unsigned int r2);

// now real variables in support of emulated calls...

int rc;
static unsigned int initial_time = 0; // remove a base value from calls to TIME.
static FILE *Handle[256] = { [ 0 ... 255 ] = NULL };

void FakeSVC(int tkno);  // Pandora SVCs will be executed by this code,
                         // not by the Pandora ROM or by a real or
                         // emulated BBC Micro. Body of FakeSVC is at the
                         // end of this file.

// There's a similar project to provide fake MOS calls for the 6502 BBC Micro at
// https://github.com/ivanizag/bbz
// - can probably reuse some of it for the OSBYTE implementation etc. (although
// it's written in go, not c)

void print_escaped(int c, FILE *f) {
  switch (c) {
            case '\n':
              fputc('\\', f);
              fputc('n', f);
              break;
            case '\t':
              fputc('\\', f);
              fputc('t', f);
              break;
            case '\f':
              fputc('\\', f);
              fputc('f', f);
              break;
            case '\r':
              fputc('\\', f);
              fputc('r', f);
              break;
            default:
              if (c < 32 || c >= 127) {
                fprintf(f, "\\x%02x", c);
              } else {
                fputc(c, f);
              }
              break;
  }
}

void vdu(int c) {
  fputc(c, stdout);
}

void impstring(int ad) {
  int c, len = read_x8(ad++);
  while (len-- > 0) {
    c = read_x8(ad++);
    vdu(c);
    if (debug_emulation) print_escaped(c, stderr);
  }
}

void FakeSVC(int tkno) {
  if (debug_emulation) fprintf(stderr, "x'%0x       ", tkno);
  /*
On entry, R0-R7 contain values to pass to SVC call
On exit,  R0-R7 contain returned data
          F=0 - no error occured
          F=1 - error occured, R1=error number
   */
  if (tkno == 0) {
      /*
        SVC &00 (0) - Indirect SVC call
        -------------------------------
        On entry:            On exit:
        R0=SVC call
        R1-R7=as called SVC  R1-R7=as called SVC
       */
    tkno = r[0] & 0xFF;
  }
  switch (tkno) {
    case OS_WRCH:  //  &01   Write character to output stream(s)
      /*
        SVC &01 (1) - OS_WRCH
        ---------------------
        On entry:            On exit:
        R1=character         R1=preserved
       */
      /*
          We need to add a state machine to gobble an entire VDU sequence before deciding
          what to do with it.

          VDU Code Summary

          Number    Meaning
          0         Null - it does nothing.
          1         Send the next character to the printer ONLY.
          2         Enable the printer.
          3         Disable the printer.
          4         Write text at the text cursor position.
          5         Write text at the graphics cursor position.
          6         Enable output to the screen.
          7         Bell - make a short 'beep'.
          8         Move the text cursor backwards one character.
          9         Move the text cursor forwards one character.
         10         Move the text cursor down one line.
         11         Move the text cursor up one line.
         12         Clear the text area - identical to CLS.
         13         Move the text cursor to the start of the current line.
         14         Enable the auto-paging mode.
         15         Disable the auto-paging mode.
         16         Clear the graphics area - identical to CLG.
         17         Define a text colour - identical to COLOUR.
         18         Define a graphics colour - identical to GCOL.
         19         Select a colour palette.
         20         Restore the default logical colours.
         21         Disable output to the screen.
         22         Select the screen mode - identical to MODE.
         23         Program characters and control the cursor.
         24         Define a graphics window.
         25         Identical to PLOT.
         26         Restore the default text and graphics windows.
         27         Send the next character to the screen.
         28         Define a text window.
         29         Set the graphics origin.
         30         Home the text cursor to the top left of the screen.
         31         Move the text cursor - identical to TAB(x,y).
        127         Backspace and delete.
       */
      if (debug_emulation) fprintf(stderr, "   ; OS_WRCH (TK_oswrch) R1=0x%02" PRIX32 "",
              r[1] & 0xFF);
      if (isprint(r[1] & 0xFF) && ((r[1] & 0xFF) >= 32)) {
        if (debug_emulation) fprintf(stderr, " ('%c')", (r[1] & 0xFF));
        vdu(r[1] & 0xFF);
      }
      if (debug_emulation) fprintf(stderr, "\n");
      /*
       o 2.1.1 OS_WRCH (TK_oswrch) ................................... 4

     2.1.1 OS_WRCH

  o  SVC code = 1

  o  Effect:

     Write character to currently selected output stream(3) selected with
     OS_BYTE call 03.

  o  On Call:

     R1(B) contains character to be written.

  o  On Return:

     F bit clear always. All registers unchanged.
       */
      F_FLAG = 0;
      pc = startpc + 2;
      break;

    case OS_STRING:  //  &02   Write string to output stream(s)
      /*
SVC &02 (2) - OS_STRING - Print String
--------------------------------------
On entry:            On exit:
R1=>text             R1=preserved
R2=length            R2=preserved
       */
      if (debug_emulation) fprintf(stderr,
                                   "   ; OS_STRING (TK_string) R1=0x%08" PRIX32 " length(R2)=0x%08" PRIX32,
                                   r[1], r[2]);

      /*
       o 2.1.2 OS_STRING (TK_string) ................................. 5

     2.1.2 OS_STRING

  o  SVC code = 2

  o  Effect:

     Write character string to the currently selected output stream(s).

  o  On Call:

      R1(D) is a pointer to a string of characters.
      R2(D) is length.

  o  On Return:

     F bit clear always. All registers unchanged.
       */
      {
        int buffer = r[1];
        int len = r[2];
        int pos = 0;
        if (debug_emulation) fprintf(stderr, "\"");
        while (pos < len) {
          int c = read_x8(buffer+pos++);
          if (debug_emulation) {
            print_escaped(c, stderr);
          }
          vdu(c);
        }
        F_FLAG = 0;
        pc = startpc + 2;
      }
      if (debug_emulation) fprintf(stderr, "\"\n");
      break;

    case OS_ASCI:  //  &03   Write char with CR-> LF/CR to output
      /*
SVC &03 (3) - OS_ASCI
---------------------
On entry:            On exit:
R1=character         R1=preserved
       */
      if (debug_emulation) fprintf(stderr, "   ; OS_ASCI (TK_osascii) R1=0x%02" PRIX32 "\n", r[1]);
      /*
       o 2.1.3 OS_ASCI (TK_osascii) .................................. 6

     2.1.3 OS_ASCI

  o  SVC code = 3

  o  Effect:

     Write character to currently selected output stream(s).  If character is
     a carriage return (ASCII 16_0D) then a line feed (ASCII 16_0A) followed
     by the carriage return is output.

  o  On Call:

     R1(B) contains character to be written.

  o  On Return:

     F bit clear always. All registers unchanged.
       */
      F_FLAG = 0;
      pc = startpc + 2;
      break;

    case OS_NEWL:  //  &04   Write LF/CR to output stream(s)
      /*
SVC &04 (4) - OS_NEWL
---------------------
On entry:            On exit:
no parameters        all preserved
       */
      if (debug_emulation) fprintf(stderr, "   ; OS_NEWL (TK_osnewl)\n");
      /*
       o 2.1.4 OS_NEWL (TK_osnewl) ................................... 7

     2.1.4 OS_NEWL

  o  SVC code  =   4

  o  Effect:

     Writes the current newline string to the output stream(s), (see
     OS_SETVDU for details)

  o  On Call:

     No call parameters.

  o  On Return:

     F bit clear always. All registers unchanged.
       */
      vdu('\n');
      F_FLAG = 0;
      pc = startpc + 2;
      break;

    case OS_RDCH:  //  &05   Read character from input stream
      /*
SVC &05 (5) - OS_RDCH
---------------------
On entry:            On exit:
no parameters        R1=character
                     Cy=carry flag
       */
      if (debug_emulation) fprintf(stderr, "   ; OS_RDCH (TK_osrdch)\n");
      /*
       o 2.1.6 OS_RDCH (TK_osrdch) ..................................  9

     2.1.6 OS_RDCH

  o  SVC code = 5

  o  Effect:

     Reads a character from the currently selected input stream. The input
     stream can be selected with OS_BYTE 16_02.

  o  On Call:

     No call parameters.

  o  On Return:

     If F bit clear then the read was successful and R1(D) contains the
     character read (in least significant byte). If F bit set then error or
     escape has been detected and R1(D) contains error code (=16_11 for
     escape).

     Notes: If an escape condition is detected than it must be acknowledged
     using an OS_BYTE call 16_7E.
       */

      r[1] = fgetc(stdin);
      F_FLAG = 0;

      pc = startpc + 2;
      break;

    case OS_BYTE:  //  &06   A large group of miscellaneous calls
    {
      /*
SVC &06 (6) - OS_BYTE
---------------------
On entry:            On exit:
R1=function          R1=
R2=first parameter   R2=returned word
R3=second parameter  R3=returned word DIV 256
                     Cy=returned carry flag
       */
      int Err, Valid;

      if (debug_emulation) fprintf(stderr,
              "   ; OS_BYTE (TK_osbyte) R1(A)=0x%02" PRIX32
              " R2(X)=0x%02" PRIX32 " R3(Y)=0x%02" PRIX32 "\n",
              r[1] & 0xFF, r[2] & 0xFF, r[3] & 0xFF);
      /*
       o 2.1.7 OS_BYTE (TK_osbyte) .................................. 10

     2.1.7 OS_BYTE

  o  SVC code = 6

  o  Effect:

     Invokes a miscellaneous set of operating system calls on the Host (the
     OSBYTE or FX commands of the BBC Microcomputer).

     The mapping between the osbyte calls described in the BBC machine user
     guide and their Pandora counterparts is:

  o  On call:

     The call function (6502 A register) is placed in R1(B).

     Parameter 1 (6502 X register) is placed in R2(B).

     Parameter 2 (6502 Y register) is placed in R3(B).

  o  On return:

     The F bit is clear if no error occurred. The validity flag (6502 C flag)
     is placed into the C bit.

     Calls type 0-16_7F return:

     R2(D) contains zero extended result (6502 X register).

     Calls type 16_80-16_FF return:

     R2(D) contains result 1 (6502 X register) in b0-b7 and result 2 (6502 Y
     register) in b8-b15.

     R3(D) contains zero extended result 2 (6502 Y register).

     Exceptions to this rule are:

     Call 16_82 - R2(D)=0 (indicating client, not host, memory)

     Call 16_83 - R2(D)=Lowest address not allocated for use by Pandora (see
     sections 3, 4).

     Call 16_84 - R2(D)=Address of the first byte of Pandora high memory
     workspace.

     Call 16_9D - changes no registers.
       */

      Err = OSByte(r[1] & 0xFF, r[2] & 0xFF, r[3] & 0xFF, &Valid);

      F_FLAG = 0;
      C_FLAG = 1;

      pc = startpc + 2;
    } break;

    case OS_WORD:  //  &07   A large group of miscellaneous calls
      /*
SVC &07 (7) - OS_WORD
---------------------
On entry:            On exit:
R1=function          Control block updated
R2=>control block
       */
      if (debug_emulation) fprintf(stderr,
              "   ; OS_WORD (TK_osword) R1(A)=0x%02" PRIX32
              " R2(X)=0x%08" PRIX32 "\n",
              r[1] & 0xFF, r[2]);
      /*
       o 2.1.8 OS_WORD (TK_osword) .................................. 11

     2.1.8 OS_WORD

  o  SVC code = 7

  o  Effect:

     Invokes a miscellaneous set of operating system calls on the HOST. Unlike
     OS_BYTE calls these calls all require a control block.

     On entry to the routine R1(B) contains the OS_WORD function required.
     R2(D) points to the control block.

  A list  of OSWORD  calls available  is in Appendix  B. Note  that the  format
  of the control  block for  OSWORD 0 is different to that  used when  calling
  OSWORD on the BBC HOST.
       */

      (void)OSWord(r[1] & 0xFF, r[2]);

      pc = startpc + 2;
      break;

    case OS_CLI:  //  &08   Pass command line to BBC to execute
      /*
SVC &08 (8) - OS_CLI
--------------------
On entry:
R1=>command string
R2=max string length
       */
      if (debug_emulation) fprintf(stderr,
              "   ; OS_CLI (TK_oscli) R1=0x%08" PRIX32 " R2=0x%08" PRIX32 "\n",
              r[1], r[2]);
      /*
       o 2.1.9 OS_CLI (TK_oscli) .................................... 12

     2.1.9 OS_CLI

  o  SVC code = 8

  o  Effect:

     Sends a line to the BBC mos command interpreter in the HOST.

  o  On Call:

     R1(D) points at command line. R2(D) is length.

  o  On Return:

     If F bit clear, command executed successfully and all registers are as on
     call.

     If F bit set, error detected whilst executing command. R1(D) contains
     error number. Error messages can be read using OS_ERROR. If the error
     code is 16_104 then LOADs from within OS_CLI have been prohibited and
     this call attempted such a LOAD (see section 2.4.6).

     If a program to be loaded and run has a valid 32016 header then the
     program is entered in supervisor mode as a control program. Otherwise it
     is entered with the same status (PSR) as caller.
       */

      F_FLAG = 0;

      pc = startpc + 2;
      break;

    // there is no 9
      
    case OS_FILE:  //  &0A   Manipulate file data and catalogue
      /*
SVC &0A (10) - OS_FILE
----------------------
On entry:            On exit:
R1=function          R1=result
R2=>filename         R2=undefined
R3=max name length   R3=undefined
R4=load              R4=load
R5=exec              R5=exec
R6=length/start      R6=length
R7=attrs/end         R7=attrs

x'a          ; OS_FILE (TK_osfile) R1=0x00 R2=0x0000698C R3=0x07 R4=0x00006C91 R5=0x000021B0 R6=0x00006C91 R7=0x00006C9B
       */
      if (debug_emulation) fprintf(stderr,
              "   ; OS_FILE (TK_osfile) R1=0x%02" PRIX32 " R2=0x%08" PRIX32
              " R3=0x%02" PRIX32 " R4=0x%08" PRIX32 " R5=0x%08" PRIX32
              " R6=0x%08" PRIX32 " R7=0x%08" PRIX32 "\n",
              r[1], r[2], r[3], r[4], r[5], r[6], r[7]);
      /*
       o 2.1.10 OS_FILE (TK_osfile) ................................. 13

     2.1.10 OS_FILE

  o  SVC code = 13

  o  Effect:

     Loads and save data to a file and reads/alters catalogue information.

  o  On Call:

     R1(B) contains a OS_FILE function number (see below)
     R2(D) points to file/directory name
     R3(D) length of name
     R4(D) contains load address
     R5(D) contains execution address
     R6(D) contains the Data start address or length
     R7(D) contains the Data end address or attributes

  o  On Return:

     F bit clear:  successful request
         register contents depend on OS_FILE function

     F bit set:    error detected by HOST whilst executing command
         R1(D) - error number returned by I/O processor (HOST).
         Error message can be read using OS_ERROR
         all other registers unchanged

     The attribute of a file is a 4 byte (32 bit) item whose bits refer to
     the state of various protection flags, and other catalogue information.
     Filing systems may not use same of the flags.

     The bottom 8 bits have the following meanings:

     bit            meaning

     7
     6     The file is executable by other users.
     5     The file is writeable by other users.
     4     The file is readable by other users.
     3     The file is locked.
     2     The file is executable by you.
     1     The file is writeable by you.
     0     The file is readable by you.

     Filing systems with date information available place it in the remaining
     24 bits (or a subset of them). Otherwise, they should be returned as 0.

     The Type in R1(D) describes the object which was found:

     Type 0 is no object found
     Type 1 is file found
     Type 2 is directory found

       */
      switch (r[1]) { /*      OS_FILE FUNCTION

      16_FF  LOAD file to given address/file's address.
             The low byte of the execution address (R5B) determines
             whether the file should be loaded to it's own address
             (if non-zero) to be the supplied load address (if zero).
             The file's catalogue information will be written into the
             registers, see item 5. */
      case 0xFF:
        {
          /*
            NS32016$ ./bas32 2>/dev/null
            BBC Basic IV for 32000 (c) Acorn 1986
            >LOAD"testbas"
            >L.
               10 REM This is a test of LOAD/SAVE
               20 PRINT PI
            >RUN
            3.14159265
           */
          // R2=>filename
          // R3=max name length
          char filename[(r[3]&0xFF)+1];
          int pos, loadaddr;
          FILE *tempHandle;
          F_FLAG = 0;
          for (pos = 0; pos < r[3]&0xFF; pos++) {
            int c = read_x8(r[2]+pos);
            filename[pos] = c;
          }
          filename[pos] = '\0';
          tempHandle = fopen(filename, "rb");
          if (tempHandle == NULL) {
            //
            /*
              F bit clear:  successful request
                  register contents depend on OS_FILE function

              F bit set:    error detected by HOST whilst executing command
                  R1(D) - error number returned by I/O processor (HOST).
                  Error message can be read using OS_ERROR
                  all other registers unchanged
            */
            F_FLAG = 1;
          }
          
          if (r[5]&0xFF != 0) {
            // Use load address from file.
            F_FLAG = 1;
            // need an error message+number
            break;
          }
          // R4=load
          // R5=exec
          // R6=length
          // R7=attrs
          loadaddr = r[4];
          // x'a          ; OS_FILE (TK_osfile) R1=0xFF R2=0x0000698C R3=0x07 R4=0x00006C91 R5=0x00000000 R6=0x000066E2 R7=0x00EFADF0
          for (;;) {
            int c = fgetc(tempHandle);
            if (c == EOF) break;
            write_x8(loadaddr,c);
            loadaddr += 1;
          }
          rc = fclose(tempHandle);
        }
        break; /*

      16_00  SAVE data to file (no wild cards are allowed in the file name).
             If start address = end address then the data is of zero length.
             The file's catalogue information will be written into the
             registers, see item 5. */
      case 0x00:
        {
          // R2=>filename
          // R3=max name length
          char filename[(r[3]&0xFF)+1];
          int pos;
          FILE *tempHandle;
          for (pos = 0; pos < r[3]&0xFF; pos++) {
            int c = read_x8(r[2]+pos);
            filename[pos] = c;
          }
          filename[pos] = '\0';
          tempHandle = fopen(filename, "wb");
          if (tempHandle == NULL) {
            //
            /*
              F bit clear:  successful request
                  register contents depend on OS_FILE function

              F bit set:    error detected by HOST whilst executing command
                  R1(D) - error number returned by I/O processor (HOST).
                  Error message can be read using OS_ERROR
                  all other registers unchanged
            */
            F_FLAG = 1;
          } else F_FLAG = 0;
          
          // R6=length/start
          // R7=attrs/end
          for (pos = r[6]; pos < r[7]; pos++) {
            int c = read_x8(pos);
            rc = fputc(c, tempHandle);
            // test rc
          }
          rc = fclose(tempHandle);
          // test rc

          // R4=load
          // R5=exec
          // Set load/exec attributes by dropping through to 16_01 "WRITE catalogue information" ..?
          // ....
#if defined(__linux__) || defined(__linux) || defined(linux)
          fprintf(stdout, "*** SETTING EXTENDED ATTRIBUTES\n");
          {
            char value[12];
            sprintf(value, "0x%08x", r[4]);
            if (setxattr(filename, "user.acorn_load", value, strlen(value), 0) == -1) perror("setxattr");
            sprintf(value, "0x%08x", r[5]);
            if (setxattr(filename, "user.acorn_exec", value, strlen(value), 0) == -1) perror("setxattr");
          }
#endif
          
        }
        break; /*

      16_01  WRITE catalogue information for file (no wild cards are allowed
             in file name).
             Length information is not alterable. */
      case 0x01:
        // use extended attributes on linux: https://en.wikipedia.org/wiki/Extended_file_attributes#Linux
        //     https://man7.org/tlpi/code/online/dist/xattr/t_setxattr.c.html
        //     https://man7.org/tlpi/code/online/dist/xattr/xattr_view.c.html
#if defined(__linux__) || defined(__linux) || defined(linux)
        fprintf(stdout, "*** SETTING EXTENDED ATTRIBUTES\n");
#endif
        // use forks in windows: https://en.wikipedia.org/wiki/Fork_(file_system)
        // otherwise use .inf files ... (but then have to remember to rename/delete/etc when the file is modified.)
        // ....
        break; /*

      16_05  READ file's catalogue information.
             The Load address (R4 D), Execution address (R3 D), Length
             in bytes (R6 D), Attributes (R7 D) and Type (in R1 D) are
             returned for the particular file. */
      case 0x05:
        // ....
        break; /*

      16_02  WRITE the load address to the catalogue information.
             Only the Load address is required. */
      case 0x02:
        // ....
#if defined(__linux__) || defined(__linux) || defined(linux)
        fprintf(stdout, "*** SETTING EXTENDED ATTRIBUTE: load address\n");
#endif
        break; /*

      16_03  WRITE the execution address to the catalogue information.
             Only the execution address is required. */
      case 0x03:
        // ....
#if defined(__linux__) || defined(__linux) || defined(linux)
        fprintf(stdout, "*** SETTING EXTENDED ATTRIBUTE: exec address\n");
#endif
        break; /*

      16_04  WRITE the attributes to the catalogue information.
             Only the attributes are required. */
      case 0x04:
        // ....
#if defined(__linux__) || defined(__linux) || defined(linux)
        fprintf(stdout, "*** SETTING EXTENDED ATTRIBUTE: catalogue information\n");
#endif
        break; /*

      16_06  DELETE the file (no wild cards are allowed in the file name)
             and return catalogue information. */
      case 0x06:
        // ....
        break;

      default:
        break;
      }
      pc = startpc + 2;
      break;

    case OS_FIND:  //  &0B   Open file for read/write/update
      /*
SVC &0B (11) - OS_FIND
----------------------
On entry:            On exit:
R1=function          R1=handle
R2=>string or handle
R3=max string length
       */
      if (debug_emulation) fprintf(stderr, "   ; OS_FIND (TK_osfind) R1=0x%08" PRIX32 "  R2=0x%04" PRIX32 " R3=0x%02" PRIX32 "\n", r[1], r[2], r[3]);
      /*
       o 2.1.11 OS_FIND (TK_osfind) ................................. 15

     2.1.11 OS_FIND

  o  SVC code = 11

  o  Effect:

     Opens a file for reading/writing/update.

  o  On Call:

     R1(B) determines the type of OS_FIND operation (see below).

  o  On Return:

     F bit clear: successful request, register contents depend on OS_FIND
        request
     F bit set    error detected by HOST while executing command.
        R1(D) contains error number returned by I/O processor.
        error message can be read using OS_ERROR
        all other registers unchanged.

  o  On Call R1(B) nonzero

     R1(B)<>0 causes a named file to be opened.
     R2(D)    points to the name of the file to be opened.
     R3(D)    is the length of the file name pointed at by R2.

     If R1(B) = 16_40 the file is opened for input only.
     If R1(B) = 16_80 the file is opened for output only.
     If R1(B) = 16_C0 the file is opened for reading and updating.

  o  On Exit:

     F bit clear
          implies that the file was successfully opened.
    R1(D) is handle for the opened file
       (which will be in the range 1..255)

     F bit set
          signals an error
    R1(D) = #X100 implies that the file could not be found.

  o  On call R1(B) zero

     R1(B) = 16_00 causes (a file)/(files) to be closed.

     If R2(B)<>0 the file whose handle is given by R2(S) is closed.
     If R2(B) = 0 all open files are closed
      (including SPOOL and EXEC files).

  o  On exit:

     F bit clear operation was successful

     F bit set operation failed

       */

      if (r[1]) {
        FILE *thisfile;
        // On Call R1(B) nonzero:
        int i;
        for (i = 1; i <= 255; i++) {
          if (Handle[i] == NULL) break;
        }
        if (i == 256) {
          // No more free handles
          // r[1] = <too many files open>
          F_FLAG = 1;
        } else {
          // i is next available handle.
          char filename[(r[3]&0xFF)+1];
          // TO DO: copy filename in from 32016 address space via r[2], and insert terminating \0 at offset r[3].
          int pos;
          for (pos = 0; pos < r[3]&0xFF; pos++) {
            int c = read_x8(r[2]+pos);
            filename[pos] = c;
          }
          filename[pos] = '\0';
          if (debug_emulation) fprintf(stderr, "fopen \"%s\" ", filename);
          if ((r[1]&0xFF) == 0x40) {
            thisfile = fopen(filename, "rb");
          } else if ((r[1]&0xFF) == 0x80) {
            thisfile = fopen(filename, "wb");
          } else if ((r[1]&0xFF) == 0xc0) {
            thisfile = fopen(filename, "wb+");
          } else {
            // We'll default to read for now.
            // however should do: r[1] = <unknown open type code>, F_FLAG = 1
            thisfile = fopen(filename, "rb");
          }
          Handle[i] = thisfile;
          if (thisfile) {
            r[1] = i;
            F_FLAG = 0;
          } else {
            // cannot open file
            r[1] = errno; // or 0x100 if simple "file not found".
            F_FLAG = 1;
          }
        }
      } else {
        // On call R1(B) zero:
        int rc;
        if ((r[2]&0xFF) == 0) {
          // Close all open files
          int i;
          for (i = 0; i <= 255; i++) {
            if (Handle[i] != NULL) {
              rc = fclose(Handle[i]);
              Handle[i] = NULL;
            }
          }
        } else {
          // crashing! Not sure what R2(S) means.  It's not a byte. R2 points somewhere.
          // Maybe it's a typo for R2(B)?
          // If R2(B)<>0 the file whose handle is given by R2(S) is closed.
          if (Handle[r[2]&0xFF] == NULL) {
            // passed handle of stream that was not open - TO DO: report
          } else {
            rc = fclose(Handle[r[2]&0xFF]);
            Handle[r[2]&0xFF] = NULL;
          }
        }
        F_FLAG = 0;
      }

      pc = startpc + 2;
      break;

    case OS_ARGS:  //  &0C   Read/write an open files attributes
      /*
SVC &0C (12) - OS_ARGS
----------------------
On entry:            On exit:
R1=function          R1=result
R2=handle            R2=preserved
R3=data              R3=data
       */
      if (debug_emulation) fprintf(stderr,
              "   ; OS_ARGS (TK_osargs) - Read/write an open files attributes "
              "R1=0x%02" PRIX32 " R2=0x%02" PRIX32 " R3=0x%08" PRIX32 "\n",
              r[1], r[2], r[3]);
      /*
       o 2.1.12 OS_ARGS (TK_osargs) ................................. 16

     2.1.12 OS_ARGS

  o  SCV code = 12

  o  Effect:

     Reads/Writes an OPEN file's attributes.

  o  On Call:

     R1(B) specifies the type of operation.
     R2(B) contains the file handle.
     R3(D) contains data to be written.

     If R2(B) is non-zero then OSARGS will do one of the following jobs on
     the file of which R2(B) is the handle:

          R1(B) = 16_00 read sequential pointer to R3(D)
          R1(B) = 16_01 writes R3(D) to sequential pointer
          R1(B) = 16_02 read extent to R3(D)
          R1(B) = 16_03 writes R3(D) to extent
          R1(B) = 16_FF ensure this file is up to date on the media.

     If the pointer is set to past the end of the file then the file will be
     padded such that intervening bytes read as nulls.

     If R2(3) is zero then OSARGS will do one of the fallowing operations on
     the filing system:

          R1(B) = 16_00 Return type of filing system in R1(D):
              0- No current filing system
              1- 1200 baud CFS
              2- 300 baud CFS
              3- ROM filing system
              4- Disc filing system
              5- Econet filing system
              6- Teletext/Prestel 'Telesoftware'

          R1(B) = 16_01 Return address of rest of command line in I/O
            processor in R3(D).

          R1(B) = 16_FF Ensure all open files (and any other necessary
            information) are up to date on the media.

  o  On Return:

     F bit clear: Successful, R1(D) and R3(D) operation specific

     F bit set:   Error, R1(D) is the error code.
       */

      F_FLAG = 0;

      pc = startpc + 2;
      break;

    case OS_BGET:  //  &0D   Get byte from file
      /*
SVC &0D (13) - OS_BGET
----------------------
On entry:            On exit:
                     R1=byte
R2=handle            Cy=carry flag
       */
      if (debug_emulation) fprintf(stderr,
              "   ; OS_BGET (TK_osbget) - Get byte from file R2=0x%02" PRIX32
              "\n",
              r[2]&0xFF);
      /*
       o 2.1.13 OS_BGET (TK_osbget) ................................. 17

     2.1.13 OS_BGET

  o  SVC code = 13

  o  Effect:

     Gets a byte from a specified OPEN file.

  o  On Call:

     R2(B) is the file handle

  o  On Return:

     F bit clear: implies a successfully completed transfer and R1(D)
        contains the byte read.

     F bit set:   An error has been detected.
        If R1(D) = 16_1FE then an attempt has been made to read past
        end of file.
       */

      r[1] = '\r';  // temp fake return value
      F_FLAG = 0;

      pc = startpc + 2;
      break;

    case OS_BPUT:  //  &0E   Put byte to a file
      /*
SVC &0E (14) - OS_BPUT
----------------------
On entry:            On exit:
R1=byte              R1=preserved
R2=handle            R2=preserved
       */
      if (debug_emulation) fprintf(stderr,
              "   ; OS_BPUT (TK_osbput) - Put byte to a file R1=0x%02" PRIX32 "  R2=0x%02" PRIX32
              "\n",
              r[1]&0xFF, r[2]&0xFF);
      /*
       o 2.1.14 OS_BPUT (TK_osbput) ................................. 18

     2.1.14 OS_BPUT

  o  SVC code = 14

  o  Effect:

     Puts a byte to specified OPEN file

  o  On Call:

     R2(B) is the file handle and R1(B) contains the byte to put.

  o  On Return:

     F bit clear: implies a successfully completed transfer
     F bit set: then error detected by HOST and R1(D) contains error number.
       */

      if (Handle[r[2]&0xFF] != NULL) {
        // fputc(), putc() and putchar() return the character written as an unsigned char cast to an int or EOF on error.
        rc = fputc(r[1]&0xFF, Handle[r[2]&0xFF]);
        if (rc == EOF) {
          r[1] = errno; // for now. actually Panos has its own error numbering scheme that we need to adhere to...
          F_FLAG = 1;
        } else {
          F_FLAG = 0;
        }
      } else {
        // file not open - need to supply an error:
        F_FLAG = 1;
      }

      pc = startpc + 2;
      break;

    case OS_GBPB:  //  &0F   Read/Write bytes to a file
      /*
SVC &0F (15) - OS_GBPB
----------------------
On entry:            On exit:
R1=function          R1=result
R2=channel           R2=updated channel
R3=address           R3=updated address
R4=number            R4=updated number
R5=pointer           R5=updated pointer
                     Cy=carry flag
       */
      if (debug_emulation) fprintf(stderr,
              "   ; OS_GBPB (TK_osgbpb) - Read/Write bytes to a file "
              "R1=0x%02" PRIX32 " R2=0x%08" PRIX32 " R3=0x%08" PRIX32
              " R4=0x%08" PRIX32 " R5=0x%08" PRIX32 "\n",
              r[1], r[2], r[3], r[4], r[5]);
      /*
       o 2.1.15 OS_GBPB (TK_osgbpb) ................................. 19

     2.1.15 OS_GBPB

  o  SVC code =15

  o  Effect:

     Write/Read a group of bytes from a specified open file.

  o  On Call:

     R1(B) determines the type of operation:
          R1(B) = 16_01 Put byte using byte offset
          R1(B) = 16_02 Put byte ignoring byte offset
          R1(B) = 16_03 Get byte using byte offset
          R1(B) = 16_04 Get byte ignoring byte offset
          R1(B) = 16_05 Read title/cycle number/option/drive
          R1(B) = 16_06 Read current directory
          R1(B) = 16_07 Read current library
          R1(B) = 16_08 Read file names

     R2(B) is the file handle
     R3(D) points to the start of the data area
     R4(D) contains the number of bytes or items to transfer
     R5(D) contains the optional value of the pointer

  o  On Return:

     F bit set: implies error detected.

     F bit clear: implies transfer completed successfully.
        Values returned are as below:

     R1(B) = 16_01 to 16_04:

         R3(D) is updated (i.e. old data address plus the amount transferred)
         R4(D) shows how much data has not been transferred
     (and is usually zero)
         R5(D) is the updated value of the pointer.

     Pandora returns the value 16_1FE in R1 if an attempt is made to read
     past the end of file.

     R1(B) = 16_05:

         This returns in the area pointed at by the data pointer the
         title/cycle number/option and drive of the currently selected disk.
         It is returned in this form:

         (title length)(title)(option)(drive)

         The cycle number, option and drive are single binary bytes.

     R1(B) = 16_06:

         This returns in the area pointed at by the data pointer the currently
         selected directory name.
         It is returned in this form:

         <length disk name><disk name>
         <length directory name><directory name><priv>

         <priv> = 16_00 =>0wner
         <priv> = 16_FF =>Public

     R1(B) = 16_07:

         This returns in the area pointed at by the data pointer the currently
         selected library name.
         It is returned in this form:

         <length disk name><disk name>
         <length library name><library name><priv>

         <priv> = 16_00 =>0wner
         <priv> = 16_FF =>Public

     R1(B) = 16_08:

         This returns the names of files in the current directory.
         The format of the control block is similar to that for sequential
         files:

         R2(B) returns the cycle number
         R3(D) is the address to put the data
         R4(D) is the file number of filenames to transfer
         R5(D) is the file pointer

        If the pointer is set to zero the search will begin with the first
        file. All registers are updated in a similar manner to the way
        pointers are updated for R1(B) = 16_01 to 16_04.
        The format of the filenames is as follows:

        <length filename 1><filename 1>
        <length filename 2><filename 2>.....

       */

      F_FLAG = 0;

      pc = startpc + 2;
      break;

    // There is no &10
      
    case OS_EXIT:  //  &11   Returns control to control program
      /*
SVC &11 (17) - OS_EXIT
----------------------
Enters current control program with R1=2
       */
      if (debug_emulation) fprintf(stderr, "   ; OS_EXIT - Returns control to control program\n");
      /*
     2.4.8 OS_EXIT

  o  SVC code = 17

  o  Effect

     Causes control to be passed back to the control program. The entry point
     at which the control program is entered is defined using OS_ENTRY. On
     entry to the control program R1=2 indicating that entry was caused by
     OS_EXIT.

  o  On Call:

     No parameters

  o  On Return:

     This call does not return.
       */

      //fprintf(stderr, "Normal exit from SVC OS_EXIT\n");
      exit(0);

      pc = startpc + 2;
      break;

    case OS_DEBUG:
      if (debug_emulation) fprintf(stderr, "   ; OS_DEBUG (TK_call_debug)\n");

      F_FLAG = 0;

      pc = startpc + 2;
      break;

    case OS_ESCAPE:  //  &14   Read value of kernel escape flag
      /*
SVC &14 (20) - OS_ESCAPE - Read Escape flag
-------------------------------------------
On entry:            On exit:
no parameters        F flag=1 - Escape state set
       */
      if (debug_emulation) fprintf(stderr,
              "   ; OS_ESCAPE (TK_read_escape) - Read value of kernel escape "
              "flag\n");
      /*
       o 2.2.9 OS_ESCAPE (TK_read_escape) ........................... 36
       */

      F_FLAG = 0;

      pc = startpc + 2;
      break;

    case OS_INLINE:  //  &15   Output inline string
      /*
SVC &15 (21) - OS_INLINE - Print inline text
--------------------------------------------
On entry:            On exit:
no parameters        all preserved
 SVC &15
 EQUB length
 EQUS "string"
       */
      if (debug_emulation) fprintf(stderr,
              "   ; OS_INLINE (TK_immediate_out) - Output inline string: \"");
      /*
       o 2.1.5 OS_INLINE (TK_immediate_out) .......................... 8

     2.1.5 OS_INLINE

  o  SVC code = 21

  o  Effect:

     Output an inline string with effect as OS_STRING.

  o  On Call:

     A byte string of format <length><string to be output> follows the SVC
     instruction in store.

  o  On Return:

     F bit clear always. All registers unchanged.
       */
      pc = startpc + 2 + read_x8(startpc + 2) + 1;
      impstring(startpc + 2);
      if (debug_emulation) fprintf(stderr, "\"\n");

      F_FLAG = 0;

      break;

    case OS_HANDLER:  //  &1E   Set routine to be called on event
      /*
SVC &1E (30) - OS_HANDLER - Set event handler
---------------------------------------------
On entry:            On exit:
R1=event number      R1=preserved
R2=offset/module     R2=preserved
   procedure descriptor
       */
      if (debug_emulation) fprintf(stderr,
              "   ; OS_HANDLER (TK_set_event_handler) - Set routine to be "
              "called on event\n");
      /*
       o 2.2.1 OS_HANDLER (TK_set_event_handler) .................... 24
       */

      F_FLAG = 0;

      pc = startpc + 2;
      break;

    case OS_ERROR:  //  &1F   Read error message from I/O processor
      /*
SVC &1F (31) - OS_ERROR - Read last error
-----------------------------------------
On entry:              On exit:
R1=>buffer             R1=preserved
R2=max size of buffer  R2=length of error
                       R3=error number
       */
      if (debug_emulation) fprintf(stderr,
              "   ; OS_ERROR (TK_read_error) - Read error message from I/O "
              "processor\n");
      /*
       o 2.2.7 OS_ERROR (TK_read_error) ............................. 34
       */

      F_FLAG = 0;

      pc = startpc + 2;
      break;

    case OS_SETVDT:  //  &28   Positions the Virtual Dispatch Table
      /*
SVC &28 (40) - OS_SETVDT - Set Virtual Dispatch Table address
-------------------------------------------------------------
On entry:            On exit:
R1=new VDT address   R1=preserved
       */
      if (debug_emulation) fprintf(stderr,
              "   ; OS_SETVDT (TK_set_vdt) - Positions the Virtual Dispatch "
              "Table\n");
      /*
       o 2.3.1 OS_SETVDT (TK_set_vdt) ............................... 27
       */

      F_FLAG = 0;

      pc = startpc + 2;
      break;

    case OS_SPLIT:  //  &32
      /*
SVC &32 (50) - OS_SPLIT
-----------------------
Just exits without doing anything
       */
      if (debug_emulation) fprintf(stderr, "   ; OS_SPLIT\n");

      F_FLAG = 0;

      pc = startpc + 2;
      break;

    case OS_ENTRY:  //  &33   Defines <break> entry address
      /*
SVC &33 (51) - OS_ENTRY - Set Control Program Entry
----------------------------------------------------
On entry:            On exit:
R1=Entry address     R1=preserved
       */
      if (debug_emulation) fprintf(stderr,
              "   ; OS_ENTRY (TK_set_control_program_entry) - Defines <break> "
              "entry address\n");
      /*
       o 2.4.1 OS_ENTRY (TK_set_control_program_entry) .............. 28
       */

      F_FLAG = 0;

      pc = startpc + 2;
      break;

    case OS_VERSION:  //  &34   Returns the version number of kernel
      /*
SVC &34 (52) - OS_VERSION - Get version
---------------------------------------
On entry:            On exit:
no parameters        R1=version*256, eg &200 for 2.00
       */
      if (debug_emulation) fprintf(stderr,
              "   ; OS_VERSION (TK_get_version) - Returns the version number "
              "of kernel\n");
      /*
       o 2.4.2 OS_VERSION (TK_get_version) .......................... 29
       */

      F_FLAG = 0;

      pc = startpc + 2;
      break;

    case OS_SETVDU:  //  &35   Install vdu handler in the I/O processor
      /*
SVC &35 (53) - OS_SETVDU - Set VDU Handler
------------------------------------------
On entry:            On exit:
R1<256 - Handler ID  R1=previous handler
R1>255 - =>handler
SVC &35 (53) - OS_SETVDU - Set VDU Handler
------------------------------------------
On entry:            On exit:
R1<256 - Handler ID  R1=previous handler
R1>255 - =>handler
       */
      if (debug_emulation) fprintf(stderr,
              "   ; OS_SETVDU (TK_set_vdu_handler) - Install vdu handler in "
              "the I/O processor\n");
      /*
       o 2.4.3 OS_SETVDU (TK_set_vdu_handler) ....................... 30
       */

      F_FLAG = 0;

      pc = startpc + 2;
      break;

    case OS_CONFIG:  //  &36   Returns the value of the H/W switches
      /*
SVC &36 (54) - OS_CONFIG - Read config setting
----------------------------------------------
On entry:            On exit:
no parameters        R1.b0=FPU present
                     R1.b1=MMU present
       */
      if (debug_emulation) fprintf(stderr,
              "   ; OS_CONFIG (TK_read_config_switches) - Returns the value of "
              "the H/W switches\n");
      /*
       o 2.4.4 OS_CONFIG (TK_read_config_switches) .................. 31
       */

      F_FLAG = 0;

      pc = startpc + 2;
      break;

    case OS_CONTROL:  //  &39   Enable/disable events 251-255
      /*
SVC &39 (57) - OS_CONTROL - Enable/Disable event
------------------------------------------------
On entry:                  On exit
R1=event number &FB..&FE   R1=preserved
R2.b0=0 - disable          R2=previous state
R2.b0=1 - enable
       */
      if (debug_emulation) fprintf(stderr,
              "   ; OS_CONTROL (TK_control_event) - enable Escape handler - "
              "Enable/disable events 251-255\n");
      /*
       o 2.2.2 OS_CONTROL (TK_control_event) ........................ 25
       */

      F_FLAG = 0;

      pc = startpc + 2;
      break;

      //--------------------------------------------------------------------------
    case OS_PRIV:  //  &37   Control passing of SVC operations
      /*
SVC &37 (55) - OS_PRIV - Enable/Disable Pandora SVCs
----------------------------------------------------
On entry:                                On exit:
R1=SVC call number                       R1=preserved
R2.b0=0 - This SVC passed on to VDT      R2=previous state
R2.b0=1 - This SVC performed by Kernel
       */
      fprintf(
          stderr,
          "   ; OS_PRIV - Control passing of SVC operations  R1=0x%02" PRIX32
          " R2&1=0x%1" PRIX32 "\n",
          r[1], r[2] & 1);
      /*
     2.4.5 OS_PRIV

  o  SVC code = 55

  o  Effect:

     Controls which SVC operations Pandora will carry out.

  o  On Call:

     R1(B) = SVC number.

     R2 bit 0 =0 -> pass this SVC on via the VDT
    =1 -> carry out this SVC in Pandora

        All other bits reserved

  o  On Return:

     F bit clear, operation successful F1(D)
        previous status of this SVC in bit 0

     F bit set, error R1(D) contains the error code
          =16_101 Attempt to modify status of non-Pandora SVC
          =16_102 Attempt to modify the status of OS_PRIV
          =16_103 Called from user mode
      */

      F_FLAG = 0;

      pc = startpc + 2;
      break;

      //--------------------------------------------------------------------------
    case OS_NOLOAD:  //  &38   Allow/disallow LOAD from within OS_CLI
      /*
SVC &38 (56) - OS_NOLOAD - No OSCLI Load
----------------------------------------
On entry:                                On exit:
R1.b0=0 - disable LOADs from OSCLI       R1=previous state
R1.b0=1 - enable LOADs from OSCLI
       */
      if (debug_emulation) fprintf(stderr,
              "   ; OS_NOLOAD (TK_no_osc1i_load) - Allow/disallow LOAD from "
              "within OS_CLI  R1&1=0x%02" PRIX32 "\n",
              r[1] & 1);
      /*
       o 2.4.6 OS_NOLOAD (TK_no_osc1i_load) ......................... 33

     2.4.6 OS_NOLOAD

  o  SVC code = 56

  o  Effect:

     Controls whether or not OS_CLI calls are allowed to cause LOADS into the
     second processor store. After this call any OS_CLI operation which
     attempts to load data into the 32016 store will fail.

  o  On Call:

     R1(D) bit 0 = 0, Disallow LOADs from OS_CLI operations.
           bit 0 = 1, Allow LOADs from OS_CLI operations.
           All other bits reserved.

  o  On Return:

     R1(D) bit 0 contains the old status.
       */

      F_FLAG = 0;

      pc = startpc + 2;
      break;

      //---------------------------------------------------------------------------
    case OS_INDIR:  //  &00   Request SVC call passed R0
      if (debug_emulation) fprintf(stderr,
              "   ; OS_INDIR (TK_indirect_svc) - Request SVC call passed R0\n");
      /*
       o 2.2.11 OS_INDIR (TK_indirect_svc) .......................... 38

     2.4.11 OS_INDIR

  o  SVC code = 0

  o  Effect:

     Calls the SVC request passed in R0.

  o  On Call:

     Registers contain parameters appropriate for the called SVC.

  o  On Return:

     Registers and flags contain return values from the called SVC.
      */
      pc = startpc + 2;
      break;

      //---------------------------------------------------------------------------
    case OS_SVR:  //  &20   Enter supervisor mode
      if (debug_emulation) fprintf(stderr,
              "   ; OS_SVR (TK_to_supervisor_mode) - Enter supervisor mode\n");
      /*
       o 2.2.10 OS_SVR (TK_to_supervisor_mode) ...................... 37

     2.4.10 OS_SVR

  o  SVC code = 32

  o  Effect:

     Clear the U bit in the PSR register.
     All other PSR are unchanged.

  o  On Call:

     No parameter

  o  On return

     F bit = 0 always
     All other register unchanged
      */

      U_FLAG = 0;

      F_FLAG = 0;

      pc = startpc + 2;
      break;

      //---------------------------------------------------------------------------
    case OS_ABORT:  //  &21   Jump through ABORT vector
      if (debug_emulation) fprintf(stderr, "   ; OS_ABORT - Jump through ABORT vector\n");

      F_FLAG = 0;

      pc = startpc + 2;
      break;

      //---------------------------------------------------------------------------
    default:
      if (debug_emulation) fprintf(stderr, "   ; *** unknown SVC ***\n");

      F_FLAG = 1;

      pc = startpc + 2;
      break;
  }
}

int osByte166to255(/* env *environment,*/ unsigned char a, unsigned char x,
                   unsigned char y);

int OSByte(int A, int X, int Y, int *Valid) {
  char *option;
  int a, x, y, newA, newX, newY; //, newP;

  option = "";
  /*

o  On return:

   The F bit is clear if no error occurred. The validity flag (6502 C flag)
   is placed into the C bit.

   Calls type 0-16_7F return:

   R2(D) contains zero extended result (6502 X register).

   Calls type 16_80-16_FF return:

   R2(D) contains result 1 (6502 X register) in b0-b7 and result 2 (6502 Y
   register) in b8-b15.

   R3(D) contains zero extended result 2 (6502 Y register).

   Exceptions to this rule are:

   Call 16_82 - R2(D)=0 (indicating client, not host, memory)

   Call 16_83 - R2(D)=Lowest address not allocated for use by Pandora (see
   sections 3, 4).

   Call 16_84 - R2(D)=Address of the first byte of Pandora high memory
   workspace.

   Call 16_9D - changes no registers.
*/
  a = A;
  x = X;
  y = Y;
  switch (a) {
    case 0x00:
      option = "Identify Operating System version";
      /*
              Entry parameters:
                      X=0 Execute BRK with a message giving the O.S. type
                      X<>0 RTS with O.S. type returned in X
              On exit,
                      X=0, OS 1.00
                      X=1, OS 1.20
      */

      if (x == 0) {
        // env_raiseError(errorTodo, "MOS as interpreted by BZZ");
      } else {
        newX = 0;
      }
      break;

    case 0x02:
      option = "Select input device";
      /*
              On entry, the value in X determines the input device(s), as
         follows: X=0 Keyboard selected, RS423 disabled. X=1 RS423 selected,
         keyboard disabled. X=3 Keyboard selected, RS423 enabled (but not
         selected.) On exit, X=0 if previous input was from the keyboard, X=1 if
         previous input was from the RS423. A is preserved, Y an C are
         undefined.
      */

      // We do nothing
      newX = 0;
      break;

    case 0x03:
      option = "Select output device";
      /*
              On entry, the value in X determines the output device to be
         selected
      */

      // env_mem_Poke(mosCharDestinations, x);
      isIO = TRUE;
      break;

    case 0x04:
      option = "Enable/disable cursor editing";
      /*
              Entry parameters: X determines editing keys' status, Y=0
      */

      // We do nothing
      break;

    case 0x05:
      option = "Select print destination";
      /*
              Entry parameters: X determines print destination
      */

      // We do nothing
      break;

    case 0x0b:
      option = "Set keyboard auto-repeat delay";
      /*
              Entry parameters: X determines delay before repeating starts
      */

      // We do nothing
      break;

    case 0x0c:
      option = "Set keyboard auto-repeat period";
      /*
              Entry parameters: X determines auto-repeat periodic interval
      */

      // We do nothing
      break;

    case 0x0f:
      option = "Flush specific buffer";
      /*
              Entry parameters: X value selects class of buffer
      */

      // We do nothing
      break;

    case 0x15:
      option = "Flush specific buffer";
      /*
              Entry parameters: X determines the buffer to be cleared
      */

      // We do nothing
      break;

    case 0x72:
      option = "Specify video memory to use on next MODE change";
      /*
              On entry:
                      X=0 force use of shadow memory on future MODE changes
                      X=1 only use shadow memory if MODE number is > 127
                      X=255 never use shadow memory even if MODE > 127 (Solidisk
         ROM/RAM Extension) On exit: X=previous setting
      */

      newX = 1;
      break;

    case 0x76:
      option = "Reflect keyboard status in keyboard LEDs";
      /*
              This call reflects the keyboard status in the state of the
         keyboard LEDs, and is normally used after the status has been changed
         by OSBYTE &CA
      */

      // We do nothing
      break;

    case 0x7c:
      option = "Clear ESCAPE condition";
      /*
              No entry parameters
              This call clears any ESCAPE condition without any further
              action. The Tube is informed if active.
              The ESCAPE flag is stored as the top bit of location &FF and
              should never be interfered with directly.
      */

      // env_mem_Poke(zpEscapeFlag, 0);
      break;

    case 0x7d:
      option = "Set ESCAPE condition";
      /*
              No entry parameters
              This call partially simulates the ESCAPE key being pressed. The
              Tube is informed (if active). An ESCAPE event is not generated.
      */

      // env_mem_Poke(zpEscapeFlag, 0x80);
      break;

    case 0x7e:
      option = "Ack detection of an ESC condition";
      /*
              If an ESCAPE condition is detected, all active buffers will be
         flushed and any open EXEC files will be closed. There are no entry
         conditions on exit, X=255 if the ESCAPE condition existed (X=0
         otherwise), A is preserved, Y and C are undefined
      */

      // escape = env_mem_Peek(zpEscapeFlag);
      if ((escape & 0x80) == 0) {
        newX = 0;
      } else {
        newX = 0xff;
      }
      // env_mem_Poke(zpEscapeFlag, 0);
      break;

    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) {
        // err = file_Seek(0, io_SeekCurrent, &pos);
        if (err) {
          // env_raiseError(errorTodo, file_Stat(&info);
          if (err) {
            // env_raiseError(errorTodo, err.Error());
          } else {
            // if (pos >= info_Size()) {
            //  newX = 1;  // EOF
            //} else {
            newX = 0;  // Not EOF
            //}
          }
        }
      }
      break;

    case 0x80:
      option = "Read ADC channel";
      /*
              Entry parameters: X determines action and buffer or channel
              On exit, for input buffers X contains the number of characters in
              the buffer and for output buffers the number of spaces remaining.
      */

      if (x == 0xff) {  // Keyboard buffer
        newX = 0;
      } else {
        // env_notImplemented("OSBYTE80 supported only for X=0xff");
      }
      break;

    case 0x81:
      /*
         Entry parameters: X and Y specify time limit in centiseconds
         On exit,
                      If a character is detected, X=ASCII value of key
         pressed, Y=0 and C=0. If a character is not detected within timeout
         then Y=&FF and C=1 If Escape is pressed then Y=&1B (27) and C=1. If
         called with Y=&FF and a negative INKEY value in X (see appendix C)
         this call performs a keyboard scan. On exit, X and Y contain &FF if
         the key being scanned is pressed and 0 otherwise.
      */

      // isIO = TRUE
      if (y < 0x80) {
        option = "Read key with time limit";
        // We will just wait the time and return that no key was pressed
        timeLimitMs = ((unsigned short)(x) + ((unsigned short)(y) << 8)) * 10;
        // env_logIO(fmt_Sprintf("Sleep(%v ms", timeLimitMs));
        // time_Sleep(time_Duration(timeLimitMs) * time_Millisecond);
        newY = 0xff;
        // newP = newP | 1;  // Set carry
        C_FLAG = 1; // Set carry
      } else if (y == 0xff && x != 0) {
        option = "Scan keyboard for key press";
        // We will just return that the key was not pressed
        newX = 0;
        newY = 0;
      } else if (y == 0xff && x == 0) {
        option = "Check machine type";
        // See: http://beebwiki.mdfs.net/INKEY
        newX = 0x28;  // BBZ, not currently reserved
      } else {
        option = "Undefined use of OSBYTE81";
      }
      break;

    case 0x82:
      /*
   Exceptions to this rule are:

   Call 16_82 - R2(D)=0 (indicating client, not host, memory)
       */
      option = "Read machine high order address";
      /*
              This call provides a 16 bit high order address for filing system
              addresses which require 32 bits. As the BBC microcomputer
              uses 16 bit addresses internally a padding value must be
              provided which associates a given address to that machine.
              On exit, X and Y contain the padding address (X-high, Y-low)
              (This address is &FFFF for the BBC microcomputer I/O
              processor)
      */

      newX = 0xff;
      newY = 0xff;
      break;

    case 0x83:
      /*
   Exceptions to this rule are:

   Call 16_83 - R2(D)=Lowest address not allocated for use by Pandora (see
   sections 3, 4).
       */
      option = "Read bottom of user mem";
      newX = (unsigned char)(userMemBottom & 0xff);
      newY = (unsigned char)(userMemBottom >> 8);
      break;

    case 0x84:
      /*
   Exceptions to this rule are:

   Call 16_84 - R2(D)=Address of the first byte of Pandora high memory
   workspace.
       */
      option = "Read top of user mem";
      newX = (unsigned char)(romStartAddress & 0xff);
      newY = (unsigned char)(romStartAddress >> 8);
      break;

    case 0x85:
      option = "Read top of user mem for mode";
      newX = (unsigned char)(romStartAddress & 0xff);
      newY = (unsigned char)(romStartAddress >> 8);
      break;

    case 0x86:
      option = "Read text cursor position";
      // Not implemented. Returns 1, 1
      newX = 1;
      newY = 1;
      break;

    case 0x87:
      option = "Read character at text cursor position";
      /*
              No entry parameters
              On exit,
              X contains character value (0 if char. not recognised)
              Y contains graphics MODE number
      */

      // Not implemented. Just returns space
      newX = ' ';
      newY = env_vdu_mode;
      break;

    case 0x8b:
      option = "Set filing system options";
      /*
              This call is directly equivalent to *OPT and it controls the
         computer’s response to errors during file operations such as LOAD and
         SAVE. On entry X contains the option number and Y contains the
         particular selected option
      */

      // TODO
      break;

    case 0x8e:
      option = "Enter language ROM";
      /*
              Entry parameters: X determines which language ROM is entered
              The selected language will be re-entered after a soft BREAK.
              The action of this call is to printout the language name and enter
         the selected language ROM at &8000 with A=1. Locations &FD and &FE in
         zero page point to the copyright message in the ROM. When a Tube is
         present this call will copy the language across to the second
         processor.
      */

      // env_initLanguage(x);
      newA = 1;
      break;

    case 0x97:
      option = "Write SHEILA";
      // env_mem_Poke(sheilaStart + (unsigned short)(x), y);
      break;

    case 0x9d:
      /*
   Exceptions to this rule are:
   Call 16_9D - changes no registers.
       */
      option = "Unknown OSBYTE";
      break;

    case 0xa0:
      option = "Read VDU variable value";
      /*
              Entry parameters: X contains the number of the variable to be read
              On exit, X contains low byte of number and Y contains the high
         byte This call reads locations &300,X and &301,X
      */

      // Not implemented. Just returns O.
      newX = 0;
      newY = 0;
      if (x == 0x09) {
        // Bottom row / Right hand column
        newX = 31;
        newY = 39;  // Pascal editor only works with 80 columns
      }
      break;

    default:
      if (a >= 0xa6) {
        /*newX, newY, option = */ osByte166to255(/*env,*/ a, x, y);
        if (*option == '\0') {
          option = "Read/write system variable";
        }
      } else {
        // Send to the other ROMS if available.
        // env_mem_Poke(zpA, a);
        // env_mem_Poke(zpX, x);
        // env_mem_Poke(zpY, y);
        // newA = serviceOSBYTE;
        // env_cpu_SetPC(procServiceRoms);
        // env_log(fmt_Sprintf("OSBYTE%02x_to_roms(X=0x%02x,Y=0x%02x)", a, x,
        // y));
        // procServiceRoms issues a 254-Bad command if the command is not
        // handled by any ROM
      }
  }

  // env_cpu_SetAXYP(newA, newX, newY, newP);

  if (*option != '\0') {
    if (debug_emulation) fprintf(stderr, "                                           ; %s\n",
            option);
    // char *msg = fmt_Sprintf("OSBYTE%02x('%s',X=0x%02x,Y=0x%02x) =>
    // (X=0x%02x,Y=0x%02x)",
    //                        a, option, x, y, newX, newY);
    // if (isIO) {
    //  env_logIO(msg);
    //} else {
    //  env_log(msg);
    //}
  }
  
  return 0;
}

int osByte166to255(/* env *environment,*/ unsigned char a, unsigned char x,
                   unsigned char y) {
  /*
    Set newValue = (oldValue AND Y) EOR X

    .osbyte166to255EntryPoint = $e99c
    TAY                                                 Y=A
    LDA .mosVariablesMinus166,Y                         read current value
    TAX                                                 preserve this in X
    AND .osbyteY                                        }
    EOR .osbyteX                                        } new value = (old value
    AND Y) EOR X STA .mosVariablesMinus166,Y                         store it
    LDA .mosVariablesMinus166+1,Y                       get value of next byte
    into A TAY                                                 Y=A RTS
   */

  // address  = mosVariablesStart + (unsigned short)(a) - 0xa6;
  // oldValue = env_mem_Peek(address);
  // newValue = (oldValue & y) ^ x;
  // updateOSVar(env, a, newValue);

  // return oldValue, env_mem_Peek(address + 1), mosVariableNames[a];
  return 0;
}

// func updateOSVar(env *environment, a uint8, value uint8) {
//  address = mosVariablesStart + (unsigned short)(a) - 0xa6;
//  env_mem_Poke(address, value);

//  if (a == 0xda) {
//    env_vdu_clearQueue();
//  }
//}

#ifdef NEVER
char *mosVariableNames[256];

void initOSVars(environment *env) {
  auto void f(unsigned char a, char *name, signed char value) {
    // updateOSVar(env, a, value);
    mosVariableNames[a] = name;
  }

  f(0xa8, "adress of extended vector table LO",
    (unsigned char)(extentedVectorTableStart & 0xff));
  f(0xa9, "adress of extended vector table HI",
    (unsigned char)(extentedVectorTableStart >> 8));
  f(0xda, "Number of items in VDU queue", 0);
  f(0xec, "Character output device status", 0);

  /*
    This location contains a value indicating the type of the last BREAK
    performed. value 0 - soft BREAK value 1 - power up reset value 2 - hard
    BREAK
  */
  f(0xfd, "Hard/soft break", 1);
}
#endif

int OSWord(unsigned char r1, unsigned int r2) {
  int Carry = 0; // do I need to set C_FLAG = 0 ?
  
  // int xy = (unsigned short)(x) + (unsigned short)(y) << 8;
  /*
                        Table of OSWORD calls

R1(B)   DESCRIPTION

00      Read a line from currently selected input.
01      Read system time.
02      Write system time.
03      Read system time interval counter.
04      Write to system time interval counter.
05      Read I/O processor.
06      Write to I/O processor.
07      Make a sound.
08      Define an envelope.
09      Read pixel value.
0A      Read_character definition for given character.
0B      Read palette value for given logical colour.
0C      Write palette value for given logical colour.
0D      Read last two graphics cursors.
0E      Read real-time clock
0F      Write to real-time clock

7D      Get DFS cycle number.
7E      Read current DFS disc size.
7F      Bash disc controller.

   */

  if (debug_emulation) fprintf(stderr,
          "                                           ; OSWord(0x%02x, "
          "0x%04x); // ",
          r1, r2);
  switch (r1) {
    case 0x00:  // Read line
      {
      /*
OSWORD call 16_00

o  Read a line from the currently selected input. R2(D) points to a buffer
   containing:

      0(R2) The address of the buffer for the input line
      4(R2) The length of the buffer
      5(R2) Input character lower bound
      6(R2) Input character upper bound

   Characters will only be entered in the buffer if they are within the
   range specified by locations 5(R2) (inclusive lower bound) and 6(R2)
   (inclusive upper bound). During input, DEL deletes the last character
   input and NAK (ctrl/u) deletes the entire input line.

o  On Exit:

   F = 0  Indicates that a carriage-return (ASCII 16_0D)
          terminated the line.

   F = 1  Indicates that an escape condition occurred.

   R3D is set to the length of the input line (including the CR if F = 0).

   https://mdfs.net/Software/Tube/32016/KernelAPI says 'control block is updated on exit'!
   does that mean the line or does it mean the line length? - maybe that's where this is failing..?

       */
      /*
              This routine accepts characters from the current input stream and
         places them at a specified location in memory. During input the
         delete code (ASCII 127) deletes the last character entered, and CTRL U
         (ASCII 21)  deletes  the  entire line.  The routine ends if RETURN is
         entered (ASCII 13) or an ESCAPE condition occurs. On exit C=0 indicates
         that RETURN (CR; ASCII code 13 or &D) ended the line. C not equal to
         zero indicates that an escape condition terminated entry. Y is set to
         the length of the line, excluding the CR if C=0.
      */
      // line = env_con_readline(&stop);
      // if (stop) {
      //  env_stop = true;
      //  return;
      //}

      int buffer = read_x32(r2);
      int maxLength = read_x8(r2+4);
      int minChar = read_x8(r2+5);
      int maxChar = read_x8(r2+6);
      int pos;

      for (pos = 0; pos < maxLength; pos++)  write_x8(buffer+pos, '\r');
      pos = 0;
      
      if (debug_emulation) fprintf(stderr, "Readline: buffer @%08x  maxlen=%d  minchar=%d maxchar=%d\n", buffer, maxLength, minChar, maxChar);
      
      for (;;) {
        int c = fgetc(stdin);
        if (c == EOF) {
          // hack for now.
          //fprintf(stderr, "Abnormal exit due to end of file on console\n");
          exit(0);
        }
        c &= 0xFF;
        if (c == '\n') c = '\r';
        write_x8(buffer+pos++, c); // buffer[pos++] = c;
        if (c == '\r') { r[3] = pos; break; }
        if (pos == maxLength-1) { // last available slot
          // force a return if we reach max capacity
          write_x8(buffer+pos, '\r');
          r[3] = maxLength;
          break;
        }
        if (!((minChar <= c) && (c <= maxChar))) {
          pos -= 1; // discard if out of range
        }
      }

      if (debug_emulation) {
        fprintf(stderr, " // Read a line from currently selected input. (Got %d chars: \"", r[3]);
        pos = 0;
        for (;;) {
          int c = read_x8(buffer+pos++);
          print_escaped(c, stderr);
          if (c == '\r') break;
        }
        fprintf(stderr, "\")");
      }
      Carry = C_FLAG = 0; // not at all sure this is even needed.
      F_FLAG = 0;
      }
      break;

    case 0x01:  // Read system clock
      /*
OSWORD call 16-01

o  Read system time

   The five byte system time is read into locations 0(R2) (lsb) to  4(R2)
   (msb).

   System time is set to zero by a hard reset.
       */
      /*
              This routine may be used to read the system clock (used for the
         TIME function in BASIC). The five byte clock value is written to the
         address contained in the X and Y registers. This clock is incremented
         every hundredth of a second and is set to 0 by a hard BREAK.
      */
      // duration = time_Since(env_referenceTime);
      // ticks = duration.Milliseconds() / 10;
      // env_mem_pokeNBytes(xy, 5, (uint64_t)(ticks));

      // env_log(fmt_Sprintf("OSWORD01('read system clock',BUF=0x%04x) => %v",
      // xy,
      //                    ticks & 0xff_ffff_ffff));
      if (debug_emulation) fprintf(stderr, " // Read system time.");
      /*
&00527E [FF8A07E2] F1 SVC     x'7          ; OS_WORD (TK_osword) R1(A)=0x01 R2(X)=0x0000698C
                                           ; OSWord(0x01, 0x698c); //  // Read system time.

         Example user code (in Imp77 with embedded assembler):

          %integerfn CPU Time {Courtesy of Mark Taunton}
            %record %format tfm(%integer low, %byte high)
            %record (tfm) T
            *MovQB _ #1, 1
            *Addr  _ T, 2
            *Svc   _ 7
            %result = T_low
          %end
       */
      {
        struct timespec now;
        if (clock_gettime(CLOCK_REALTIME, &now) == -1) {
            perror("clock gettime");
            return EXIT_FAILURE;
        } else if (debug_emulation) {
          fprintf(stderr, " sec: %ld nsec: %ld ", now.tv_sec, now.tv_nsec);
        }
        unsigned int high = now.tv_sec;
        unsigned char low = now.tv_nsec / 10000000; // should be in range 0..99
        unsigned long long int fivebytes = (unsigned long long int)high * 100LL + (unsigned long long int)low;
        low = (unsigned char)(fivebytes & 0xFFLL);
        high = (unsigned int)(fivebytes >> 8LL);
        if (initial_time == 0) initial_time = high;
        high -= initial_time;
        write_x32(r2+1, high); // byte-sex dependent. fix.
        write_x8(r2, low);
        if (debug_emulation) {
          int offset;
          fprintf(stderr, " OSWORD TIME: returns:");
          for (offset = 4; offset >= 0; offset--) {
            int c = read_x8(r2+offset);
            fprintf(stderr, " %02X", c);
          }
          fprintf(stderr, " (high to low)\n");
        }
      }
      break;

    case 0x02:  // Write system clock
      /*
OSWORD call 16_02

o  Write system time.

   The five byte system time is reinitialised with the value at locations
   0(R2) (lsb) to 4(R2) (msb).
       */
      /*
              This routine may be used to set the system clock to a five byte
         value contained in memory at the address contained in the X and Y
         registers.
      */
      // ticks = env_mem_peekNBytes(xy, 5);
      // duration = time_Duration(ticks * 10 * uint64(time_Millisecond));
      // env_referenceTime = time_Now();
      // env_referenceTime = env_referenceTime_Add(duration * -1);

      // env_log(fmt_Sprintf("OSWORD02('write system clock',TICKS=%v)", ticks));
      if (debug_emulation) fprintf(stderr, " // Write system time.");
      break;

    case 0x03:  // Read Interval timer
      /*
OSWORD call 16_03

o  Read time interval counter.

   The system time interval counter is a five byte incrementing counter. It
   is reset to zero by any reset. When the system time interval counter
   crosses zero an event is generated. The five byte interval counter is
   read into locations 0(R2) (lsb) to 4(R2) (msb).
       */
      /*
              In addition to the clock there is an interval timer which is
         incremented every hundredth of a second. The interval is stored in five
         bytes pointed to by X and Y.
      */
      // duration = time_Since(env_lastTimerUpdate);
      // timer = env_timer + uint64(duration.Milliseconds() / 10);
      // env_mem_pokeNBytes(xy, 5, uint64(timer));

      // env_log(fmt_Sprintf("OSWORD03('read interval timer',BUF=0x%04x) => %v",
      //                    xy, timer & 0xff_ffff_ffff));
      if (debug_emulation) fprintf(stderr, " // Read system time interval counter.");
      break;

    case 0x04:  // Write interval timer
      /*
OSWORD call 16_04

o  Writes to the system time interval counter.

   The five interval counter is initialised with the value at locations
   0(R2) (lsb) to 4(R2) (msb). (Note that the counter is an incrementing
   counter, thus a value of 16_FFFFFFFFFF would give a time interval of 1
   centisecond).
       */
      /*
              On entry X and Y point to five locations which contain the new
         value to which the clock is to be set. The interval timer increments
         and may cause an event when it reaches zero. Thus setting the timer to
         &FFFFFFFFFE would cause an event after two hundredths of a second.
      */
      // env_timer = env_mem_peekNBytes(xy, 5);
      // env_lastTimerUpdate = time_Now();

      // env_log(
      //    fmt_Sprintf("OSWORD04('write interval timer',TIMER=%v)",
      //    env_timer));
      if (debug_emulation) fprintf(stderr, " // Write to system time interval counter.");
      break;

    case 0x05:  // Read I/O processor memory
      /*
OSWORD call 16_05

o  Read a byte from I/O processor.

   Uses a 32 bit address taken from locations 0(R2) to 3(R2), result
   returned in 4(R2).

   Useful  when  used  in conjunction with the tube - the tube will only
   pass the byte if the address is in the top range.
       */
      /*
              A byte of I/O processor memory may be read across the Tube using
         this call. A 32 bit address should be contained in memory at the
         address contained in the X and Y registers. On exit, The byte read will
         be contained in location XY+4.
      */
      // address = (unsigned int)(env_mem_peekWord(xy)) + (unsigned
      // int)(env_mem_peekWord(xy + 2)) << 16; value = env_mem_Peek(((unsigned
      // short)(address))); env_mem_Poke(xy + 4, value);

      // env_logIO(fmt_Sprintf(
      //    "OSWORD05('Read I/O processor memory',ADDRESS=0x%08x) => 0x%02x",
      //    address, value));
      if (debug_emulation) fprintf(stderr, " // Read I/O processor.");
      break;

    case 0x06:  // Write I/O processor memory
      /*
OSWORD call 16_06

o  Write a byte to I/O processor.


   Uses a 32 bit address taken from locations 0(R2) to 3(R2). Byte for
   writing is taken from 4(R2).
       */
      /*
              This call permits I/O processor memory to be written across the
         Tube. A 32 bit address is contained in the parameter block addressed by
         the X and Y registers and the byte to be written should be placed in
         XY+4.
      */
      // address = (unsigned int)(env_mem_peekWord(xy)) + (unsigned
      // int)(env_mem_peekWord(xy + 2)) << 16; value = env_mem_Peek(xy + 4);
      // env_mem_Poke((unsigned short)(address), value);

      // env_log(fmt_Sprintf(
      //    "OSWORD06('Write I/O processor memory',ADDRESS=0x%08x,VAL=0x%02x)",
      //    address, value));
      if (debug_emulation) fprintf(stderr, " // Write to I/O processor.");
      break;

    case 0x07:  // Sound command
      /*
OSWORD call 16_07

o  Make a sound.

   The 8 bytes at locations 0(R2) to 7(R2) are treated as 4 2-byte values,
   0(R2) lsb, 1(R2) msb etc. These four values define the sound effect. See
   the sound specification in the new user guide for more details.
       */
      /*
              This routine takes an 8 byte parameter block addressed by the X
         and Y registers. The 8 bytes of the parameter block may be considered
         as the four parameters used for the SOUND command in BASIC.
      */
      // channel = env_mem_peekWord(xy);
      // amplitude = int8(env_mem_peekWord(xy + 2));
      // pitch = env_mem_peekWord(xy + 4);
      // duration = env_mem_peekWord(xy + 6);
      // TODO: play sound

      // env_log(fmt_Sprintf(
      //    "OSWORD07('Sound command',CHAN=%v,AMPL=%v,PITCH=%v,DUR=%v)",
      //    channel, amplitude, pitch, duration));
      if (debug_emulation) fprintf(stderr, " // Make a sound.");
      break;

    case 0x08:  // Define an envelope
      /*
OSWORD call 16_08

o  Define an envelope.

   The 13 bytes at locations 1(R2) to 13(R2) are used to define the envelope
   indicated by 0(R2). See the sound specification in the new user guide for
   more details.
       */
      /*
              The ENVELOPE parameter block should contain 14 bytes of data which
         correspond to the 14 parameters described in the ENVELOPE command. This
         call should be entered with the parameter block address contained in
         the X and Y registers.
      */
      // number = env_mem_Peek(xy);
      // TODO: define envelope

      // env_log(fmt_Sprintf("OSWORD08('Define envelope',NUMBER=%v)", number));
      if (debug_emulation) fprintf(stderr, " // Define an envelope.");
      break;

    case 0x09:
      /*
OSWORD call 16_09

o  Read pixel value.

   The 4 bytes at locations 0(R2) are 3(R2) are treated as 2 2-byte values.
   0(R2) (lsb) and 1(R2) (msb) represent an X-coordinate, 2(R2) (lsb) and
   3(R2) (msb) represent a Y-coordinate. The value of the pixel at the
   addressed coordinate is deposited in location 4(R2). An invalid
   coordinate returns a value of 16_FF.
       */
      if (debug_emulation) fprintf(stderr, " // Read pixel value.");
      break;

    case 0x0A:
      /*
OSWORD call 16_0A

o  Read character definition far a given character.

   The character definition for the character given in location 0(R2) is
   deposited in locations 1(R2) (top row) to 8(R2) (bottom row).
       */
      if (debug_emulation) fprintf(stderr, " // Read_character definition for given character.");
      break;

    case 0x0B:
      /*
OSWORD call 16_0B

o  Read the palette value for a given logical colour.

   The 4-byte physical colour definition for the logical colour given at
   location 0(R2) is deposited at locations 1(R2) to 4(R2).
       */
      if (debug_emulation) fprintf(stderr, " // Read palette value for given logical colour.");
      break;

    case 0x0C:
      /*
OSWORD call 16_0C

o  Write the palette value for a given logical colour.

   The 4-byte physical colour definition at locations 1(R2) to 4(R2) is
   assigned to the logical colour 0(R2).
       */
      if (debug_emulation) fprintf(stderr, " // Write palette value for given logical colour.");
      break;

    case 0x0D:
      /*
OSWORD call 16_0D

o  Read graphics cursor positions.

   The last two positions visited by the graphics cursor are returned as X,
   Y, X, Y (each two byte integers).
       */
      if (debug_emulation) fprintf(stderr, " // Read last two graphics cursors.");
      break;

    case 0x0E:
      /*
OSWORD call 16_0E

o  Read the real-time clock.
       */
      if (debug_emulation) fprintf(stderr, " // Read real-time clock");
      break;

    case 0x0F:
      /*
OSWORD call 16_0F

o  Write to the real-time clock.
       */
      if (debug_emulation) fprintf(stderr, " // Write to real-time clock");
      break;

    case 0x7D:
      /*
OSWORD call 16_7D

o  Read the DFS cycle number (of currently selected directory). The cycle
   number is returned in the location (R2 D).
       */
      if (debug_emulation) fprintf(stderr, " // Get DFS cycle number.");
      break;

    case 0x7E:
      /*
OSWORD call 16_7E

o  Read the DFS disc size (in bytes of the currently selected disc).

   The four byte disc size is returned in locations 0(R2) to 3(R2).
       */
      if (debug_emulation) fprintf(stderr, " // Read current DFS disc size.");
      break;

    case 0x7F:
      /*
OSWORD call 16_7F

o  Access the disc controller.

   (R2 D) points at this control block:

           OFFSET

           0 +------------------------------+
             |            Drive             |
           1 +------------------------------+
             |  Address to put to/get from  |
           5 +------------------------------+
             |     Number of parameters     |
           6 +------------------------------+
             |           Command            |
           7 +------------------------------+
             |                              |
             |          Parameters          |
             |                              |
         7+p +------------------------------+
             |            Result            |
         8+p +------------------------------+

   The drive byte will select drives 0-3 in the standard DFS manner (i.e.
   drive 2 is side two of drive 0). If set to 16_FF however no drive will be
   selected. It should be noted that no command will work if the drive is
   not spinning. The address bytes are where data should be taken from/put to
   depending on the type of command. If no data transfer takes place, then
   they may be ignored.

   (R2 D) + 5 determines the number of parameters for the disc controller
   after the command byte.

   The command byte is the command for the disc controller command register.
   Bit 6 should be set and bit 7 should be unset as the drive selection is
   done automatically and these bits are used for it. Thus the command for
   read sector becomes 16_53 and write sector 16_4B. The parameter bytes are
   sent to the disc controller parameter register after the command byte is
   sent to the command register.

   The result returned at the end of the operation is read from the result
   register and stored in the next byte after the parameters.

   Note that the direction of any possible data transfer is determined by
   bit 4 of the command byte: a 1 means that you are reading from the disc,
   a 0 means that you are writing to it.
       */
      if (debug_emulation) fprintf(stderr, " // Bash disc controller.");
      break;

    default:

      // Send to the other ROMS if available.
      // env_mem_Poke(zpA, a);
      // env_mem_Poke(zpX, x);
      // env_mem_Poke(zpY, y);
      // env_cpu_SetAXYP(serviceOSWORD, x, y, p);

      // env_cpu_SetPC(procServiceRoms);
      // env_log(fmt_Sprintf("OSWORD%02x_to_roms(X=0x%02x,Y=0x%02x)", a, x, y));

      // procServiceRoms issues a 254-Bad command if the command is not handled
      // by any ROM
      break;
  }

  if (debug_emulation) fprintf(stderr, "\n");
  return Carry;
}
