overview components memorymap downloads possibilities contact
 
schematics firmware resources
The Amélie project
Downloads - some notes on Porting

Introduction

This document outlines issues and notes involved in porting the assembler and the emulator to different platforms.
 

Porting the assembler

6502asm should not present any special challenges. It incorporates selectively-compiled code for the DOS environment where you cannot create an array of 64K without using _far calls. On other platforms (which would include Win32 as a console application), it just assumes an array of this size is okay. Output is to file and/or screen. Nothing special, so no 'odd' libraries required.

 

Porting the emulator

AmélieEm, on the other hand, makes extensive use of the functions provided by conio and also some functions provided by dos, not to mention the somewhat grotty idea of directly peeking in memory to see if a key has been pressed (because kbhit() is amazingly slow!). You'll need to provide your own implementations of these things.
The basic rule is that only the minimum amount of system-specific code will be put into AmélieEm (i.e. filing system differences, specific pragmas, etc). For the user interface, a "dos.h" and "conio.h" should be written that fakes the Borland TurboC versions.

Here is an example of AmélieEm (v0.01af) running under RISC OS:

Example of AmélieEm running under RISC OS.

 

Here is the dos library emulation I cobbled together for RISC OS. Note that it does the translations, so the existing emulator code does not need to be modified for a RISC OS build. It is called dos.c and it requires a header file to define these functions globally.

/* Hacky implementation of required DOS.h functions */

#include "kernel.h"
#include "dos.h"

void delay(int d)
{
   _kernel_swi_regs r;
   int  tickthen, ticknow;

   d = d / 10; /* milli -> centi */

   _kernel_swi(0x20042, &r, &r);    // XOS_ReadMonotonicTime
   tickthen = r.r[0];
   tickthen + = d;

   do
   {
      _kernel_swi(0x20042, &r, &r); // XOS_ReadMonotonicTime
      ticknow = r.r[0];
   } while (ticknow < tickthen);

   return;
}


int  peek(int seg, int addr)
{
   /* This is used to peek in DOS memory for the
      length of the keyboard queue; as it is MUCH
      faster than calling kbhit()...

      The command used (in the emulator) is:
        if ( peek(0x0040, 0x001A) !=
             peek(0x0040, 0x001C) ) [then ...]
   */

   _kernel_swi_regs r;
   int  reply;

   if ((seg == 0x0040) && (addr == 0x001A))
   {
       /* Return SIZE of keyboard buffer - HARDCODED! */
       return 255;
   }

   if ((seg == 0x0040) && (addr == 0x001C))
   {
       /* Return FREE SPACE in keyboard buffer */
       r.r[0] = 128;
       r.r[1] = 255;
       _kernel_swi(0x20006, &r, &r);  // XOS_Byte
       reply = 255 - r.r[1];
       return reply;
   }

   /* Anything else? Ignore! */
   return 0;
}

What may perhaps need a little bit of an explanation is the 0x0040:001A / 0x0040:001C stuff.
You see, the kbhit() call is very slow. What is much faster is to look at the keyboard queue markers to examine the pointers to the start and end of the queue. If they are the same - voila! - no key has been pressed. Since the addresses given are BIOS addresses, we need to fake the peek() call, and fake also the response to interrogating the keyboard status by returning something relevant.
Under RISC OS we cannot easily return the address of the keyboard buffer markers, so we return the free space and the byte remaining - both of which will be "255" if there has been no key pressed.
This would be a place to patch in support for other hardware (COM1:, LPT1:, etc) however please be aware that direct memory access is somewhat unfriendly - in fact I wouldn't be surprised if this makes XP wet itself. I don't have XP so I can't tell you...

Why is kbhit() slow? Like any useful operating system, DOS supports the concept of streams - "keyboard" input could be coming from a file. We can bypass that as it is rather unlikely anybody would use AmélieEm in such a manner, and in doing so we can greatly speed things up.

Some other PC BIOS addresses which may prove to be useful:

0040:0000  Address of COM1
0040:0008  Address of LPT1
0040:0011  Equipment list (number of COM/LPT ports).
0040:001E  32 byte circular keyboard buffer
0040:0097  Keyboard LED flags.

For RISC OS, I would advocate using the documented OS interfaces as the system is cleaner and has a lower latency than DOS/BIOS on a PC. The only exception is a possible speed enhancement within the keyboard checking, because the OS software interrupt OS_Byte 128, 255 actually does the following (in RISC OS 2):

Osbyte80
        AND     R1, R1, #&FF

        TST     R1, #&80            ; is it ADVAL(-n)
        BEQ     AdvalPositive       ; no, then do adval(+ve)
        EOR     R1, R1, #&FF        ; convert to buffer number
        CLC                         ; (C:=0 and V:=0)
        TEQ     R1, #Buff_Mouse     ; is it mouse (only input buf >= 2) ?
        CMPNE   R1, #Buff_RS423Out  ; C=1 <=> output buffer, so count spaces
                                    ; V=0, so will do count not purge

        ADR     R14, MyOsbyte80
CnpEntry
        Push    "R10,R12,R14"
        MOV     R10, #CNPV
        B       GoVec

Since GoVec calls CallAVector, it may make sense to try calling the CnpV vector directly via OS_CallAVector. This is invariably something best handled in assembler.
Important! The CallAVector routine in the OS takes the vector number in R10 (as shown in the code above), however the OS_CallAVector SWI takes the vector number in R9. This is correct, the SWI entry point pushes the vector into R10, sets up some stuff, calls CallAVector, then tidies up after...
The code is:

;************************************************
; SWI to call a vector
;************************************************
CallAVector_SWI ; R9 is the vector number (!!)
        Push    "lr"
        MOV     R10, R9
        ORR     R14, R14, #SVC_mode
        TEQP    PC, R14             ; restore caller CCs
        BL      CallVector
        MOV     R10, PC, LSR #28    ; restore CCs
        Pull    "lr"
        BIC     lr, lr, #&F0000000
        ORR     lr, lr, R10, LSL #28
        ExitSWIHandler

I'll leave it up to you to decide if it is better calling vector or using the OS_Byte call. For now, I'll do it the OS_Byte way as it is easier. Things may have changed in later versions of RISC OS due to separation of mouse and keyboard drivers from the kernel, and maybe more with HAL implementations, such that the "documented" method is the only sane approach.

 

Next is the header for the conio emulation. This is conio.h...

/* Hacky implementation of required CONIO.h functions */

#define BLACK           0
#define LIGHTRED        1
#define LIGHTGREEN      2
#define YELLOW          3
#define LIGHTBLUE       4
#define LIGHTMAGENTA    5
#define LIGHTCYAN       6
#define WHITE           7

#define RED             9
#define GREEN           10
#define BROWN           11
#define BLUE            12
#define MAGENTA         13
#define CYAN            14
#define LIGHTGRAY       15
#define DARKGRAY        8

#define _NOCURSOR       0
#define _NORMALCURSOR   1

typedef struct text_info
{
   unsigned char winleft;
   unsigned char wintop;
   unsigned char winright;
   unsigned char winbottom;
   unsigned char attribute;
   unsigned char normattr;
   unsigned char currmode;
   unsigned char screenheight;
   unsigned char screenwidth;
   int curx;
   int cury;
};

extern void gotoxy(int, int);
extern void textcolor(int);
extern void textbackground(int);
extern void cprintf(char *, ...);

extern void putch(int);
extern void clrscr(void);
extern int  wherex(void);
extern int  wherey(void);
extern void clreol(void);

extern int  getch(void);
extern int  kbhit(void);

extern void _setcursortype(int);
extern void sound(int);
extern void nosound(void);

/* RISC OS's VDU output is a singular entity... */
#define cputs cprintf

extern void gettextinfo(struct text_info *);
extern void window(int, int, int, int);
extern void gettext(int, int, int, int, char *);
extern void puttext(int, int, int, int, char *);
extern int  textattr(int);
extern void highvideo(void);
extern void lowvideo(void);
extern void normvideo(void);

What may require explanation here is the cprintf() function. Under DOS, if you set up a colour, you should use cprintf() to print text in that colour, normal printf() prints in light grey as normal. Under RISC OS, all screen output is performed graphically so changing colour affects subsequent output, be it writing text or drawing lines. The only thing that isn't affected is the outline font system, but we make no use of this.

Because the screen output is graphical, it is not really possible to read a text character and 'attribute' from screen the way DOS does it. Therefore the conio emulation creates a copy of the screen information in memory, which is then used alongside actual screen output.

Here is the lovely amazingly hacky implementation of conio.c...
Note that it has one important dependency - when the emulator starts, it assumes that you are in the correct screenmode, with the correct colourset, and the correct display font. The !Run file loads the font and sets the screenmode. A little BASIC program run before the main executable sets the colours. Note also that colours are set using VDU calls. If we build a WimpPalette and set that, it'll affect all subsequent operation - including the Desktop. It is a pain in the ass on a RiscPC because the 'enhanced' palette manager no longer includes a way to restore the 'default' palette. Note that there is practically no error checking. We 'assume' valid values will always be given, as the code 'works' with the DOS (Borland) version of the library.

Important - the Amélie source includes a number of #ifdef sections to cater for differences between DOS and RISC OS (and the compilers used), however providing the same level of support for all screen output would turn the code into a total mess. Therefore it has been decided (an "executive decision"!) that all screen and keyboard stuff within Amélie will be via conio, and any ports of Amélie will have to figure out a way to emulate, fake, or otherwise rip-off how conio works.

There are annotations within this source...

/*
   Name   : AmélieEm
   Version: v0.01af
   Date   : Saturday, 4th August 2007

   Module : conio [ > > RISC OS only < < ]
   Purpose: Hacky implementation of CONIO and CGA text display for RISC OS.

   Creator: Rick Murray <heyrick -at- merseymail -dot- com>
*/


#include "stdio.h"
#include "kernel.h"
#include "conio.h"
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "amem.h"


static int lastforecolour = WHITE;
static int lastbackcolour = BLACK;

The use of these colours is mostly historical. The first version of this library changed screen mode for every clrscr, but this caused unacceptable flicker. The second version set the screen mode on the first call. Now that we support colour and use the PC-ANSI-like display font, we simply clear the screen and leave setting the mode/colours/font up to an external program. The clrscr() command uses the colours, so don't get rid of them just yet...
I chose to use an external program as this provides the user with greater flexibility to tweak the colours and/or mode (a listing is given at the end). The colours chosen are the values extracted from a saved screen from the Aleph1 !PC (PC emulator) software, which should roughly correspond to those values which could be extracted from a CGA display driver.

static int ctrlgetch      = 0;
static _kernel_swi_regs r;
static unsigned char scrtext[ (80 * 25) ];
static unsigned char scrattr[ (80 * 25) ];
static int  cursorx = 0;
static int  cursory = 0;
static int  currattr = 0;
static unsigned char vdudata[16];

extern int  single_stepped; /* it is part of Tracey */


#define textwrite(x, y, z) ( scrtext[((y) * 80) + (x)] = (z) )
#define textread(x, y)     ( scrtext[((y) * 80) + (x)] )
#define attrwrite(x, y, z) ( scrattr[((y) * 80) + (x)] = (z) )
#define attrread(x, y)     ( scrattr[((y) * 80) + (x)] )


#define XOS_NewLine               0x20003
#define XOS_ReadC                 0x20004
#define XOS_Byte                  0x20006
#define XOS_Exit                  0x20011
#define XOS_EnterOS               0x20016
#define XOS_Module                0x2001E
#define XOS_Claim                 0x2001F
#define XOS_ReadEscapeState       0x2002C
#define XOS_RemoveCursors         0x20036
#define XOS_RestoreCursors        0x20037
#define XOS_CallEvery             0x2003C
#define XOS_ChangeEnvironment     0x20040
#define XOS_ReadMonotonicTime     0x20042
#define XOS_WriteN                0x20046
#define XIIC_Control              0x20240
#define XWimp_CommandWindow       0x600EF
#define XWimp_ReadSysInfo         0x600F2
#define XSound_Control            0x60189
#define XParallel_HardwareAddress 0x62EC0

Most of these aren't used right now, only NewLine, ReadC, Byte, RemoveCursors, and RestoreCursors.
In any case, we can now call SWIs by name.


void gotoxy(int x, int y)
{
   /* Correct for CONIO starting at 1,1 */
   x--;
   y--;

   /* Set our copy position */
   cursorx = x;
   cursory = y;

   /* Now set the actual output position */
   _kernel_oswrch(31);
   _kernel_oswrch(x);
   _kernel_oswrch(y);

   return;
}

Slight differences in technique - a display starts at 1,1 under DOS, or 0,0 under RISC OS! Therefore the x--,y--.
We could have done something like  __kernel_oswrch(--x); cursorx = x; etc, the code in either case should be more or less the same, just in a different order.


void absgotoxy(int x, int y)
{
   /* Absolute go-to-x,y : assumes position given is relative to 0,0 */

   /* Set our copy position */
   cursorx = x;
   cursory = y;

   /* Now set the actual output position */
   _kernel_oswrch(31);
   _kernel_oswrch(x);
   _kernel_oswrch(y);

   return;
}


void textcolor(int c)
{
   int  currattr = 0;

   /* Set our copy attribute */
   currattr = attrread(cursorx, cursory);
   currattr &= 0xF0;       /* mask to keep background */
   currattr |= (c & 15);   /* push in new foreground  */
   attrwrite(cursorx, cursory, currattr);
   /* THEORY: We *could* alter the colour of any given output simply by altering the
              attributes, and NOT reprinting the output afterwards. Thankfully
              AmélieEm does not do this, so we don't need to try to support it! :-)
   */

   /* Now set the actual output colour */

   _kernel_oswrch(17);
   _kernel_oswrch(c);
   lastforecolour = c;

   return;
}


void textbackground(int c)
{
   int  currattr = 0;
   int  ccopy = c;

   /* Background colours can ONLY be 'dim', unless black... */
   if (ccopy > 0)
   {
     if (ccopy < 8)
        ccopy += 8;
   }

   /* Set our copy attribute [and see above comment] */
   currattr = attrread(cursorx, cursory);
   currattr &= 0x0F;                /* mask to keep foreground */
   currattr |= ((ccopy & 15) << 4); /* push in new background  */
   attrwrite(cursorx, cursory, currattr);
   currattr = currattr;

   /* Now set the actual output colour */
   _kernel_oswrch(17);
   _kernel_oswrch(ccopy + 128);
   lastbackcolour = ccopy;

   return;
}

As you can now see, ASCII codes under 32 'do stuff' to the display. So to change colour, for example, we write character 17, followed by the colour number. There are other ways to change colour, I think writing codes to the display is probably the quickest - though I'll have to look at the VDU driver source sometime to check it doesn't do something dopey like figure out the colour to set and then call the generic SWI... :-)
Later: The VDU driver code is a well-structured nightmare. I think I'll pass on following where that's going!


void cprintf(char *message, ...)

{
   /* Accepts printf() style parameters */
   int  items;
   char *out;
   va_list variables;
   int recurse;

   if ((out = (char *)malloc(1024)) == NULL) /* Allocate a block of memory */
      return;

   va_start(variables, message);
   items = vsprintf(out, message, variables);

   for (recurse = 0; recurse < strlen(out); recurse++)
   {
      /* omit linefeeds - much uses CRLF for DOSisms, RISC OS works otherwise */
      if ( out[recurse] != 13 )
      {
         if ( out[recurse] < 32 )
         {
            /* filter out control codes [shouldn't happen!] */
            if ( out[recurse] == 10 )
               putch(10); // for screen output tracking
         }
         else
         {
            putch( out[recurse] ); // for screen output tracking
         }
      }
   }

   va_end(variables);
   free(out);

   return;
}


We implement our own cprintf() so that we can sanely cater for the CRLF line termination used on DOS systems (and within the rest of AmélieEm's source), plus also allowing us to filter out unwanted control codes.
Note that we allocate a buffer of 1024 bytes. If the expanded output is larger, Bad Things Will Happen. I will see if VarArgs can return a length in a later version of the library.
Important: For all output where we are not managing the cursor ourselves, we must call putch().

void putch(int b)
{
   /* We mingle phantom-screen and real-screen positioning */

   if (b == 10)
   {
      /* New line */
      cursory++;
      cursorx = 0;

      /* Scrolling is NOT supported! */
      if (cursory > 24)
         cursory = 0; /* wrap to top instead */

      /* Handle actual output positioning here */
      absgotoxy(cursorx, cursory);
   }
   else
   {
      /* Place cursor */
      absgotoxy(cursorx, cursory);

      /* Output byte */
      _kernel_oswrch(b);

      /* And paste it into the screen copy, along with attribute */
      currattr = ((lastbackcolour << 4) | lastforecolour);
      textwrite(cursorx, cursory, b);
      attrwrite(cursorx, cursory, currattr);

      /* Now faff with the cursor */
      cursorx++;
      if (cursorx > 79)
      {
         cursorx = 0;
         cursory++;

         if (cursory > 24)
            cursory = 0;
      }
   }

   return;
}

Be aware that putch() does not filter unwanted control codes - watch passing anything other than ASCII <10>.


The cputs() code used to be here. This has been removed, with cputs() aliased to actually call cprintf().


void clrscr(void)

{
   int  lp = 0;

   /* Clear the ACTUAL screen */
   textcolor(lastforecolour);
   textbackground(lastbackcolour);
   _kernel_oswrch(12); /* CLS */

   /* Clear the array - ourselves, for speed */
   currattr = ((lastbackcolour << 4) + lastforecolour);
   for (lp = 0; lp < ((25 * 80) - 1); lp++)
   {
      scrtext[lp] = 32; /* all your bytes are spaces / all your base are mine :-) */
      scrattr[lp] = currattr;
   }

   cursorx = 0;
   cursory = 0;

   return;
}


int  wherex(void)
{
   /* Read from our copy position */

   // int  r0, r1, r2;
   // r0 = 134; r1 = 0; r2 = 0;
   // _kernel_osbyte(r0, r1, r2);
   // return (r2 + 1); 

   return (cursorx + 1); // +1 correction for CONIO 1,1 start offset
}


int  wherey(void)
{
   /* Read from our copy position */

   // int  r0, r1, r2;
   // r0 = 134; r1 = 0; r2 = 0;
   // _kernel_osbyte(r0, r1, r2);
   // return (r2 + 1); 

   return (cursory + 1); // +1 correction for CONIO 1,1 start offset
}


void clreol(void)
{
   int xpos, ypos;

   xpos = wherex();
   ypos = wherey();

   for (int loop = xpos; loop <= 80; loop++)
      putch(' ');
   gotoxy(xpos, ypos); /* does CONIO restore? */

   return;
}

RISC OS has no built-in clear-to-end-of-line, but it's easy to fake. I'm not sure if the DOS version restores the original cursor position. I've done so just in case, because if I'm wrong, subsequent code will probably do it. If I didn't, and it did, things could look messy.
Note that we define loop within the for() - you'll need a C99 compiler (ISO/IEC 9899:1999) for this!


The early versions of Amélie under RISC OS were badly stunted by numerous key operations not working, so the keyboard interfacing has been completely redesigned.
Here goes (remember ctrlgetch is a global variable):

int  getch(void)
{
   /* A better attempt at emulating the way DOS returns '0' then control key. */
   if (ctrlgetch != 0)
   {
      int reply = ctrlgetch;
      ctrlgetch = 0;
      return reply;
   }

   ctrlgetch = 0;

   _kernel_swi(XOS_ReadC, &r, &r);

   /* A 'control' key? - WE ONLY SUPPORT THOSE WE USE. */
   switch (r.r[0])
   {
      case 141: /* Alt-T */
                ctrlgetch = 20;
                break;

      case 187: /* Alt-X [Alt-F4 not so easily done...] */
                ctrlgetch = 45;
                break;

      case 139: /* Cursor UP [Page Up] */
                r.r[0] = 121; /* OS_Byte 121 -> Direct keyboard scan */
                r.r[1] = 191; /* 63 EOR &80 - PageUp scan code */
                _kernel_swi(XOS_Byte, &r, &r);
                if (r.r[1] == 0xFF)
                {
                   ctrlgetch = 73; /* Page Up */
                }
                else
                {
                   ctrlgetch = 72; /* Cursor Up */
                }
                break;

      case 138: /* Cursor DOWN [Page Down] */
                r.r[0] = 121;
                r.r[1] = 206; /* 78 EOR &80 - PageDown scan code */
                _kernel_swi(XOS_Byte, &r, &r);
                if (r.r[1] == 0xFF)
                {
                   ctrlgetch = 81; /* Page Down */
                }
                else
                {
                   ctrlgetch = 80; /* Cursor Down */
                }
                break;

      case 136: /* Cursor LEFT */
                ctrlgetch = 0; /* NOT CURRENTLY REQUIRED */
                break;

      case 137: /* Cursor RIGHT */
                ctrlgetch = 0; /* NOT CURRENTLY REQUIRED */
                break;

      case 241: /* F1 */
      case 242: /* F2 */
      case 243: /* F3 */
      case 244: /* F4 */
      case 245: /* F5 */
      case 246: /* F6 */
      case 247: /* F7 */
      case 248: /* F8 */
                ctrlgetch = r.r[0] - 182; /* make DOS Fkey */
                break;

      default/* Ignore it */
                ctrlgetch = 0;
                break;
   }

   if (ctrlgetch != 0)
      return 0;

   return r.r[0];
}

Okay... What's going on here is essentially we are looking for a 'control' keypress, Alt-key, cursor, etc. When we have such a keypress, we preserve the value that we want to return and actually return zero.
This allows us to mimic the DOS behaviour where Alt-X is handled by two calls to getch() - the first returning zero and the second returning forty five.
For normal keypresses, we simply return the key ASCII code.
The big switch statement is concerned with looking for the keys that we're interested in and returning a suitable DOS-alike code. You will notice that Cursor Left and Right are listed but do nothing. This is because Amélie does not use Left/Right - but if it does in the future, these definitions will save me looking up the key codes.
The only complication is in our cursor mode, the same codes are returned for Cursor Up and Page Up; likewise Cursor Down and Page Down. Therefore it is necessary to perform a direct scan of the keyboard to tell which it actually is.


int  kbhit(void)
{
   int  reply;

   r.r[0] = 128; /* OS_Byte 128 -> Read buffer status */
   r.r[1] = 255; /*               255 = keyboard FREE */
   _kernel_swi(XOS_Byte, &r, &r);
   reply = 255 - r.r[1];

   return reply;
}

This differs from the DOS library. kbhit() is supposed to return TRUE or FALSE. We return the number of bytes used in the keyboard buffer. Empty = 0 = False. Anything else is not-FALSE. This will work so long as comparisons with kbhit() check against FALSE. If it is a problem, just insert code to say "if (reply != 0) reply= 1;".


void _setcursortype(int c)
{
   if (c == _NOCURSOR)
   {
      _kernel_swi(XOS_RemoveCursors, &r, &r);
   }

   if (c == _NORMALCURSOR)
   {
      _kernel_swi(XOS_RestoreCursors, &r, &r);
   }

   return;
}

Changing the cursor type is not really necessary under RISC OS as we only ever use this call to set the cursor on or off. That's easy enough to do...


void sound(int f)
{
   /* We don't support sound-until-no-sound, so
      just make a beep... */


   return;
}

The DOS sound system works on the principle of make a sound at frequency 'f' until a new frequency is supplied or nosound() is called. The Acorn system works on the principle of make a sound on channel 'c' frequency 'f' for duration 'd' at volume 'v'. I say "Acorn" as the BBC micro did it like this in the early '80s. Anyway, I don't feel like emulating the DOS system as AmélieEm only uses it for warning bleeps. Thus, calling VDU7 (tty bell) will suffice.


void nosound(void)
{
   return;
}


void gettextinfo(struct text_info *ti)
{
   /* We don't fully support windows, so return full screen info */

   ti->winleft = 1;
   ti->wintop = 1;
   ti->winright = 80;
   ti->winbottom = 25;
   ti->attribute = currattr;
   ti->normattr = currattr; /* how does this differ from 'attribute'? */
   ti->currmode = 3;        /* C80 */
   ti->screenheight = 25;
   ti->screenwidth = 80;
   ti->curx = cursorx;
   ti->cury = cursory;

   return;
}

This is supposed to return information on the screen (overall) and the text window, however the text window is only supported in a rudimentary manner (at this time) so this function will always return details of the whole screen.
Under RISC OS, the current mode is fixed to "CO80". This is because we are returning what we are pretending to be, rather than the actual RISC OS screen mode.


void window(int left, int top, int right, int bottom)
{
   _kernel_oswrch(28); /* set text window position */
   _kernel_oswrch( (left - 1) );
   _kernel_oswrch( (bottom - 1) );
   _kernel_oswrch( (right - 1) );
   _kernel_oswrch( (top - 1) );

   return;
}

At this time, we only support enough to get a text window on-screen, for the 'welcome' display and any error report boxes.


void gettext(int left, int top, int right, int bottom, char *destin)
{
   int  startcol = (left - 1);
   int  endcol   = (right - 1);
   int  startrow = (top - 1);
   int  endrow   = (bottom - 1);
   int  thiscol  = 0;
   int  thisrow  = 0;
   int  destoff  = 0;

   /* Layout of CONIO block is "<text><attr><text><attr><text><attr>[etc]". */

   for (thisrow = startrow; thisrow <= endrow; thisrow++)
   {
      for (thiscol = startcol; thiscol <= endcol; thiscol++)
      {
         destin[destoff++] = textread(thiscol, thisrow);
         destin[destoff++] = attrread(thiscol, thisrow);
      }
   }

   return;
}

Read from the screen display buffer (actually, our virtual copy) into the memory block pointed to by destin.


void puttext(int left, int top, int right, int bottom, char *destin)
{
   int  startcol = (left - 1);
   int  endcol   = (right - 1);
   int  startrow = (top - 1);
   int  endrow   = (bottom - 1);
   int  thiscol  = 0;
   int  thisrow  = 0;
   int  destoff  = 0;
   int  ttxt     = 0;
   int  tatr     = 0;

   for (thisrow = startrow; thisrow <= endrow; thisrow++)
   {
      for (thiscol = startcol; thiscol <= endcol; thiscol++)
      {
         ttxt = destin[destoff++];
         tatr = destin[destoff++];

         /* set up the array */
         textwrite(thiscol, thisrow, ttxt);
         attrwrite(thiscol, thisrow, tatr);

         /* only output to actual screen if NOT single-stepping - speeds things up */
         if (single_stepped != TRUE)
         {
            /* now copy to the actual screen */
            lastforecolour = (tatr & 15);
            lastbackcolour = ( (tatr >> 4) & 15 );

            /* For speed, we write the VDU data directly in one go */
            vdudata[0] = 31;       /* cursor position   */
            vdudata[1] = thiscol;
            vdudata[2] = thisrow;
            vdudata[3] = 17;       /* foreground colour */
            vdudata[4] = lastforecolour;
            vdudata[5] = 17;       /* background colour */
            vdudata[6] = (lastbackcolour + 128);
            vdudata[7] = ttxt;     /* byte to display   */
            r.r[0] = (int)vdudata;
            r.r[1] = 8;
            _kernel_swi(XOS_WriteN, &r, &r);
            /* If only VDU stream active, OS_WriteN uses direct access to VDU
               drivers so it faster than repeated calls to OS_WriteC (and I
               would assume _kernel_oswrch?). Refer to PRM 1-522. */

         }
      }
   }

   return;
}

And in this case, we copy from the block pointed to by destin into screen memory. As RISC OS works differently, we have to not only paste the data into our virtual screen, but we also have to write it to the actual screen.
The exception to this is if we are single-stepping, in which case we disable writes to the actual screen. This is because on a PC, the screen update writes, at most 4000 bytes, to the display adaptor's memory. RISC OS, working graphically, would need to write around 70K for the same display. Then we have the overheads of translating the PCish output to a form suitable for RISC OS in the first place.
That's not to say my 40MHz RiscPC is slow (compared to my 466MHz PC!), it's just that the display briefly flickers. It's bloody annoying. So I've disabled it. :-)


void textattr(int attr)
{
   int  ourattr = (attr & 127); /* mask out "blink" bit, NOT SUPPORTED */

   lastforecolour = (ourattr & 15);
   lastbackcolour = ( (ourattr >> 4) & 15 );
   textcolor(lastforecolour);
   textbackground(lastbackcolour);

   return ; /* return what? previous attributes? */
}

Set the foreground and background colours at one time.


void highvideo(void)
{
   /* Push in 'intense' video colours */

   lastforecolour -= 8;
   if (lastforecolour < 8)
      lastforecolour += 8; /* in case ALREADY bright */

   return;
}


void lowvideo(void)
{
   /* Push in 'non-intense' video colours */

   lastforecolour += 8;
   if (lastforecolour > 15)
      lastforecolour -= 8; /* in case ALREADY dim */

   return;
}


void normvideo(void)
{
   /* Restore default colours - dim white on black, like DOS when booted */

   lastforecolour = LIGHTGRAY;
   lastbackcolour = BLACK;
   textcolor(lastforecolour);
   textbackground(lastbackcolour);

   return;
}

In any case, I don't think it is used, just added for completeness.

 

MODE 46, by the way, is a 16 colour 80x25 mode. This is as close as we have for DOS's CO80 without poking the video hardware ourselves. It will appear letterboxed on multisync/SVGA/etc monitors. I'll look into this some other time...

 

I hope this has given an insight into some of the issues involved in porting the software to a different platform. Some ports, such as Linux i386 will be easier than others - if most of this can't be done with curses(), it should be possible to call BIOS functions (OS permitting) as the display hardware is the same. For other systems (Apple, Amiga, etc), you'll have to swot up on the display driver system and fake it as best you can.
The main places to check are apisys.c and tracey.c within AmélieEm. There may be other bits dotted around in the source, but if you get Tracey to compile, chances are you'll have the stuff used by the other bits already in place. In any case, the quickest way to find out what needs to be done on your system is to either port the above, or just run a compile and see what it reports as an unknown function call!

 

Here, for your reference, is a listing of the SetPalette program:

REM This program sets up the display mode, the
REM colour set, and the text style for AmélieEm.
REM It is a separate program so it may be fully
REM customised for different hardware, differing
REM tastes (in colour, etc etc).
REM
REM Rick, 2007/08/02
REM

DIM mblk% 24

REM Set screen mode to use; is 80x25 (text)
SYS "XOS_SWINumberFromString",,"ScreenModes_ReadInfo" TO swi%
IF (swi% = &487C0) THEN
  REM RiscPC - try setting the mode according to
  REM our defined mode. If you have not already
  REM merged this mode with your base modefile,
  REM you should consider doing so; as it offers
  REM a higher refresh rate (less flicker) than
  REM the standard 640x200 definition (which
  REM runs at 60Hz instead of our 90Hz).
  REM
  REM IMPORTANT: Some monitors (AKF60, AKF85
  REM according to !MakeModes) may have some
  REM problems with the line rate and experience
  REM difficulty with ANY 640x200 mode unless
  REM it is the internally-letterboxed version.
  REM In this case, remove ALL of this code and
  REM set "MODE 46" and wish you had a more
  REM capable monitor. :-)

  mblk%!0  = 1
  mblk%!4  = 640
  mblk%!8  = 200
  mblk%!12 = 2
  mblk%!16 = 90
  mblk%!20 = -1
  SYS "XOS_ScreenMode", 0, mblk% TO ; f%
  IF ((f% AND 1) = 1) THEN
    REM Our 'Amélie' mode is not installed, try
    REM *any* 640x200 (4bpp) mode.

    mblk%!16 = -1
    SYS "XOS_ScreenMode", 0, mblk% TO ; f%
    IF ((f% AND 1) = 1) THEN
      REM Unexpected error - do it the 'old' way...
      MODE 46
    ENDIF
  ENDIF
ELSE

  REM Older hardware - set to MODE 46 (640x200)
  MODE 46
  REM If you have a system that has very old
  REM display hardware, or a 50Hz "TV" monitor,
  REM try using MODE 14 here.

ENDIF

REM Black, red, green, yellow, blue, magenta, cyan, white
VDU 19,  0, 16,   0,   0,   0
VDU 19,  1, 16, 255,   0,   0
VDU 19,  2, 16,   0, 255,   0
VDU 19,  3, 16, 255, 255,   0
VDU 19,  4, 16,   0,   0, 255
VDU 19,  5, 16, 255,   0, 255
VDU 19,  6, 16,   0, 255, 255
VDU 19,  7, 16, 255, 255, 255

REM Again, but half-bright
VDU 19,  8, 16,  86,  86,  86
VDU 19,  9, 16, 172,   0,   0
VDU 19, 10, 16,   0, 172,   0
VDU 19, 11, 16, 172,  86,   0
VDU 19, 12, 16,   0,   0, 172
VDU 19, 13, 16, 172,   0, 172
VDU 19, 14, 16,   0, 172, 172
VDU 19, 15, 16, 196, 196, 196

REM Let cursors return key codes
SYS "XOS_Byte", 4, 1


REM Exit, ready for AmélieEm...

 

The final topic to mention is that of paths. Such code used to be in the form:

#ifdef __MSDOS__
   fp = fopen("EPROM.IMG", "rb");
#else
 #ifdef __riscos
   fp = fopen("<AmelieEm$Dir>.EpromImage", "rb");
 #else
   #error No specific OS support for opening files.
 #endif
#endif

Now it looks like:

fp = fopen(ROMPATH, "rb");

Which is a lot tidier.
The file variables are now held within the amem.h file, as follows:

#ifdef __riscos
// Filenames and paths for RISC OS

#define ROMPATH   "<AmelieEm$Dir>.EPROMimage"
#define RAMAREA   "<AmelieEm$Dir>.RAMareaImg"
#define ROMAREA   "<AmelieEm$Dir>.ROMareaImg"
#define DUMPAREA  "<AmelieEm$Dir>.MemoryImg"
#define QUICKSAVE "<AmelieEm$Dir>.QuickSave"
#else
// Filenames (path is current directory) for DOS
#define ROMPATH   "EPROM.IMG"
#define RAMAREA   "RAMAREA.IMG"
#define ROMAREA   "ROMAREA.IMG"
#define DUMPAREA  "MEMORY.IMG"
#define QUICKSAVE "QUICK.SAV"
#endif

Furthermore, unless the size and order of integers is "16 bit in x86 byte ordering ", it is recommended that the QuickSave and QuickLoad functions use a special version number so files from another OS cannot be loaded (unless you wish to specifically support this?). The standard version of the array is as produced under DOS.
At this time, the DOS versions will count up from 1 (16 bit 'int', 32 bit 'long', msb last (Intel ordering)) and the RISC OS versions will count down from 255 (32 bit int and long, msb first (Motorola ordering) ).

 

Don't forget - if you have ported 6502asm or AmélieEm, then please send me a copy (with modified sources).
It is a requirement of the licence agreement... you did read that blurb didn't you?
:-)

© 2007 Rick Murray