#include <stddef.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>

#include <oslib/macros.h>
#include <oslib/osspriteop.h>
#include <oslib/jpeg.h>
#include <oslib/wimp.h>
#include <oslib/colourtrans.h>
#include <oslib/toolbox.h>
#include <oslib/menu.h>
#include <event.h>

#include "options.h"

#include "ztypes.h"
#include "control.h"
#include "unicutils.h"
#include "osdepend.h"
#include "screen.h"
#include "text.h"
#include "v6.h"
#include "v6ro.h"
#include "graphics.h"
#include "pngro.h"

#include "fileio.h"
#include "riscosio.h"
#include "riscoswimp.h"
#include "roinput.h"
#include "rosound.h"

#define os_MODE16BPP90X45 ((os_mode) ((5 << 27) | (45 << 14) | (90 << 1) | 1))
#define os_MODE32BPP90X45 ((os_mode) ((6 << 27) | (45 << 14) | (90 << 1) | 1))
#define os_MODE16BPP90X90 ((os_mode) ((5 << 27) | (90 << 14) | (90 << 1) | 1))
#define os_MODE32BPP90X90 ((os_mode) ((6 << 27) | (90 << 14) | (90 << 1) | 1))

#define CLIP_UNDEFINED -1
#define CLIP_WINDOW 0
#define CLIP_MARGINS 1

static int clipping = CLIP_UNDEFINED;

osspriteop_area * restrict v6_screen_area;
osspriteop_header *v6_screen;
osspriteop_trans_tab *v6_trans_tab;
static osspriteop_save_area *savearea;
int screencols;
int hires_screen;
static os_colour fg_col, bg_col;
static os_coord origin;
static const os_factors *gfx_fac;
static const os_factors gfx_fac_hires={1, 2, 1, 1};
static int last_char;
static os_t last_update;
int update_often;
static int have_blending;

static int current_window; /* Current window internally selected (cf screen_window) */

/* Memory for the non-standard colours for each window */
unsigned colourmem[8][2];

static int v6ro_get_colour(void);
static void set_clipping(int c);

static os_colour v6ro_colour_number_to_palette(os_colour_number n)
{
    switch (bitmap_depth)
    {
        default:
        {
            os_colour *pal=(os_colour *)(v6_screen+1);
            return pal[n*2] & 0xFFFFFF00;
        }
        case 4:
        {
            unsigned r,g,b;
            r = n & 0x1F;          r = (r << 3) | (r >> 2);
            g = (n >> 5) & 0x1F;   g = (g << 3) | (g >> 2);
            b = (n >> 10) & 0x1F;  b = (b << 3) | (b >> 2);
            return (b << 24) | (g << 16) | (r << 8);
        }
        case 5:
            return n << 8;
    }
}

static void v6ro_mark_area(int w, int minx, int maxx, int miny, int maxy)
{
    if (w>=0)
    {
        minx+=wind_prop[w][WPROP_X]-2;
        maxx+=wind_prop[w][WPROP_X]-2;
        miny+=wind_prop[w][WPROP_Y]-2;
        maxy+=wind_prop[w][WPROP_Y]-2;
    }
    else
    {
    	minx-=1;
    	maxx-=1;
    	miny-=1;
    	maxy-=1;
    }

    if (!fullscreen)
    {
        if (minx < -2) minx = -2;
        if (miny < -2) miny = -2;
        if (maxx > h_screen_width+2) maxx = h_screen_width+2;
        if (maxy > h_screen_height+2) maxy = h_screen_height+2;
    }

    if (changed_box.x0 > minx)
    	changed_box.x0 = minx;
    if (changed_box.x1 < maxx)
    	changed_box.x1 = maxx;
    if (changed_box.y0 > miny)
        changed_box.y0 = miny;
    if (changed_box.y1 < maxy)
    	changed_box.y1 = maxy;
}

/*static void v6ro_force_redraw(void)
{
    if (changed_box.x1==0)
    	return;

    wimp_force_redraw(ScreenW, changed_box.x0*V6_PIX_X,
                              -changed_box.y1*V6_PIX_Y,
                               changed_box.x1*V6_PIX_X,
                              -changed_box.y0*V6_PIX_Y);

    changed_box.x0=h_screen_width;
    changed_box.x1=0;
    changed_box.y0=h_screen_height;
    changed_box.y1=0;
}*/

static void v6ro_update_window_noswitch(void)
{
    int temp;

    changed_box.x0*=V6_PIX_X;
    changed_box.x1*=V6_PIX_X;
    temp=changed_box.y0;
    changed_box.y0=-changed_box.y1*V6_PIX_Y;
    changed_box.y1=-temp*V6_PIX_Y;
    do_update_window(&changed_box);
    changed_box.x0=h_screen_width;
    changed_box.x1=0;
    changed_box.y0=h_screen_height;
    changed_box.y1=0;
    last_update = os_read_monotonic_time();
}

void v6ro_update_window(void)
{
    if (changed_box.x1==0)
    	return;

    switchoutput(-1);
    v6ro_update_window_noswitch();
    switchoutput(+1);
}

void v6ro_redraw_window(wimp_draw *r)
{
    int rx, ry;
    os_box box;

    /* Relative x+y offsets */
    rx=r->box.x0 - r->xscroll;
    ry=r->box.y1 - r->yscroll;

    box.x0=r->clip.x0 - rx;
    box.x1=r->clip.x1 - rx;
    box.y0=r->clip.y0 - ry;
    box.y1=r->clip.y1 - ry;

    if (log2bpp == bitmap_depth && v6_trans_tab == NULL
        && factors.xmul == factors.xdiv
        && factors.ymul == factors.ydiv)
        osspriteop_put_sprite_user_coords(osspriteop_PTR,
                                          v6_screen_area,
                                          (osspriteop_id) v6_screen,
                                          rx,
                                          ry-SHeightOS,
                                          os_ACTION_OVERWRITE);
    else
        osspriteop_put_sprite_scaled(osspriteop_PTR,
                                     v6_screen_area,
                                     (osspriteop_id) v6_screen,
                                     rx,
                                     ry-SHeightOS,
                                     os_version >= RISC_OS_350 ? osspriteop_USE_PALETTE
                                                               : NONE,
                                     &factors,
                                     log2bpp <= 3 ? v6_trans_tab : 0);

    if (box.x0 < 0 || box.x1 > SWidthOS || box.y1 > 0 || box.y0 < - SHeightOS)
        draw_border(rx, ry);

   /* os_plot(os_MOVE_TO, r->clip.x0, r->clip.y0);
    os_plot(os_PLOT_TO, r->clip.x0, r->clip.y1-1);
    os_plot(os_PLOT_TO, r->clip.x1-1, r->clip.y1-1);
    os_plot(os_PLOT_TO, r->clip.x1-1, r->clip.y0);
    os_plot(os_PLOT_TO, r->clip.x0, r->clip.y0);*/
}

void switchoutput(int dir)
{
    static int screenno=0;
    static int c0,c1,c2,c3;

    if (dir > 0 && screenno==0)
    {
        osspriteop_switch_output_to_sprite(osspriteop_PTR, v6_screen_area,
                                           (osspriteop_id) v6_screen,
                                           savearea, &c0, &c1, &c2, &c3);
    	screenno=1;
    }
    else if (dir < 0 && screenno==1)
    {
    	osspriteop_unswitch_output(c0, c1, c2, c3);
    	screenno=0;
    }
}

static void v6ro_do_gfx(const zword_t *text, int x, int y, int bg, int fg, int n)
{
    os_PALETTE(2) pal;
    int i;
    osspriteop_action flags;
    osspriteop_TRANS_TAB(16) trans_tab;

    if (!gfx_area)
    	load_gfx();

    pal.entries[0]=bg;
    pal.entries[1]=fg;

    if (os_version < RISC_OS_360)
    	flags=NONE;
    else
    	flags=colourtrans_RETURN_WIDE_ENTRIES;

    colourtrans_generate_table(0, (os_palette *) &pal,
                               os_CURRENT_MODE, colourtrans_CURRENT_PALETTE,
                               (osspriteop_trans_tab *) &trans_tab,
                               flags, NULL, NULL);

    if (os_version < RISC_OS_360)
    	flags=os_ACTION_OVERWRITE;
    else
    	flags=os_ACTION_OVERWRITE | osspriteop_GIVEN_WIDE_ENTRIES;

    if (bg==TCOL_TRANSPARENT)
        flags|=os_ACTION_USE_MASK;

    for (i=0; i<n; i++)
    {
    	osspriteop_put_sprite_scaled(osspriteop_PTR,
                                    gfx_area,
                                    (osspriteop_id) gfx_ptr[text[i]-32],
                                    x*V6_PIX_X, -y*V6_PIX_Y-30,
                                    flags,
                                    gfx_fac,
                                    (osspriteop_trans_tab *) &trans_tab);
        x+=8;
    }
}

void v6ro_display_string(const zword_t *t, int len)
{
    int w=screen_window;
    unsigned *const wprop=wind_prop[w];
    int x=wprop[WPROP_X_CURSOR];
    int y=wprop[WPROP_Y_CURSOR];
    int font=display_font; //wprop[WPROP_FONT_NUMBER];
    int style=display_attr; //wprop[WPROP_TEXT_STYLE];
    int f, b;
    int width;
    int generous=FALSE;
    int i, this;
    int n=0;
    zword_t tex[128];
    const zword_t *text=tex;

    if (x > wprop[WPROP_X_WIDTH]-wprop[WPROP_R_MARGIN])
        return;

    set_clipping(CLIP_MARGINS);

    if (fixed_space_bit)
    	style|=FIXED_FONT;

    if (font==4)
    {
        font=TEXT_FONT;
        style|=FIXED_FONT;
    }

    if (style & REVERSE)
    {
        f=bg_col; b=fg_col;
    }
    else
    {
        f=fg_col; b=bg_col;
    }

    if ((style & FIXED_FONT) || font==3)
    {
    	width=16*len;
    	for (i=0; i<len; i++)
    	{
    	    this = t[i];
    	    if (font == 3 && (this < 0x20 || this > 0x7E))
    	        this = 0x7E; /* Inverted ? */
    	    tex[i] = this;
    	}
    }
    else
    {
        #ifdef ALLOW_QUOTES
        int last;
        for (i=0; i<len; i++)
        {
            this=t[i];
            last = n==0 ? last_char : tex[n-1];

            if (smartquotes && (this=='i' || this=='l' || this=='-' || this=='\''
                    || this == '`' || this=='\"'))
            {
#ifdef NEW_QUOTING
                if (this=='\'' && unicode_to_native(0x2019, 0))
                    tex[n++]=0x2019 /**/;
                else if (this=='`')
                {
                    if (unicode_to_native(0x2018, 0))
                        tex[n++]=0x2018 /**/;
                    else
                        tex[n++]='\'';
                }
#else
                if (this=='\'' && unicode_to_native(0x2018, 0))
                {
                    if (last != ' ' && last != '(' && last != 0x201C /**/)
                        tex[n++]=0x2019 /**/;
                    else
                        tex[n++]=0x2018 /**/;
                }
                else if (this=='`' && unicode_to_native(0x2018, 0))
                    tex[n++]=0x2018 /**/;
#endif
                else if (this=='\"' && unicode_to_native(0x201C, 0) && !in_input_line)
                {
                    if (last != ' ' && last != '(' && last != '[' && last != 0x2018 /**/)
                        tex[n++]=0x201D/**/;
                    else
                        tex[n++]=0x201C/**/;
                }
                else if ((this=='i' || this=='-' || this=='l') && !in_input_line)
                {
                    /* Check for ligatures */
                    if (n>0 && last=='f' && this=='i' && unicode_to_native(0xFB01, 0))
                        tex[n-1]=0xFB01;
                    else if (n>0 && last=='f' && this=='l' && unicode_to_native(0xFB02, 0))
                        tex[n-1]=0xFB02;
                    #if 0
                    /*else if (n>0 && (last=='-' || last=='') && this=='-')
                        tex[n-1]=''; This can't happen, unfortunately */
                    else if (last==' ' && this=='-')
                    	tex[n++]='';
                    #endif
                    else if ((last==' ' || last==0x2013) && this=='-' && unicode_to_native(0x2013, 0))
                    	tex[n++]=0x2013;
                    else
                    	tex[n++]=t[i];
                }
                else
                    tex[n++]=t[i];
            }
            else
            	tex[n++]=t[i];
        }
        last_char=tex[n-1];
        len=n;
        #else
        text = t;
        #endif

        for (i=0, width=0; i<len; i++)
            width+=fwidth[style>>1][unicode_to_native(text[i], '?')];

        width=(width+200)/400;
    }

    if ((wprop[WPROP_ATTRIBUTES] & (WATTR_WRAPPING|WATTR_BUFFER))==WATTR_WRAPPING)
    {
        if (x-1+(width+1)/2>wprop[WPROP_X_WIDTH]-wprop[WPROP_R_MARGIN])
        {
            /* Right, we need to character wrap at some stage. Let's figure out where */
            for (i=0, width=0; i<len; i++)
            {
                if ((style & FIXED_FONT) || font==3)
                    width+=char_width;
                else
                    width+=fwidth[style>>1][unicode_to_native(text[i], '?')];

                if ((x-1)*800+width>wprop[WPROP_X_WIDTH]*800-wprop[WPROP_R_MARGIN]*800)
                {
                    /* Check whether the overrunning characters are just spaces */
                    int j,s;
                    for (j=i,s=0; j<n; j++)
                    {
                        if (text[j]!=' ')
                        {
                            s=1;
                            break;
                        }
                    }

    	    	    if (s)
    	    	    {
                    	v6ro_display_string(text, i);
                    	script_new_line();
                    	v6_output_new_line();
                    	v6ro_display_string(text+i, len-i);
                    	return;
                    }
                }
            }

            /* Paranoia - shouldn't get here */
            width=(width+200)/400;

        }
    }

    if (font==3)
    {
    	v6ro_do_gfx(text, x, y, b, f, len);
    }
    else
    {
        font_string_flags ff = font_GIVEN_LENGTH | font_GIVEN_FONT;
        #ifndef font_BLENDING
        #define font_BLENDING ((font_string_flags) 0x800u)
        #endif

        for (i=0; i<len; i++)
        {
            if (text[i]>=128)
            {
                generous=TRUE;
        	break;
            }
        }

        if (b==TCOL_TRANSPARENT)
        {
            if (have_blending)
                ff |= font_BLENDING;
            else
                b = v6ro_colour_number_to_palette(v6ro_get_colour());
        }
        else
        {
    	    int bwidth;

            bwidth=MIN((wprop[WPROP_X_WIDTH] - wprop[WPROP_R_MARGIN] - (x-1))*2, width);
            os_plot(os_MOVE_BY, bwidth-1, -30);
            if (style & REVERSE)
                os_plot(os_PLOT_BY|os_PLOT_RECTANGLE, -(bwidth-1), 30);
            else
                os_plot(os_PLOT_BG_BY|os_PLOT_RECTANGLE, -(bwidth-1), 30);
        }

        colourtrans_set_font_colours(font_CURRENT, b, f, 14, 0, 0, 0);

        if (style & FIXED_FONT)
            font_paint_u(fontno[style>>1], text,
                       ff | font_GIVEN_BLOCK,
                       (origin.x+x*V6_PIX_X)*400, (origin.y-V6_PIX_Y*y+10-32)*400,
                       &fblock, NULL, len);
        else
            font_paint_u(fontno[style>>1], text,
                       ff | font_OS_UNITS,
                       origin.x+x*V6_PIX_X, origin.y-V6_PIX_Y*y+10-32, NULL, NULL, len);
    }
    v6ro_mark_area(w, x, x+(width+V6_PIX_X)/V6_PIX_X+V6_PIX_X/*for f*/, y-(generous?2:0), y+V6_CHAR_H);
    x+=(width+1)/V6_PIX_X;
    if (x > wprop[WPROP_X_WIDTH]-wprop[WPROP_R_MARGIN]) x=wprop[WPROP_X_WIDTH]-wprop[WPROP_R_MARGIN]+1;
    v6ro_move_cursor(y, x);
}

void v6ro_display_char(int c)
{
    zword_t buf[1];

    if (c=='\n')
    {
        v6ro_scroll_line();
        return;
    }

    buf[0]=c;
    v6ro_display_string(buf, 1);
}

int v6ro_text_length(const zword_t *line_buffer, int len)
{
    return (text_length(line_buffer, len,
                                    wind_prop[screen_window][WPROP_FONT_NUMBER],
                                    wind_prop[screen_window][WPROP_TEXT_STYLE]>>1,
                                    fixed_space_bit)+400)/800;
}

int v6ro_text_length_z(const char *line_buffer, int len)
{
    zword_t *l = calloc(len, sizeof (zword_t));
    int i;

    for (i=0; i<len; i++)
        l[i] = zscii_to_unicode(line_buffer[i]);

    i = v6ro_text_length(l, len);

    free(l);

    return i;
}

int v6ro_fit_word(const zword_t *line_buffer, int len)
{
    int width=v6ro_text_length(line_buffer, len);

    if (width==0)
    	return 1;

    return wind_prop[screen_window][WPROP_X_CURSOR]-1+width <= wind_prop[screen_window][WPROP_X_WIDTH]-wind_prop[screen_window][WPROP_R_MARGIN];
}

//void v6ro_scroll_pixels(int w, int n)
//{
//    int x, y, height, width, old_caret;
//
//    x=wind_prop[w][WPROP_X];
//    y=wind_prop[w][WPROP_Y];
//    width=wind_prop[w][WPROP_X_WIDTH];
//    height=wind_prop[w][WPROP_Y_HEIGHT];
//
//    v6ro_update_window();
//
//    v6ro_select_window(w);
//
//    /* Scroll window in screen memory */
//    if (n>0)
//    {
//        os_plot(os_MOVE_TO, V6_PIX_X, -height*V6_PIX_Y-1);
//        os_plot(os_MOVE_TO, V6_PIX_X*width, -(n+1)*V6_PIX_Y);
//        os_plot(os_PLOT_BLOCK|os_PLOT_TO, V6_PIX_X, -(height-n)*V6_PIX_Y-1);
//
//        if (n>=height/2)
//        {
//            os_plot(os_MOVE_TO, V6_PIX_X, -height*V6_PIX_Y-1);
//            os_plot(os_PLOT_RECTANGLE|os_PLOT_BG_BY, (width-1)*V6_PIX_X, n*V6_PIX_Y-1);
//        }
//    }
//    else
//    {
//        os_plot(os_MOVE_TO, V6_PIX_X, -(height+n)*V6_PIX_Y-1);
//        os_plot(os_MOVE_TO, V6_PIX_X*width, -V6_PIX_Y);
//        os_plot(os_PLOT_BLOCK|os_PLOT_TO, V6_PIX_X, -height*V6_PIX_Y-1);
//
//        if (n>=height/2)
//        {
//            os_plot(os_MOVE_TO, V6_PIX_X, -(height+n)*V6_PIX_Y-1);
//            os_plot(os_PLOT_RECTANGLE|os_PLOT_BG_BY, (width-1)*V6_PIX_X, -V6_PIX_Y);
//        }
//    }
//
//    if (w==screen_window)
//        v6ro_move_cursor(wind_prop[w][WPROP_Y_CURSOR],
//                         wind_prop[w][WPROP_X_CURSOR]);
//
//    /* Do wimp block copy for desktop */
//    switchoutput(-1);
//    old_caret=caret_enabled;
//    place_caret();
//    wimp_block_copy(ScreenW,
//                    (x-1)*V6_PIX_X,
//                    -(y-1+height)*V6_PIX_Y,
//                    (x-1+width)*V6_PIX_X,
//                    -(y+n-1)*V6_PIX_Y,
//                    (x-1)*V6_PIX_X,
//                    -(y-1+height-n)*V6_PIX_Y);
//    v6ro_mark_area(w, 1, 1+width,
//                      1+height-n, 1+height);
//
//    /* Move cursor */
//    v6ro_update_window();     /* This unswitches output itself */
//    complete_redraw();
//    switchoutput(+1);
//    v6ro_select_window(screen_window);
//    caret_enabled=old_caret;
//    place_caret();
//}
void v6ro_scroll_pixels(int w, int n)
{
    int x, y, height, width, old_caret;

    x=wind_prop[w][WPROP_X];
    y=wind_prop[w][WPROP_Y];
    width=wind_prop[w][WPROP_X_WIDTH];
    height=wind_prop[w][WPROP_Y_HEIGHT];

    switchoutput(-1);

    if (changed_box.x1)
    {
        v6ro_update_window_noswitch();
        complete_redraw();
    }

    /* Do wimp block copy for desktop */
    old_caret=caret_enabled;
    place_caret();
    wimp_block_copy(ScreenW,
                    (x-1)*V6_PIX_X,
                    -(y-1+height)*V6_PIX_Y,
                    (x-1+width)*V6_PIX_X,
                    -(y+n-1)*V6_PIX_Y,
                    (x-1)*V6_PIX_X,
                    -(y-1+height-n)*V6_PIX_Y);

    caret_enabled=old_caret;
    place_caret();

    switchoutput(+1);

    v6ro_select_window(w); /* Sets clipping to window */

    /* Scroll window in screen memory */
    if (n>0)
    {
        os_plot(os_MOVE_TO, V6_PIX_X, -height*V6_PIX_Y-1);
        os_plot(os_MOVE_TO, V6_PIX_X*width, -(n+1)*V6_PIX_Y);
        os_plot(os_PLOT_BLOCK|os_PLOT_TO, V6_PIX_X, -(height-n)*V6_PIX_Y-1);

        if (n>=height/2)
        {
            os_plot(os_MOVE_TO, V6_PIX_X, -height*V6_PIX_Y-1);
            os_plot(os_PLOT_RECTANGLE|os_PLOT_BG_BY, (width-1)*V6_PIX_X, n*V6_PIX_Y-1);
        }
    }
    else
    {
        os_plot(os_MOVE_TO, V6_PIX_X, -(height+n)*V6_PIX_Y-1);
        os_plot(os_MOVE_TO, V6_PIX_X*width, -V6_PIX_Y);
        os_plot(os_PLOT_BLOCK|os_PLOT_TO, V6_PIX_X, -height*V6_PIX_Y-1);

        if (n>=height/2)
        {
            os_plot(os_MOVE_TO, V6_PIX_X, -(height+n)*V6_PIX_Y-1);
            os_plot(os_PLOT_RECTANGLE|os_PLOT_BG_BY, (width-1)*V6_PIX_X, -V6_PIX_Y);
        }
    }

    v6ro_mark_area(w, 1, 1+width,
                      1+height-n, 1+height);

    //if (w==screen_window)
    //    v6ro_move_cursor(wind_prop[w][WPROP_Y_CURSOR],
    //                     wind_prop[w][WPROP_X_CURSOR]);

    /* Move cursor */
    //v6ro_update_window();     /* This unswitches output itself */
    //complete_redraw();
    v6ro_select_window(screen_window);
}

#define Y_CURSOR (wind_prop[screen_window][WPROP_Y_CURSOR])
#define X_CURSOR (wind_prop[screen_window][WPROP_X_CURSOR])
#define HEIGHT (wind_prop[screen_window][WPROP_Y_HEIGHT])
#define L_MARGIN (wind_prop[screen_window][WPROP_L_MARGIN])
#define ATTRIBUTES (wind_prop[screen_window][WPROP_ATTRIBUTES])

void v6ro_scroll_line(void)
{
    int x, y;
    x=(zword_t)(L_MARGIN+1);
    y=(zword_t)(Y_CURSOR+V6_CHAR_H);

    last_char=' ';

    if (y > (signed) HEIGHT - V6_CHAR_H + 1)
    {
        y = (signed) HEIGHT - V6_CHAR_H + 1;
        if (y < 1) y = 1;

        if (ATTRIBUTES & WATTR_SCROLL)
            v6ro_scroll_pixels(screen_window, Y_CURSOR-(HEIGHT-2*V6_CHAR_H+1));
    }

    v6ro_move_cursor(y, x);
}

static void v6ro_exit(void)
{
    switchoutput(-100);
}

#if 0
void add_palette_entry(int r, int g, int b)
{
    os_colour col=(b<<24)+(g<<16)+(r<<8);
    os_colour *palptr;
    int i;

    if (screencols==256)
    	return;

    palptr=(os_colour *)(v6_screen+1);

    for (i=0; i<screencols; i++)
        if (palptr[i*2]==col)
            return;

    palptr[screencols*2]=palptr[screencols*2+1]=col;

    screencols+=1;

    /* Because we've poked the palette */
    colourtrans_invalidate_cache();

    /* Sort out the desktop redraw routine's translation table */
    switchoutput(-1);
    palettechange_handler(NULL, NULL);
    switchoutput(+1);
}
#endif

static void create_savearea(void)
{
    int savesize;

    free(savearea);

    savesize=osspriteop_read_save_area_size(osspriteop_PTR, v6_screen_area,
                                            (osspriteop_id) v6_screen);

    savearea=(osspriteop_save_area *)malloc(savesize);

    if (savearea == NULL)
    	fatal_lookup("NoMem");

    savearea->a[0]=0;

    switchoutput(+1);

    os_join_cursors();

    if (hires_screen)
    {
        // Select a system font size of 8x16
        // (VDU 23,17,7,2,8;16;0,0)
        os_writen("\x17\x11\x07\x02\x08\x00\x10\x00\x00\x00", 10);
    }

    switchoutput(-1);
}

int bitmap_width, bitmap_height, bitmap_depth;

void v6ro_initialize_screen(int width, int height)
{
    int savesize, yeig, log2bpp, len;
    os_mode mode;
    int area_size;
    zword_t *loading;
    int loading_len;
    int fm_version;

    font_cache_addr(&fm_version, SKIP, SKIP);
    have_blending = fm_version >= 335;

    width *= V6_CHAR_W;
    height *= V6_CHAR_H;

    initialise_flex();

    os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_YEIG_FACTOR, &yeig);
    os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_LOG2_BPP, &log2bpp);

    if (yeig<=1)
    {
    	bitmap_height=height; hires_screen=TRUE;
    	bitmap_width = width;
    	switch (log2bpp)
    	{
    	    default: mode = os_MODE8BPP90X90; bitmap_depth = 3; break;
    	    case 4: mode = os_MODE16BPP90X90; bitmap_depth = 4; break;
    	    case 5: mode = os_MODE32BPP90X90; bitmap_depth = 5; break;
    	}
    }
    else
    {
        bitmap_height=height/2; hires_screen=FALSE;
    	bitmap_width = width;
    	switch (log2bpp)
    	{
    	    default: mode = os_MODE8BPP90X45; bitmap_depth = 3; break;
    	    case 4: mode = os_MODE16BPP90X45; bitmap_depth = 4; break;
    	    case 5: mode = os_MODE32BPP90X45; bitmap_depth = 5; break;
    	}
    }

    /* First, we want to allocate a screen sprite. The sprite will be
       640*(200|400), 256|32K|16M colours, with palette if 256 cols */

    area_size=bitmap_width*bitmap_height;
    if (log2bpp == 4) area_size *= 2;
    else if (log2bpp == 5) area_size *= 4;
    else area_size += 8*256;
    area_size += sizeof(osspriteop_area) + sizeof(osspriteop_header);

    v6_screen_area=(osspriteop_area *)malloc(area_size);

    if (v6_screen_area == NULL)
    	fatal_lookup("NoMem");

    v6_screen_area->size=area_size;
    v6_screen_area->first=16;
    osspriteop_clear_sprites(osspriteop_USER_AREA, v6_screen_area);

    osspriteop_create_sprite(osspriteop_USER_AREA, v6_screen_area, "screen",
                             FALSE, bitmap_width, bitmap_height, mode);

    v6_screen=osspriteop_select_sprite(osspriteop_USER_AREA, v6_screen_area,
                                       (osspriteop_id) "screen");

    if (bitmap_depth == 3)
        osspriteop_create_true_palette(osspriteop_PTR, v6_screen_area,
                                          (osspriteop_id) v6_screen);

    savesize=osspriteop_read_save_area_size(osspriteop_PTR, v6_screen_area,
                                                   (osspriteop_id) v6_screen);

    create_savearea();

    modechange_handler(NULL, NULL);

    gfx_fac=hires_screen?&gfx_fac_hires:NULL;

    /*colourtrans_write_palette(v6_screen_area, (osspriteop_id) v6_screen,
                              (os_palette *) palette, colourtrans_PALETTE_FOR_SPRITE);*/

    switchoutput(+1);
    atexit(v6ro_exit);

    find_fonts();

    set_true_colours(TCOL_DEFAULT, TCOL_DEFAULT, -1);
    v6ro_clear_screen();

    loading=msgs_lookup_u("Loading");
    loading_len=strlen_u(loading);

    len=v6ro_text_length(loading, loading_len);

    v6ro_move_cursor(1+(height-V6_CHAR_H)/2, 1+(width-len)/2);

    v6ro_display_string(loading, loading_len);

    v6ro_move_cursor(1, 1);

    initialise_wimp();
}

int v6ro_resize_screen(int new_cols, int new_rows)
{
    int new_bitmap_height, new_bitmap_width;
    int area_size;

    new_bitmap_width = new_cols * V6_CHAR_W;
    new_bitmap_height = new_rows * V6_CHAR_H;
    if (!hires_screen) new_bitmap_height /= 2;

    area_size = new_bitmap_width*new_bitmap_height;
    if (bitmap_depth == 4) area_size *= 2;
    else if (bitmap_depth == 5) area_size *= 4;
    else area_size += 8*256;
    area_size += sizeof(osspriteop_area) + sizeof(osspriteop_header);
    if (area_size > v6_screen_area->size)
    {
        osspriteop_area *a = realloc(v6_screen_area, area_size);
        if (a == NULL)
        {
            warning_lookup("NoMem");
            return 0;
        }

        v6_screen_area = a;
        v6_screen_area->size=area_size;
    }

    if (new_bitmap_width > bitmap_width)
        osspriteop_insert_delete_columns(osspriteop_USER_AREA,
                                         v6_screen_area,
                                         (osspriteop_id) "screen",
                                         bitmap_width,
                                         new_bitmap_width - bitmap_width);
    else if (new_bitmap_width < bitmap_width)
        osspriteop_insert_delete_columns(osspriteop_USER_AREA,
                                         v6_screen_area,
                                         (osspriteop_id) "screen",
                                         new_bitmap_width,
                                         new_bitmap_width - bitmap_width);
    if (new_bitmap_height != bitmap_height)
        osspriteop_insert_delete_rows(osspriteop_USER_AREA,
                                      v6_screen_area,
                                      (osspriteop_id) "screen",
                                      0,
                                      new_bitmap_height - bitmap_height);

    if (area_size < v6_screen_area->size)
    {
        osspriteop_area *a = realloc(v6_screen_area, area_size);
        if (a == NULL)
        {
            fatal_lookup("NoMem"); /* Shouldn't happen... */
            return 0;
        }

        v6_screen_area = a;
        v6_screen_area->size=area_size;
    }

    v6_screen=osspriteop_select_sprite(osspriteop_USER_AREA, v6_screen_area,
                                       (osspriteop_id) "screen");
    /* Reset the graphics context */
    create_savearea();

    /* Reset colours, origin etc for current window */

    switchoutput(+1);

    colourtrans_set_gcol(screen_bg, NONE, os_ACTION_OVERWRITE, NULL);
    if (new_bitmap_width > bitmap_width)
    {
        os_plot(os_MOVE_TO, bitmap_width*2, 0);
        os_plot(os_PLOT_TO|os_PLOT_RECTANGLE, new_bitmap_width*2, new_bitmap_height*2);
    }
    if (new_bitmap_height > bitmap_height)
    {
        os_plot(os_MOVE_TO, 0, 0);
        os_plot(os_PLOT_TO|os_PLOT_RECTANGLE, new_bitmap_width*2, (new_bitmap_height-bitmap_height)*2-1);
    }

    bitmap_width = new_bitmap_width;
    bitmap_height = new_bitmap_height;

    SWidth=new_cols*char_width;
    SHeight=new_rows*line_height;

    SWidthOS=SWidth/400;
    SHeightOS=SHeight/400;

    v6ro_select_window(screen_window);

    switchoutput(-1);

    return 1;
}

void v6ro_move_cursor(zword_t y, zword_t x)
{
    os_plot(os_MOVE_TO, x*2, -y*2);

    wind_prop[screen_window][WPROP_Y_CURSOR]=y;
    wind_prop[screen_window][WPROP_X_CURSOR]=x;
}

void v6ro_get_cursor_position(int *row, int *col)
{
    /*Flush any sort of buffer*/
    *row=wind_prop[screen_window][WPROP_Y_CURSOR];
    *col=wind_prop[screen_window][WPROP_X_CURSOR];
}

void v6ro_update_margins(void)
{
    if (clipping == CLIP_MARGINS)
        clipping = CLIP_UNDEFINED;
}

static void set_clipping(int c)
{
    if (c != clipping)
    {
        int xmin, ymin, xmax, ymax;
        int w=current_window;

        xmin=1*V6_PIX_X;
        xmax=(1+wind_prop[w][WPROP_X_WIDTH])*V6_PIX_X-1;
        ymin=-(wind_prop[w][WPROP_Y_HEIGHT])*V6_PIX_Y;
        ymax=-1;
        if (c==CLIP_MARGINS)
        {
            xmin+=wind_prop[w][WPROP_L_MARGIN]*V6_PIX_X;
            xmax-=wind_prop[w][WPROP_R_MARGIN]*V6_PIX_X;
        }
        if (xmin+origin.x<0) xmin=-origin.x;
        if (xmax+origin.x>SWidthOS) xmax=SWidthOS-origin.x;
        if (ymin+origin.y<0) ymin=-origin.y;
        if (ymax+origin.y>SHeightOS) ymax=SHeightOS-origin.y;
        set_graphics_window(xmin, ymin, xmax, ymax);
        clipping = c;
    }
}

void v6ro_select_window(int w)
{
    int x0, y0;

    last_char=' ';

    display_attr=wind_prop[w][WPROP_TEXT_STYLE];
    display_font=wind_prop[w][WPROP_FONT_NUMBER];
    colourtrans_set_gcol(fg_col = colourmem[w][0], os_GCOL_SET_FG, 0, 0);
    colourtrans_set_gcol(bg_col = colourmem[w][1], os_GCOL_SET_BG, 0, 0);
    colourtrans_set_font_colours(font_CURRENT, bg_col, fg_col, 14, SKIP, SKIP, SKIP);
    true_fg=fg_col;
    true_bg=bg_col;

    x0=(wind_prop[w][WPROP_X]-2)*V6_PIX_X;
    y0=SHeightOS-(wind_prop[w][WPROP_Y]-1)*V6_PIX_Y;

    current_window = w;

    origin.x=x0;
    origin.y=y0;
    set_graphics_origin(x0, y0);

    clipping = CLIP_UNDEFINED;
    set_clipping(CLIP_WINDOW);
}

void v6ro_set_colours(os_colour f, os_colour b, int w)
{
    os_colour_number cf, cb;

    if (w == -1)
        w = screen_window;

    /* TCOL_DEFAULT already dealt with */
    if (f==TCOL_SAMPLE)
        f=v6ro_colour_number_to_palette(v6ro_get_colour());
    else if (f>0xFFFFFF00)
        f=colourmem[w][0];

    if (b==TCOL_SAMPLE)
        b=v6ro_colour_number_to_palette(v6ro_get_colour());
    else if (b==TCOL_TRANSPARENT)
        ;
    else if (b>0xFFFFFF00)
        b=colourmem[w][1];

    colourmem[w][0] = f;
    colourmem[w][1] = b;
    wind_prop[w][WPROP_COLOUR_DATA]=(os_colour_to_z_colour(b) << 8) |
                                     os_colour_to_z_colour(f);
    /* We'll do the approximation thing */
    cf = colourtrans_return_colour_number(f);
    f = v6ro_colour_number_to_palette(cf);
    cb = colourtrans_return_colour_number(b);
    if (b != TCOL_TRANSPARENT)
        b = v6ro_colour_number_to_palette(cb);
    wind_prop[w][WPROP_TRUE_FG]=os_colour_to_z_true_colour(f);
    wind_prop[w][WPROP_TRUE_BG]=os_colour_to_z_true_colour(b);

    if (w == screen_window)
    {
        true_fg=fg_col=f;
        true_bg=bg_col=b;
        colourtrans_set_font_colours(font_CURRENT, b, f, 14, SKIP, SKIP, SKIP);
        os_set_colour(NONE, cf);
        os_set_colour(os_COLOUR_SET_BG, cb);
    }
}

void v6ro_clear_screen(void)
{
    if (bg_col==TCOL_TRANSPARENT) return;
    os_reset_windows();
    os_clg();
    v6ro_mark_area(-1, -16384, 16383, -16384, 16383);   /* Allow for border */
    screen_bg=bg_col;
    v6ro_select_window(screen_window);
    SetBorder(fullscreen);
    /*v6ro_update_window();*/
}

void v6ro_clear_window(int w)
{
    if (colourmem[w][1]==TCOL_TRANSPARENT) return;
    if (w != screen_window)
        v6ro_select_window(w);

    //os_clg();
    os_plot(os_MOVE_TO, V6_PIX_X, -V6_PIX_Y);
    os_plot(os_PLOT_BG_BY | os_PLOT_RECTANGLE, (wind_prop[w][WPROP_X_WIDTH]-1)*V6_PIX_X,
                                             -(wind_prop[w][WPROP_Y_HEIGHT]-1)*V6_PIX_Y-1);
    if (w != screen_window)
        v6ro_select_window(screen_window);

    v6ro_mark_area(w, 1, 1+wind_prop[w][WPROP_X_WIDTH], 1, 1+wind_prop[w][WPROP_Y_HEIGHT]);
    /*v6ro_update_window();*/
}

void v6ro_place_caret(void)
{
    int x, y;

    if (in_input_line)
    	x=input_x-2;
    else
    	x=wind_prop[screen_window][WPROP_X]+wind_prop[screen_window][WPROP_X_CURSOR]-2;

    y=wind_prop[screen_window][WPROP_Y]+wind_prop[screen_window][WPROP_Y_CURSOR]-2;

    switchoutput(-1);
    wimp_set_caret_position(ScreenW, wimp_ICON_WINDOW,
                            x*V6_PIX_X,
                            -y*V6_PIX_Y-31,
                            32, 0);
    switchoutput(+1);
}

void v6ro_clear_line(int value)
{
    if (bg_col==TCOL_TRANSPARENT) return;

    set_clipping(CLIP_MARGINS);

    /* Flush buffers */
    if (value==1)
        value=wind_prop[screen_window][WPROP_X_WIDTH]
                    - (wind_prop[screen_window][WPROP_X_CURSOR]-1)
                    - wind_prop[screen_window][WPROP_R_MARGIN];
    else
        value-=1;

    os_plot(os_PLOT_RECTANGLE|os_PLOT_BG_BY, value*V6_PIX_X-1, -30);
    os_plot(os_MOVE_BY, -(value*V6_PIX_X-1), 30);
    v6ro_mark_area(screen_window,
                   wind_prop[screen_window][WPROP_X_CURSOR],
                   wind_prop[screen_window][WPROP_X_CURSOR]+value,
                   wind_prop[screen_window][WPROP_Y_CURSOR],
                   wind_prop[screen_window][WPROP_Y_CURSOR]+V6_CHAR_H);
}

void v6ro_open_gfx(void)
{
    char gfxfilename[256];
    char *leaf=strrchr(StoryName, '.')+1;

    sprintf(gfxfilename, "<Zip2000$Dir>.Resources.Graphics.%s", leaf);

    GFile=fopen(gfxfilename, "rb");

    if (!GFile)
    {
        strcpy(gfxfilename, StoryName);
        strcpy(strrchr(gfxfilename, '.')+1, "Graphics");

        GFile=fopen(gfxfilename, "rb");
    }
}

void v6ro_plot_picture(int n, gfx_dir *p, int y, int x)
{
    Bitmap *bmp;
    osspriteop_action action;
    os_factors fac, *f;

    NOT_USED(n);

    /*if (blorb_map)
    {
        os_plot(os_MOVE_TO, x*V6_PIX_X, -y*V6_PIX_Y);
        os_plot(os_PLOT_BY, p->image_width*V6_PIX_X-2, 0);
        os_plot(os_PLOT_BY, 0, -(p->image_height*V6_PIX_Y-4));
        os_plot(os_PLOT_BY, -(p->image_width*V6_PIX_X-2), 0);
        os_plot(os_PLOT_BY, 0, (p->image_height*V6_PIX_Y-4));
        os_plot(os_MOVE_BY, p->image_width*V6_PIX_X/2-24, -p->image_height*V6_PIX_Y/2+16);
        printf("%d", n);
        v6ro_move_cursor(wind_prop[screen_window][WPROP_Y_CURSOR],
                         wind_prop[screen_window][WPROP_X_CURSOR]);

        v6ro_mark_area(screen_window, x, x+p->image_width, y, y+p->image_height);
        //v6ro_update_window();
        return;
    }*/

    bmp=build_sprite(bfp ? bfp : GFile, p, 1);
    if (!bmp)
    	return;

    set_clipping(CLIP_WINDOW);

    fac.xmul = p->image_width;
    fac.ymul = p->image_height;
    fac.xdiv = p->phys_width;
    fac.ydiv = p->phys_height;
    if (!hires_screen)
        fac.ydiv *= 2;

    if (fac.xmul == fac.xdiv && fac.ymul == fac.ydiv)
        f = NULL;
    else
        f = &fac;

    action = p->alphatype == alpha_NONE ? NONE : osspriteop_USE_MASK;

    if (!bfp)
    {
        #define first_sprite(area) ((osspriteop_header *) ((byte *)(area) + ((area)->first)))
        #define first_id(area) ((osspriteop_id) ((byte *)(area) + ((area)->first)))
        int tabsize;
        void *tab = NULL;

        /*
         * Plotting 256-colour, true-colour palette sprite into 8,16 or 32bpp.
         * In the 8 bit case, use a translation table. In 16 or 32bpp, do it
         * straight from the palette, to give optimum quality on RISC OS 3.5.
         */

        if (bitmap_depth <= 3)
        {
            tabsize =
            colourtrans_generate_table_for_sprite(bmp->sprite,
                                                  first_id(bmp->sprite),
                                                  os_CURRENT_MODE,
                                                  colourtrans_CURRENT_PALETTE,
                                                  NULL, colourtrans_GIVEN_SPRITE,
                                                  NULL, NULL);
            if (tabsize)
            {
                tab = malloc(tabsize);
                if (!tab)
                {
                    if (!bmp->directory)
                        free(bmp);
                    return;
                }
                colourtrans_generate_table_for_sprite(bmp->sprite,
                                                      first_id(bmp->sprite),
                                                      os_CURRENT_MODE,
                                                      colourtrans_CURRENT_PALETTE,
                                                      tab, colourtrans_GIVEN_SPRITE,
                                                      NULL, NULL);
            }
        }
        else
            action|=osspriteop_USE_PALETTE;

        osspriteop_put_sprite_scaled(osspriteop_PTR, bmp->sprite, first_id(bmp->sprite),
                                     x*V6_PIX_X, -(y+p->image_height-1)*V6_PIX_Y,
                                     action, f, tab);
        free(tab);
    }
    else if (p->jpeg)
    {
        jpeg_plot_scaled((jpeg_image *)(bmp->sprite+1),
                        x*V6_PIX_X,
                        -(y+p->image_height-1)*V6_PIX_Y,
                        f,
                        bmp->sprite->first - sizeof(osspriteop_area),
                        dithering ?
                        jpeg_SCALE_DITHERED|jpeg_SCALE_ERROR_DIFFUSED : NONE);
    }
    else
    {

        /* PNG Blorb case */
        if (have_adaptive && !p->adaptive)
            pngro_note_palette(&bmp->sprite);

        if (p->alphatype & alpha_COMPLEX)
        {
            int px, py;
            px = wind_prop[screen_window][WPROP_X]+x-2;
            py = wind_prop[screen_window][WPROP_Y]+y-2;
            if (!hires_screen) py>>=1;
            if (dithering && bitmap_depth == 3)
            {
                if (f)
                    do_alpha_plot_scaled_dithered(bmp, v6_screen, px, py, f);
                else
                    do_alpha_plot_dithered(bmp, v6_screen, px, py);
            }
            else
            {
                if (f)
                    do_alpha_plot_scaled(bmp, v6_screen, px, py, f);
                else
                    do_alpha_plot(bmp, v6_screen, px, py);
            }

        }
        else
        {
            if (f)
            {
                osspriteop_put_sprite_scaled(osspriteop_PTR, bmp->sprite,
                                             first_id(bmp->sprite),
                                             x*V6_PIX_X,
                                             -(y+p->image_height-1)*V6_PIX_Y,
                                             action, f, NULL);
            }
            else
            {
                /* Have an image in the "screen" depth and palette. Can do an untranslated,
                 * possibly unscaled plot.
                 */
                osspriteop_put_sprite_user_coords(osspriteop_PTR, bmp->sprite,
                                                  first_id(bmp->sprite),
                                                  x*V6_PIX_X,
                                                  -(y+p->image_height-1)*V6_PIX_Y,
                                                  action);
            }
        }
    }

    if (!bmp->directory)
    	free_bitmap(bmp);

   /* os_plot(os_MOVE_TO, x*V6_PIX_X, -y*V6_PIX_Y);
    os_plot(os_PLOT_BY, p->image_width*V6_PIX_X-2, 0);
    os_plot(os_PLOT_BY, 0, -(p->image_height*V6_PIX_Y-4));
    os_plot(os_PLOT_BY, -(p->image_width*V6_PIX_X-2), 0);
    os_plot(os_PLOT_BY, 0, (p->image_height*V6_PIX_Y-4));
    os_plot(os_MOVE_BY, p->image_width*V6_PIX_X/2-24, -p->image_height*V6_PIX_Y/2+16);
    printf("%d", n);
    v6ro_move_cursor(wind_prop[screen_window][WPROP_Y_CURSOR],
                     wind_prop[screen_window][WPROP_X_CURSOR]);*/

    v6ro_mark_area(screen_window, x, x+p->image_width, y, y+p->image_height);
    /* We'll update at most 10 times a second - updates can be quite slow */
    if (buffer_screen_mode == 0 &&
        (update_often || os_read_monotonic_time() - last_update > 10))
        v6ro_update_window();
}

void v6ro_remove_picture(int n, gfx_dir *p, int y, int x)
{
    NOT_USED(n);

    if (bg_col==TCOL_TRANSPARENT) return;

    set_clipping(CLIP_WINDOW);

    os_plot(os_MOVE_TO, x*V6_PIX_X, -y*V6_PIX_Y);
    os_plot(os_PLOT_BG_BY | os_PLOT_RECTANGLE, p->image_width*V6_PIX_X-1, -p->image_height*V6_PIX_Y+2);
    v6ro_mark_area(screen_window, x, x+p->image_width, y, y+p->image_height);
    if (buffer_screen_mode == 0 && update_often)
        v6ro_update_window();
}

static int v6ro_get_colour(void)
{
    char *p;
    int x, y;

    y=wind_prop[screen_window][WPROP_Y]+wind_prop[screen_window][WPROP_Y_CURSOR]-2;
    if (y<0) y = 0; else if (y >= h_screen_height) y = h_screen_height-1;
    if (!hires_screen) y/=2;
    x=wind_prop[screen_window][WPROP_X]+wind_prop[screen_window][WPROP_X_CURSOR]-2;
    if (x<0) x = 0; else if (x >= h_screen_width) x = h_screen_width-1;

    /* There is an OS_SpriteOp to do this, but it's broken in RISC OS 3.5 */

    p=(char *)v6_screen+v6_screen->image;
    p+=((v6_screen->width+1)*4)*y;
    switch (bitmap_depth)
    {
        default:
            return *(p+x);
        case 4:
            return *((unsigned short *)p + x);
        case 5:
            return *((unsigned *)p + x);
    }
}

#define cmp_MENU_MYLAST 2

static int game_menus;

/*
 * The horror. We want a separator between our main menu entries, and the game's,
 * but we can't add and remove the separator at run time. We'll have to remove
 * the Utilities entry, modify it, and reattach it. By removing the entry, we
 * may potentially leave its submenu in limbo, but we address this by making
 * sure the submenu is an autocreated, shared object, giving it permanent and
 * singular existance.
 */
static void v6ro_menu_separator(bool on)
{
    toolbox_resource_file_object * restrict object;
    menu_object * restrict menu;
    menu_entry_object entry;
    int i;

    object = (toolbox_resource_file_object *) toolbox_template_look_up(NONE, "MainMenu");
    menu = (menu_object *) object->header_size;

    for (i=1; i<menu->entry_count; i++)
        if (menu->entries[i].cmp==cmp_MENU_MYLAST)
        {
            entry = menu->entries[i];
            if (on) entry.flags |= menu_ENTRY_SEPARATE;
            menu_remove_entry(NONE, mainmenu, cmp_MENU_MYLAST);
            menu_add_entry(NONE, mainmenu, menu->entries[i-1].cmp, &entry);
            break;
        }
}

static menu_entry_object v6ro_menu_entry(toolbox_c cmp, char *text)
{
    menu_entry_object m;

    m.flags=NONE;
    m.cmp=cmp;
    m.text=text;
    m.text_limit=256;
    m.click_object_name=NULL;
    m.sub_menu_object_name=NULL;
    m.sub_menu_action=0;
    m.click_action=action_GameMenu;
    m.help=NULL;
    m.help_limit=0;

    return m;
}

/* Why do this? So that if the menu is up (eg after an Adjust-click), the tree doesn't
 * collapse.
 */
static int v6ro_change_menu(toolbox_c menu_no, int entries, char *entry[])
{
    char buf[256];
    os_error *e;
    toolbox_o menu_o;
    toolbox_c c;

    if (xmenu_get_sub_menu_show(NONE, mainmenu, menu_no, &menu_o))
        return 0;

    switchoutput(-1);

    menu_get_entry_text(NONE, mainmenu, menu_no, buf, sizeof buf);
    if (strcmp(buf, entry[0]) != 0)
    {
        menu_set_entry_text(NONE, mainmenu, menu_no, entry[0]);
        menu_set_title(NONE, menu_o, entry[0]);
    }

    /* First, change existing entries */
    for (c=1; c<entries; c++)
    {
        e = xmenu_get_entry_text(NONE, menu_o, c, buf, sizeof buf, NULL);
        if (e) break;
        if (strcmp(buf, entry[c]) != 0)
            menu_set_entry_text(NONE, menu_o, c, entry[c]);
    }

    /* Then add extra if necessary */
    for ( ; c<entries; c++)
    {
        menu_entry_object m = v6ro_menu_entry(c, entry[c]);
        menu_add_entry(NONE, menu_o, menu_ADD_AT_END, &m);
    }

    /* Finally remove any excess */
    for (e = NULL; !e; c++)
        e = xmenu_remove_entry(NONE, menu_o, c);

    switchoutput(+1);

    return 1;
}

void v6ro_add_menu(int menu_no, int entries, char *entry[])
{
    toolbox_RESOURCE_FILE_OBJECT(500) object;
    menu_object *menu;
    toolbox_o menu_o;
    toolbox_c before;
    menu_entry_object mainentry;
    os_error *e;
    int i;

    if (v6ro_change_menu(menu_no, entries, entry))
        return;

    switchoutput(-1);

    object.class_no=class_MENU;
    object.flags=NONE;
    object.version=102;
    sprintf(object.name, "GameMenu%d", menu_no);
    object.size=toolbox_SIZEOF_RESOURCE_FILE_OBJECT(0)
                   + menu_SIZEOF_OBJECT(entries-1);
    menu=(menu_object *) &object.object;
    object.header_size=(int) menu;
    object.body_size=menu_SIZEOF_OBJECT(entries-1);

    menu->flags=NONE;
    menu->title=entry[0];
    menu->title_limit=256;
    menu->help=NULL;
    menu->help_limit=0;
    menu->show_action=0;
    menu->hide_action=0;
    menu->entry_count=entries-1;

    for (i=1; i<entries; i++)
        menu->entries[i-1] = v6ro_menu_entry(i, entry[i]);

    menu_o=toolbox_create_object(toolbox_CREATE_GIVEN_OBJECT, (toolbox_id) &object);

    mainentry = v6ro_menu_entry(menu_no, entry[0]);
    mainentry.flags=menu_ENTRY_SUB_MENU;
    mainentry.click_action=0;

    /* Have to find the next higest numbered menu to stick it before. We can't use after
     * because if we stick it after Zip 2000's last entry, its separator will be
     * helpfully stripped.
     */
    before = menu_no;
    do
        e = xmenu_add_entry(menu_ADD_BEFORE, mainmenu, ++before, &mainentry, SKIP);
    while (e && e->errnum == 0x1B80AA14 && before < 32); /* Component not found */
    if (e && e->errnum == 0x1B80AA14)
        menu_add_entry(menu_ADD_BEFORE, mainmenu, menu_ADD_AT_END, &mainentry);
    else if (e)
        os_generate_error(e);

    menu_set_sub_menu_show(NONE, mainmenu, menu_no, menu_o);

    if (++game_menus == 1) v6ro_menu_separator(TRUE);

    switchoutput(+1);
}

void v6ro_remove_menu(int menu_no)
{
    toolbox_o m;
    os_error *e;

    switchoutput(-1);

    e=xmenu_get_sub_menu_show(NONE, mainmenu, menu_no, &m);

    if (!e)
    {
    	menu_remove_entry(NONE, mainmenu, menu_no);
    	toolbox_delete_object(NONE, m);
    	if (--game_menus == 0) v6ro_menu_separator(FALSE);
    }

    switchoutput(+1);
}

void v6ro_input_place_caret(const zword_t * restrict buffer, int left, int pos)
{
    input_x=left+wind_prop[screen_window][WPROP_X]+v6ro_text_length(buffer, pos);

    place_caret();
}

void v6ro_input_blank(const zword_t * restrict buffer, int left)
{
    int temp_x=wind_prop[screen_window][WPROP_X_CURSOR];

    NOT_USED(buffer);

    v6ro_move_cursor(wind_prop[screen_window][WPROP_Y_CURSOR], left);

    v6ro_clear_line(temp_x-left+3/*for luck*/);

    /*v6ro_move_cursor(wind_prop[screen_window][WPROP_Y_CURSOR], temp_x);*/
}

int v6ro_input_get_left(const zword_t * restrict buffer)
{
    return wind_prop[screen_window][WPROP_X_CURSOR]-v6ro_text_length(buffer, strlen_u(buffer));
}

int v6ro_input_will_fit(const zword_t *buffer, unsigned u_char)
{
    zword_t temp[1];
    int cwidth;

    NOT_USED(buffer);

    temp[0]=u_char;

    cwidth=v6ro_text_length(temp, 1);

    return wind_prop[screen_window][WPROP_X_CURSOR]-1+cwidth <= wind_prop[screen_window][WPROP_X_WIDTH]-wind_prop[screen_window][WPROP_R_MARGIN];
}

unsigned v6ro_char_size(int font, int style)
{
    int width;

    if (font==TEXT_FONT)
    {
        if (get_word_priv(H_FLAGS) & FIXED_FONT_FLAG) style |= FIXED_FONT;
        width = (fwidth[style>>1]['0']+400)/800;
        if (width > 255) width = 255;
    }
    else
        width = V6_CHAR_W;

    return (V6_CHAR_H << 8) | width;
}
