
/* RISC OS port of PicoDrive
   Sega MegaDrive emulator

   Copyright (C) Jeffrey Lee 2006
             (C) Adrian Lees 2006
*/

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "kernel.h"
#include "swis.h"

#include "oslib/joystick.h"
#include "oslib/messagetrans.h"
#include "oslib/osbyte.h"
#include "oslib/osfile.h"
#include "oslib/osmodule.h"
#include "oslib/osspriteop.h"
#include "oslib/sound.h"
#include "oslib/uri.h"
#include "oslib/wimp.h"

#include "../Pico/Pico.h"
#include "../Cyclone/Cyclone.h"

#include "defs.h"
extern "C" {
#include "cpu.h"
#include "dma.h"
#include "video_out.h"
};
#include "DRender.h"
#include "log.h"
#include "utils.h"

//#undef USE_DMA

#ifndef max
#define max(x,y) (((x)<(y))?(y):(x))
#endif
#ifndef min
#define min(x,y) (((x)<(y))?(x):(y))
#endif

#ifndef Tinct_PlotScaled
#define Tinct_PlotScaled 0x57243
#endif

#ifndef FILETYPE_SAVE_GAME
#define FILETYPE_SAVE_GAME 0xA88
#endif

/* ----- User interface ----- */

/* icon numbers */

#define INFO_VERSION   4
#define INFO_WEB_SITE  8

/* menu options */

#define VIEWMENU_INFO  0
#define VIEWMENU_SAVE  1
#define VIEWMENU_FULL_SCREEN 2
#define VIEWMENU_PAUSE 3
#define VIEWMENU_RESET 4
#define VIEWMENU_AUDIO 5

#define key_pressed(x) (!xosbyte1(129,(x)^0xff,0xff,&tmp) && (tmp == 0xff))

static char appdir[FILENAME_LENGTH];
static int  appdir_len = 0;

#define NOF_DMA_BUFFERS 2

static bool dma_in_progress = false;
static bool dma_first[NOF_DMA_BUFFERS];
static dma_chain_desc *dma_descs[NOF_DMA_BUFFERS];
static byte *dma_pdescs[NOF_DMA_BUFFERS];

static wimp_t ui_task = 0;

static wimp_w vieww, infow, saveasw;

extern byte viewmenu_defn[];
static wimp_MENU(12) viewmenu;

static bool multitasking = true;

/* ----- Emulation ----- */

extern struct Cyclone PicoCpu;
static bool pico_active = false;
static unsigned char *rom = NULL;

static bool paused = false;

static bool got_cartridge = false;

/* ---- Sound stuff ---- */

#define NOF_SOUND_LENS 32

#define NUM_SOUND_BUFFERS 16

#define BUFFER_SAMPLES 2048
#define SAMPLES_PER_SEC 48000

static bool sound = true;
static bool sound_available = true;
static bool drenderer_active = false;

// simple linear predictor for the number of samples to
// be supplied to DigitalRenderer since the execuion times of PicoFrame()
// invocations with differ (it executes for a fixed number of emulated
// CPU cycles not a fixed period of real time)

static int snd_lens[NOF_SOUND_LENS];
static int snd_len_sum = 0;
static int snd_len_idx = 0;

static short *sndBuffer[NUM_SOUND_BUFFERS];
static int curr_snd_buf = 0;

/* ---- Input stuff ---- */

typedef struct
{
	int nparams;
	bits buttons;
	unsigned xpos;
	unsigned ypos;
	unsigned zpos;
	unsigned xrot;
	unsigned yrot;
	unsigned zrot;
	unsigned slider;
	int hatswitch;
} joystick_state_ext;

/* \todo - mapping from buttons to actions should be user-configurable to
   			accomodate different input devices */
static bits joy_BUTTON_B = 4;
static bits joy_BUTTON_C = 2;
static bits joy_BUTTON_A = 1;
static bits joy_BUTTON_S = 8;
static bits joy_BUTTON_LEFT = 0x40;
static bits joy_BUTTON_RIGHT = 0x80;
static bits joy_BUTTON_UP = 0x10;
static bits joy_BUTTON_DOWN = 0x20;

static bool space_pressed = false;
static bool pause_pressed = false;

extern "C" {
extern os_error *xjoystick_read_extended(int joystick, joystick_state_ext**);
};

/* ---- Video output stuff ----- */

// specifies whether to use Tinct or AML's filtered scaling library for
//   rendering scaled output (the latter has been removed presently)
#define USE_TINCT 1

typedef int (*scan_fn)(unsigned, void *);

#define LEFT_MARGIN 32
#define RIGHT_MARGIN 8

static int display_width;
static int display_height;

static int scr_x, scr_y;  /* position on screen of top-left pixel */
static int scr_dh;

int scr_bpp;
static bool scr_pc_rgb;
static int scr_log2_bpp;
static int scr_start,scr_w,scr_h,scr_linelen,scr_ncol;
static int scr_xeig, scr_yeig;

/* configured full screen mode */
#define FULLSCR_AUTO    0
#define FULLSCR_DESKTOP 1
#define FULLSCR_USER    2

static int fullscr_type = FULLSCR_USER;
//static int fullscr_type = FULLSCR_DESKTOP;
static int fullscr_width = 320;
static int fullscr_height = 240;
static int fullscr_frate = -1;
static int fullscr_log2_bpp = 4;

static int blanker_delay = -1;
static os_mode desktop_mode;
static os_MODE_SELECTOR(8) desktop_mode_sel;
static const os_MODE_SELECTOR(1) default_mode = { 1, 1024, 768, 5, -1, { -1, 0 } };

static bool changed_mode = false;

static int frame_cnt = 0;
static int frame_dec = 1;

bool scale = false;
static bool smooth = false;
static bool render_to_buf = false;

static int vdu_buf = 0;
static int display_buf = 0;

static osspriteop_area *logo_area = NULL; 
static osspriteop_area *area = NULL;
static osspriteop_header *header[2];
static int spr_height;
static int spr_linelen;

static byte *spr_start[2];
static int spr_valid_lines[2];  // number of valid lines

#ifdef USE_DMA
static bool use_dma = true;
#else
static bool use_dma = false;
#endif

//plot_state plot_st;

extern "C" {
extern void convert_16_32(unsigned *dest, unsigned short *src);
extern void scale_16_16(unsigned short *dest, unsigned short *src);
extern void scale_32_32(unsigned *dest, unsigned *src);
};

static void display_init();
static os_error *display_image(int buf);
static void full_screen();
static void return_to_desktop();


#define report_error(e) xwimp_report_error((e), 1, "PicoDrive", NULL)

typedef struct
{
  char    name[12];
  wimp_w *pw;
  byte   *defn;
  int     dsize;
  char   *icons;
  int     isize;
} template_info;

static template_info templates[] =
{
  { "view",       &vieww,       NULL, 0, NULL, 0 },
  { "progInfo",   &infow,       NULL, 0, NULL, 0 },
  { "saveas",	  &saveasw,     NULL, 0, NULL, 0 },
//  { "config",     &configw,     NULL, 0, NULL, 0 },
//  { "prompt",     &promptw,     NULL, 0, NULL, 0 },
//  { "settings",   &settingsw,   NULL, 0, NULL, 0 },
//  { "controls",   NULL,         NULL, 0, NULL, 0 },
//  { "fileinfo",   &fileinfow,   NULL, 0, NULL, 0 },
};

static const wimp_MESSAGE_LIST(3) messages = { {
	message_DATA_LOAD,
	message_MODE_CHANGE,
	message_QUIT
} };


void report_text(const char *fmt, ...)
{
  os_error err;
  va_list va;
  va_start(va, fmt);
  vsprintf(err.errmess, fmt, va);
  va_end(va);
  err.errnum = 0;
  report_error(&err);
}

int RISCOSCram(int cram)
{
	// Expand 0000bbbb ggggrrrr to RISC OS 16bpp
	int out;
	if (scr_bpp == 16)
	{
		if (scr_pc_rgb)
		{
			out = ((cram & 0xF00) >> 7) | ((cram & 0x800) >> 11); // red
			out |= ((cram & 0xF0) << 2) | ((cram & 0x80) >> 2); // green
			out |= ((cram & 0xF) << 11) | ((cram & 0x8) << 7); // blue
		}
		else
		{
			out = ((cram & 0xF) << 1) | ((cram & 0x8) >> 3); // red
			out |= ((cram & 0xF0) << 2) | ((cram & 0x80) >> 2); // green
			out |= ((cram & 0xF00) << 3) | ((cram & 0x800) >> 1); // blue
		}
	}
	else
	{
		out = ((cram & 0xF) << 4) | (cram & 0xF); // red
		out |= ((cram & 0xF0) << 8) | ((cram & 0xF0) << 4); // green
		out |= ((cram & 0xF00) << 12) | ((cram & 0xF00) << 8); // blue
	}
	return out;
}

void display_init()
{
	if (multitasking)
	{
		wimp_window_state state;
		state.w = vieww;
		if (xwimp_get_window_state(&state))
		{
			/* should never happen - just choose some vaguely sensible defaults */
			scr_x = 0;
			scr_y = 0;
			scr_dh = scr_h;
			display_height = 640;
			display_width = 480;
		}
		else
		{
			scr_x = (state.visible.x0 - state.xscroll) >> scr_xeig;
			scr_y = scr_h  - ((state.visible.y1 - state.yscroll) >> scr_yeig);
			if (scr_x < 0) scr_x = 0;
			if (scr_y < 0) scr_y = 0;
			scr_dh = (state.visible.y1 - state.visible.y0) >> scr_yeig;
	
			display_width  = (state.visible.x1 - state.visible.x0) >> scr_xeig;
			display_height = (state.visible.y1 - state.visible.y0) >> scr_yeig;
		}
	}
	else
	{
		display_width = 640;
		display_height = 480;

		if (scr_w >= display_width &&
			scr_h >= display_height)
		{
			display_width = 640;
			display_height = 480;
			scale = 1;
		}
		else
		{
			display_width = 320;
			display_height = 240;
			scale = 0;
		}

		scr_x = (display_width - scr_w)/2;
		scr_y = (display_height - scr_h)/2;
	}

	if (use_dma)
	{
		int i;
		for(i = 0; i < NOF_DMA_BUFFERS; i++)
		{
			if (dma_descs[i])
			{
				release_dma_descs(&dma_descs[i]);
				dma_pdescs[i] = NULL;
			}
			dma_first[i] = true;
		}
	}
	vdu_buf = 0;
	display_buf = 0;

	/* decide whether we should render to our buffer, or directly to the screen */
	render_to_buf = use_dma || scale;
}

os_error *display_image(int buf)
{
	int h = spr_valid_lines[buf];
	os_error *err = NULL;
	if (use_dma)
	{
		if (dma_first[buf])
		{
			int margin_bytes;
			os_box sr, dr;
			int dh;
	
			/* scale for current mode and clamp to window height */
			if (!scale || scr_yeig > scr_xeig)
			{
				dh = min(h, scr_dh);
				h = dh;
			}
			else
			{
				dh = min(2*h, scr_dh);
				h = dh/2;
			}
	
			dr.x0 = scr_x;
			dr.y0 = scr_y;
			dr.x1 = scr_x + (scale ? 640 : 320);
			dr.y1 = scr_y + dh;

			sr.x0 = 0;
			sr.y0 = 0;
			sr.x1 = scale ? 640 : 320;
			sr.y1 = h;

			margin_bytes = (LEFT_MARGIN * scr_bpp)/8;
			if (scale) margin_bytes *= 2;

			err = get_addresses((byte*)scr_start, spr_start[buf] + margin_bytes,
						&dma_descs[buf], &dma_pdescs[buf], &sr, &dr, scr_linelen, spr_linelen,
						dma_first[buf]);
			if (err) return err;
	
			dma_first[buf] = false;
		}

	{
//		size_t sz = spr_linelen * spr_valid_lines[buf];
//	      clean_Dcache(os_GIVEN_CODE_RANGE, spr_start[buf], spr_start[buf] + sz);
	      drain_WB();
	}

	      if (dma_in_progress)
	      {
	        dma_wait_completion(IYONIX_DMA0_BASE, DMA_DEFAULT_TIMEOUT*30);
	        dma_in_progress = false;
	      }
	
	      /* kick off display of the buffer we've just filled */
	
	      dma_start_transfer(IYONIX_DMA0_BASE, dma_pdescs[buf]);
	      dma_in_progress = true;

		err = NULL;
	}
	else if (scale && smooth)
	{
//		if (smooth)
		{
			int req_width = display_width, req_height = display_height;
			os_error *err;
#if USE_TINCT
			const int tinct_options = 0;
			err = (os_error*)_swix(Tinct_PlotScaled, _INR(2,7),
							header[buf], 0, 0, req_width, req_height, tinct_options);
#else
			err = plot_smoothed((byte*)scr_start + (scr_y * scr_linelen) + (scr_x * scr_bpp)/8,
						(unsigned*)(spr_start[buf] + (LEFT_MARGIN*scr_bpp)/8),
						scr_linelen, spr_linelen, 320, 240, 0, 0,
						req_width, req_height, req_width, req_height,
						(use_dma ? PLOT_FLAG_USE_DMA : 0) | (5<<8) | FT_NEAREST, &plot_st);
#endif
		}
//		else
//		{
//			byte *dest, *src;
//			int y;
//	
//			if (scr_yeig > scr_xeig)
//				h = min(h, scr_dh);
//			else
//				h = min(2*h, scr_dh);
//	
//			dest = (byte*)(scr_start + (scr_y*scr_linelen) + (scr_x * scr_bpp)/8);
//			src = (byte*)(spr_start[buf] + (LEFT_MARGIN*scr_bpp)/8);
//	
//			for(y = 0; y < h; y++)
//			{
//				if (scr_bpp == 16)
//					scale_16_16((unsigned short*)dest, (unsigned short*)src);
//				else
//					scale_32_32((unsigned*)dest, (unsigned*)src);
//				dest += scr_linelen;
//				if (scr_yeig > scr_xeig || (y & 1)) src += spr_linelen;
//			}
//		}
	}
	else
	{
		/* simple, vertically-scaled rendering */
		int wb = ((scale ? 640 : 320) * scr_bpp)/8;
		byte *dest, *src;
		int y;

		if (scale) h *= 2;

		if (scr_yeig > scr_xeig)
			h = min(h/2, scr_dh);
		else
			h = min(h, scr_dh);

		dest = (byte*)(scr_start + (scr_y*scr_linelen) + (scr_x * scr_bpp)/8);
		src = (byte*)(spr_start[buf] + ((LEFT_MARGIN*scr_bpp)/8)*(scale ? 2 : 1));

		for(y = 0; y < h; y++)
		{
			memcpy(dest, src, wb);
			dest += scr_linelen;
			if (!scale || (y & 1))
			{
				src += spr_linelen;
				if (scr_yeig > scr_xeig) src += spr_linelen;
			}
		}
	}

	if (!err)
	{
		display_buf = buf;
	}
	return err;
}

void *GetScanlinePtr(int scan)
{
	if (render_to_buf)
		return spr_start[vdu_buf]+scan*spr_linelen;
	else
		return (void*)(scr_start+(scr_y+(scale?2*scan:scan))*scr_linelen + (scr_x*scr_bpp)/8);
}

int EmulateScan32(unsigned int y,unsigned *sdata)
{
	static int py = -1;

	if (y < py) spr_valid_lines[vdu_buf] = py+1;

py = y;
return 0;

//	if (use_dma)
//	{
//		if (y < py)
//		{
//			display_image(vdu_buf);
//			vdu_buf ^= 1;
//		}
//	}
//#if 0
//	else if (scale)
//	{
//		unsigned int *scr = (unsigned *)(scr_start+(scr_y+2*y)*scr_linelen+(scr_x*4));
//		unsigned int *scr2 = (unsigned *)((char*)scr + scr_linelen);
//		scale_32_32(scr, sdata);
//		scale_32_32(scr2, sdata);
//	}
//#if 1
//	else
//	{
//		unsigned int *scr = (unsigned *)(scr_start+(scr_y+y)*scr_linelen+(scr_x*4));
//		memcpy(scr, sdata, 320*4);
//	}
//#endif
//#else
//	else if (y < py){  display_image(vdu_buf); vdu_buf ^= 1; }
//#endif
	py = y;
	return 0;
}

int EmulateScan16(unsigned int y,unsigned short *sdata)
{
	static int py = -1;

	if (y < py) spr_valid_lines[vdu_buf] = py+1;
py = y;
return 0;

//	if (use_dma)
//	{
//		if (y < py)
//		{
//			display_image(vdu_buf);
//			vdu_buf ^= 1;
//		}
//	}
//#if 0
//	else if (scale)
//	{
//		unsigned short *dest = (unsigned short *) (scr_start+(scr_y+2*y)*scr_linelen+scr_x*2);
//		unsigned short *dest2 = (unsigned short *)((char*)dest + scr_linelen);
//		scale_16_16(dest, sdata);
//		scale_16_16(dest2, sdata);
//	}
//#if 1
//	else
//	{
//		// Direct copy
//		unsigned short *dest = (unsigned short *) (scr_start+(scr_y+y)*scr_linelen+scr_x*2);
//		memcpy(dest,sdata,320*2);
//	}
//#endif
//#else
//	else if (y < py){  display_image(vdu_buf); vdu_buf ^= 1; }
//#endif
//	py = y;
//	return 0;
}

int EmulateScan8(unsigned int y,unsigned short *sdata)
{
	// Down to 8pp - do nothing for now
	return 0;
}

void Emulate()
{
	joystick_state_ext *joy_ext;
	int joy_x = 0, joy_y = 0;
	static bool playing = false;
	int pad0 = 0, pad1 = 0;
	joystick_state joy;
	int joy_sw = 0;
	int tmp;

	// Read input

	if (!xjoystick_read(0, &joy))
	{
		// OSLib up to 6.70 (at least), gets X and Y the wrong way around
		joy_x = ((joy & 0xff00) << 16);
		joy_y = ((joy & 0xff) << 24);
		joy_sw = (joy & joystick_SWITCHES) >> joystick_SWITCHES_SHIFT;

		/* create a dead zone in the centre because analogue joysticks
		   cannot be relied upon to produce zero when centred */
		if (joy_x > -(32<<24) && joy_x < (32<<24)) joy_x = 0;
		if (joy_y > -(32<<24) && joy_y < (32<<24)) joy_y = 0;
	}
	if (!xjoystick_read_extended(0, &joy_ext))
	{
		if (joy_ext->hatswitch >= 0)
		{
			if (joy_ext->hatswitch < 68 || joy_ext->hatswitch > 292)
				joy_y = 1;
			else if (joy_ext->hatswitch > 112 && joy_ext->hatswitch < 248)
				joy_y = -1;

			if (joy_ext->hatswitch > 22 && joy_ext->hatswitch < 158)
				joy_x = 1;
			else if (joy_ext->hatswitch > 202 && joy_ext->hatswitch < 338)
				joy_x = -1;
		}
	}

	if (key_pressed(57)  || (joy_sw & joy_BUTTON_UP)    || joy_y > 0) pad0 |= 1;  /* Up */
	if (key_pressed(41)  || (joy_sw & joy_BUTTON_DOWN)  || joy_y < 0) pad0 |= 2;  /* Down */
	if (key_pressed(25)  || (joy_sw & joy_BUTTON_LEFT)  || joy_x < 0) pad0 |= 4;  /* Left */
	if (key_pressed(121) || (joy_sw & joy_BUTTON_RIGHT) || joy_x > 0) pad0 |= 8;  /* Right */

	if (key_pressed(50) || (joy_sw & joy_BUTTON_B)) pad0 |= 0x10;
	if (key_pressed(67) || (joy_sw & joy_BUTTON_C)) pad0 |= 0x20;
	if (key_pressed(81) || (joy_sw & joy_BUTTON_A)) pad0 |= 0x40;
	if (key_pressed(73) || (joy_sw & joy_BUTTON_S)) pad0 |= 0x80;

	if (key_pressed(42)) pad1 |= 1;
	if (key_pressed(124)) pad1 |= 2;
	if (key_pressed(122)) pad1 |= 4;
	if (key_pressed(26)) pad1 |= 8;

	if (key_pressed(54)) pad1 |= 0x10;
	if (key_pressed(102)) pad1 |= 0x20;
	if (key_pressed(70)) pad1 |= 0x40;
//	if (key_pressed(73)) pad1 |= 0x80;

	if (!paused)
	{
		static os_t last_time = 0;
		os_t time_now;

		PicoPad[0] = pad0;
		PicoPad[1] = pad1;
	
		// remove rendering function to decimate video output
		if (++frame_cnt < frame_dec)
			PicoScan = NULL;
		else
		{
			if(scr_ncol == -1)
				PicoScan = (scan_fn)EmulateScan32;
			else if(scr_ncol == 65535)
				PicoScan = (scan_fn)EmulateScan16;
			else if((scr_ncol == 63) || (scr_ncol == 255))
				PicoScan = (scan_fn)EmulateScan8;
			else
				PicoScan = 0;
		}

		time_now = os_read_monotonic_time();
		snd_len_sum -= snd_lens[snd_len_idx];
		snd_len_sum += snd_lens[snd_len_idx] = (48000 * time_now - last_time) / 100;
		if (++snd_len_idx >= NOF_SOUND_LENS) snd_len_idx = 0;
		last_time = time_now;

		/* predict how many samples we're going to need this time */
		PsndLen = snd_len_sum/NOF_SOUND_LENS;
		if (PsndLen < 512) PsndLen = 512;
		if (PsndLen > BUFFER_SAMPLES) PsndLen = BUFFER_SAMPLES;

		PicoFrame();
	
		// Render screen if not rendered directly above
		if (frame_cnt >= frame_dec)
		{
			if (render_to_buf) display_image(vdu_buf);
			vdu_buf ^= 1;
			frame_cnt = 0;
		}
	}

	if (sound && !paused)
	{
		DigitalRenderer_Stream16BitSamples(sndBuffer[curr_snd_buf], PsndLen);
		if (!playing)
		{
			DigitalRenderer_Resume();
			playing = true;
		}
	
		if (++curr_snd_buf >= NUM_SOUND_BUFFERS) curr_snd_buf = 0;
		PsndOut = sndBuffer[curr_snd_buf];
	}
	else
	{
		if (playing)
		{
			DigitalRenderer_Pause();
			playing = false;
		}
	}
}


os_error *createsprites()
{
	unsigned sprite_width = (LEFT_MARGIN + 320 + RIGHT_MARGIN) * 2;
	unsigned sprite_size = sprite_width*4*240;
	const int type = (scr_bpp == 16) ? 5 : 6;
	const int xdpi = 90, ydpi = 90;
	osspriteop_header *s;
	unsigned area_size;
	os_error *err;

	free(area);
	area = NULL;

	area_size = sizeof(osspriteop_area)+2*(sizeof(osspriteop_header)+sprite_size);

	area = (osspriteop_area*)malloc(area_size);
	if (!area)
	{
	  fprintf(stderr, "Not enough memory to create sprite (need %d bytes)\n", area_size);
		return (os_error*)"\0\0\0\0Not enough memory to creates";
	}
	
	area->first = sizeof(*area);
	area->size  = area_size;
	
	err = xosspriteop_clear_sprites(osspriteop_USER_AREA, area);
	if (err)
	{
		report_error(err);
		return err;
	}
	
	s = (osspriteop_header*)(area + 1);
	s->size = sizeof(osspriteop_header) + sprite_size;
	memcpy(s->name, "sprite1\0\0\0\0\0", 12);
	s->width     = sprite_width - 1;
	s->height    = 240 - 1;
	s->left_bit  = 0;
	s->right_bit = 31;
	s->image     = sizeof(*s);
	s->mask      = sizeof(*s);
	s->mode      = (os_mode)((type << 27) | (xdpi << 1) | (ydpi << 14) | 1);
	spr_linelen = 4 * (s->width + 1);

	if (logo_area)
	{
		byte *dp_row = (byte*)s + s->image;
		osspriteop_header *logos = (osspriteop_header*)((char*)logo_area + logo_area->first);
		unsigned *sp_row = (unsigned*)((char*)logos + logos->image);
		int y;
		for(y = 0; y <= logos->height; y++)
		{
			if (scr_bpp == 16)
			{
				byte *dp = dp_row + 32*2;
				unsigned *sp = sp_row;
				int x;
				for(x = 0; x <= logos->width; x++)
				{
					byte b = (*sp >> 16);
					byte g = (*sp >> 8);
					byte r = *sp;
					b >>= 3; g >>= 3; r >>= 3;
					if (scr_pc_rgb)
						*(unsigned short*)dp = b | (g << 5) | (r << 10);
					else
						*(unsigned short*)dp = r | (g << 5) | (b << 10);
					sp++;
					dp += 2;
				}
			}
			else if (scale)
			{
				byte *dp = dp_row + 32*4;
				unsigned *sp = sp_row;
				int x;
				for(x = 0; x <= logos->width; x++)
				{
					byte b = (*sp >> 16);
					byte g = (*sp >> 8);
					byte r = *sp;
					unsigned d = r | (g << 8) | (b << 16);
					*(unsigned*)dp = d;
					((unsigned*)dp)[1] = d;
					dp += 8;
					sp++;
				}
			}
			else
				memcpy(dp_row + 32*4, sp_row, 320*4);
			sp_row += logos->width + 1;
			dp_row += spr_linelen;
		}
	}

	header[0] = s;
	spr_start[0] = (byte*)s + s->image;
	spr_valid_lines[0] = spr_height = s->height + 1;

	s = (osspriteop_header*)((byte*)s + s->size);

	s->size = sizeof(osspriteop_header) + sprite_size;
	memcpy(s->name, "sprite2\0\0\0\0\0", 12);
	s->width     = sprite_width - 1;
	s->height    = 240 - 1;
	s->left_bit  = 0;
	s->right_bit = 31;
	s->image     = sizeof(*s);
	s->mask      = sizeof(*s);
	s->mode      = (os_mode)((type << 27) | (xdpi << 1) | (ydpi << 14) | 1);
	
	header[1] = s;
	spr_start[1] = (byte*)s + s->image;
	spr_valid_lines[1] = s->height + 1;

	return NULL;
}


void exit_handler()
{
	if (drenderer_active)
	{
		int i;
		drenderer_active = false;
		_kernel_oserror *err = DigitalRenderer_Deactivate();
		if (err)
			fprintf(stderr, "Failed to deactivate DigitalRenderer ('%s')\n", err->errmess);

		for(i = 0; i < NUM_SOUND_BUFFERS; i++)
		{
			if (sndBuffer[i])
			{
				xosmodule_free(sndBuffer[i]);
				sndBuffer[i] = NULL;
			}
		}
	}
	if (pico_active)
		PicoExit();
	return_to_desktop();
}


os_error *get_screen_info()
{
	static const os_VDU_VAR_LIST(9) screen_vars =
	{
		os_VDUVAR_DISPLAY_START,
		os_MODEVAR_LINE_LENGTH,
		os_MODEVAR_XWIND_LIMIT,
		os_MODEVAR_YWIND_LIMIT,
		os_MODEVAR_NCOLOUR,
		os_MODEVAR_XEIG_FACTOR,
		os_MODEVAR_YEIG_FACTOR,
		os_MODEVAR_LOG2_BPP,
		os_VDUVAR_END_LIST
	};
	int screen_vals[9];
	os_error *err;

	err = xos_read_vdu_variables((os_vdu_var_list*)&screen_vars, screen_vals);
	if (err) return err;

	scr_start = screen_vals[0];
	scr_linelen = screen_vals[1];
	scr_w = screen_vals[2]+1;
	scr_h = screen_vals[3]+1;
	scr_ncol = screen_vals[4];
	scr_xeig = screen_vals[5];
	scr_yeig = screen_vals[6];
	scr_bpp = 1 << (scr_log2_bpp = screen_vals[7]);

	/* determine whether we can use DMA (currently IOP321 only) */
	if ((cpu_id() & CPU_MASK) != CPU_80321 ||
	    scr_w >= 2 * scr_h)  /* quick fudge for Geminus dual screen landscape */
	{
		use_dma = false;
		frame_dec = 2;
	}
	else
	{
		frame_dec = 1;
#ifdef USE_DMA
		use_dma = true;
#endif
	}

	// Set display callback
	if(scr_ncol == -1)
		PicoScan = (scan_fn)EmulateScan32;
	else if(scr_ncol == 65535)
		PicoScan = (scan_fn)EmulateScan16;
	else if((scr_ncol == 63) || (scr_ncol == 255))
		PicoScan = (scan_fn)EmulateScan8;
	else
		PicoScan = 0;

#ifdef USE_DMA
	if (use_dma) err = output_init();
#if !USE_TINCT
	err = plot_init(&plot_st, malloc);
#endif
#endif

	/* assume PC RGB order for 16bpp modes on more recent NV GeForce cards which
	   don't have the RGB colour swap; in the continuing absence of a proper API call
	   (preferably a mode variable) to ascertain this */
	scr_pc_rgb = (gfxcard_pc_rgb && scr_log2_bpp == 4);

	return err;
}

os_error *load_cartridge(const char *filename)
{
	_kernel_oserror *err;
	unsigned int size;
	int numbufs;
	FILE *f;

	f = fopen(filename,"rb");
	if(!f) return (os_error*)"\0\0\0\0Could not open ROM file\n";

	free(rom);
	rom = NULL;
	PicoCartLoad(f,&rom,&size);
	fclose(f);
	PicoCartInsert(rom,size);
	PicoReset();

	if (sound_available)
	{
		int i;
		for(i = 0; i < NUM_SOUND_BUFFERS; i++)
		{
			if (!sndBuffer[i])
				xosmodule_alloc(BUFFER_SAMPLES*4, (void**)&sndBuffer[i]);
		}
	
		PsndLen = BUFFER_SAMPLES;
		PsndRate = SAMPLES_PER_SEC;
	
		numbufs = DigitalRenderer_NumBuffers(NUM_SOUND_BUFFERS);
	//	printf("%d buffers for streaming\n", numbufs);
	
		atexit(exit_handler);
	
		drenderer_active = true;
		err = DigitalRenderer_Activate16(2, BUFFER_SAMPLES, SAMPLES_PER_SEC, 1);
		if (err)
		{
			drenderer_active = false;
			return (os_error*)err;
		}
		else
		{
			PsndOut = sndBuffer[0];
			curr_snd_buf = 0;
		}
	}
	return NULL;
}

void full_screen()
{
	os_MODE_SELECTOR(2) defn;
	os_error *err;
	int i;

	/* remember the current screen mode */
	err = xosscreenmode_current(&desktop_mode);  //??? need to copy the descriptor too!!!
	if (err)
	{
	  /* choose a sensible mode to return to */
	  desktop_mode = (os_mode)&default_mode;
	}
	else
	{
	  /* copy the mode selector */
	  if ((unsigned)desktop_mode >= 0x100)
	  {
	    os_mode_selector *md = (os_mode_selector*)desktop_mode;
	    int i = 0;
	    memcpy(&desktop_mode_sel, md, sizeof(os_mode_selector_base));
	    while (i < 7 && md->modevars[i].var != (os_mode_var)-1)
	    {
	      desktop_mode_sel.modevars[i].var = md->modevars[i].var;
	      desktop_mode_sel.modevars[i].val = md->modevars[i].val;
	      i++;
	    }
	    /* terminate list */
	    desktop_mode_sel.modevars[i].var = (os_mode_var)-1;
	    desktop_mode = (os_mode)&desktop_mode_sel;
	  }
	}
	
	for(i = 0; i < NOF_DMA_BUFFERS; i++)
	{
		if (dma_descs[i])
		{
			release_dma_descs(&dma_descs[i]);
			dma_pdescs[i] = NULL;
		}
		dma_first[i] = true;
	}
	
	/* choose the screen mode that we will use */
	defn.flags = 1;
	switch (fullscr_type)
	{
		case FULLSCR_AUTO:
			break;

		case FULLSCR_DESKTOP:
			defn.xres = scr_w;
			defn.yres = scr_h;
			defn.frame_rate = -1;
			defn.log2_bpp = scr_log2_bpp;
			break;

		case FULLSCR_USER:
		default:
		    defn.xres = fullscr_width;
		    defn.yres = fullscr_height;
		    defn.frame_rate = fullscr_frate;
		    defn.log2_bpp = fullscr_log2_bpp;
		    break;
	}

	defn.modevars[0].var = (os_mode_var)-1;
	err = xosscreenmode_select((os_mode)&defn);
	if (!err) err = get_screen_info();

	if (err)
	{
		report_error(err);
	}
	else
	{
		static const char vdu[] = { 23,1,0,0,0,0,0,0,0,0 };

		if (xscreenblanker_get_delay(&blanker_delay))
		  blanker_delay = -1;
		else
		  xscreenblanker_set_delay(0);

		xos_writen(vdu, 10);
		xos_byte(4, 1, 0, NULL, NULL);

		vdu_buf = 0;
		xosbyte(229,0,1);
		multitasking = false;

		changed_mode = true;
		display_init();
	}
}

void return_to_desktop()
{
    if (changed_mode)
    {
      /* return to desktop mode */
      os_error *err = xwimp_set_mode(desktop_mode);
      if (err) report_error(err);
      changed_mode = false;
    }

    if (blanker_delay != -1)
	{
      xscreenblanker_set_delay(blanker_delay);
      blanker_delay = -1;
	}
}

void set_view_extent()
{
	wimp_window_state state;
	state.w = vieww;
	if (!xwimp_get_window_state(&state))
	{
		int w = scale ? 640 : 320;
		int h = scale ? 480 : 240;
		state.visible.x1 = state.visible.x0 + (w << scr_xeig);
		state.visible.y0 = state.visible.y1 - (h << scr_yeig);
		xwimp_open_window((wimp_open*)&state);
	}
}

void update_menu_state()
{
	if (!got_cartridge)
	{
		viewmenu.entries[VIEWMENU_FULL_SCREEN].icon_flags |= wimp_ICON_SHADED;
		viewmenu.entries[VIEWMENU_PAUSE].icon_flags |= wimp_ICON_SHADED;
		viewmenu.entries[VIEWMENU_RESET].icon_flags |= wimp_ICON_SHADED;
	}
	if (paused)
		viewmenu.entries[VIEWMENU_PAUSE].menu_flags |= wimp_MENU_TICKED;
	else
		viewmenu.entries[VIEWMENU_PAUSE].menu_flags &= ~wimp_MENU_TICKED;

	if (sound)
		viewmenu.entries[VIEWMENU_AUDIO].menu_flags |= wimp_MENU_TICKED;
	else
		viewmenu.entries[VIEWMENU_AUDIO].menu_flags &= ~wimp_MENU_TICKED;
}

int main(int argc,char **argv)
{
	const char *logo_file = "<PicoDrive$Dir>.logo";
	fileswitch_object_type obj_type;
	wimp_window_state state;
	bool quit = false;
	osbool has16bit;
	os_error *err;
	bits ftype;
	int size;
	int i;

	err = xos_read_var_val("PicoDrive$Dir", appdir, sizeof(appdir) - 24, 0, os_VARTYPE_EXPANDED,
			&appdir_len, NULL, NULL);
	if (!err) err = messages_open(appdir, appdir_len);
	if (err)
	{
		report_error(err);
		return 1;
	}

	ui_task = wimp_initialise((wimp_version_no)311, "PicoDrive", (const wimp_message_list *)&messages, NULL);

	sprintf(appdir + appdir_len, ".Resources.%s.Templates", territory_name);
	wimp_open_template(appdir);

	for(i = 0; i < NOF_ELEMENTS(templates); i++)
	{
		char *name = templates[i].name;
		int used, data_used;
		char *icons;
		byte *defn;

		err = xwimp_load_template(wimp_GET_SIZE, NULL, 0, (byte*)-1, name, 0, &used, &data_used, NULL);
		if (!err)
		{
			defn = (byte*)malloc(used);
			icons = (char*)malloc(data_used);
			if (!defn || !icons)
				err = lookup_error(error_MEMSHORT);

			templates[i].defn = defn;
			templates[i].dsize = used;

			templates[i].icons = icons;
			templates[i].isize = data_used;

			err = xwimp_load_template((wimp_window*)defn, icons, icons + data_used, (byte*)-1, name, 0, NULL, NULL, NULL);
			if (!err)
			{
				wimp_icon *icn = ((wimp_window*)defn)->icons;
				switch (i)
				{
					case 1:
						strcpy(icn[INFO_VERSION].data.indirected_text.text, VERSION);
						break;
				}
				err = xwimp_create_window((wimp_window*)defn, templates[i].pw);
			}
		}
		if (err)
		{
			report_error(err);
			return 2;
		}
	}
	wimp_close_template();

	state.w = vieww;
	wimp_get_window_state(&state);
	state.next = wimp_TOP;
	wimp_open_window((wimp_open*)&state);
	wimp_set_caret_position(vieww, (wimp_i)-1, 0, 0, 1<<25, -1);

	PicoCram = RISCOSCram;
	PicoInit();
	pico_active = true;

	// disable the sound on A9home because otherwise it will disable us!
	// \todo - work out why!
	if (!xosmodule_lookup("VideoHWSMI", NULL, NULL, NULL, NULL, NULL) ||
		xsoundmode_read_configuration(&has16bit, NULL) || !has16bit)
	{
		sound_available = false;
		sound = false;
	}

	// load a logo sprite, if we can
	if (!xosfile_read_stamped(logo_file, &obj_type, NULL, NULL, &size, NULL, &ftype) &&
		obj_type == osfile_IS_FILE && ftype == osfile_TYPE_SPRITE)
	{
		logo_area = (osspriteop_area*)malloc(4 + size);
		if (logo_area)
		{
			logo_area->size = 4 + size;
			logo_area->first = sizeof(*logo_area);

			if (xosspriteop_clear_sprites(osspriteop_USER_AREA, logo_area) ||
				xosspriteop_load_sprite_file(osspriteop_USER_AREA, logo_area, logo_file))
			{
				free(logo_area);
				logo_area = NULL;
			}
		}
	}

	err = get_screen_info();
	if (!err) err = createsprites();
	if (err)
	{
		report_error(err);
		return 1;
	}

	if (argc >= 2)
	{
		os_error *err = load_cartridge(argv[1]);
		if (err)
		{
			report_error(err);
			return 1;
		}
		got_cartridge = true;
	}

	set_view_extent();

	while (!quit)
	{
		const int poll_interval = 30;
		wimp_event_no event;
		wimp_block blk;

		if (multitasking)
		{
			os_t time_now;

			if (dma_in_progress)
			{
	        		dma_wait_completion(IYONIX_DMA0_BASE, DMA_DEFAULT_TIMEOUT*30);
	        		dma_in_progress = false;
			}

			time_now = os_read_monotonic_time();
#if 0
			event = wimp_poll_idle(((got_cartridge && !paused) ? 0 : wimp_MASK_NULL) | wimp_SAVE_FP, &blk,
						time_now + 1, NULL);
#else
			event = wimp_poll(((got_cartridge && !paused) ? 0 : wimp_MASK_NULL) | wimp_SAVE_FP, &blk,
						NULL);
#endif
			switch (event)
			{
				case wimp_NULL_REASON_CODE:
					if (got_cartridge)
					{
						os_t start_time = os_read_monotonic_time();
						display_init();
						do
						{
							Emulate();
						} while ((os_read_monotonic_time() - start_time) < poll_interval);
					}
					break;
			
				case wimp_REDRAW_WINDOW_REQUEST:
				{
					osbool more;
					if (!xwimp_redraw_window(&blk.redraw, &more) && more)
					{
						// render the current frame into our buffer if we've been
						//   rendering to the screen so that we have something to
//						if (got_cartridge && !use_dma && !scale)
//						{
//							render_to_buf = true;
//							PicoFrame();
//						}

						display_init();
						do
						{
							/* show the last displayed frame again */
							os_error *err = display_image(display_buf);
							if (err) log_printf("display_image err: %.8X %s\n", err->errnum, err->errmess);
						} while (!xwimp_get_rectangle(&blk.redraw, &more) && more);
					}
				}
				break;
			
				case wimp_OPEN_WINDOW_REQUEST:
					wimp_open_window(&blk.open);
					break;
			
				case wimp_CLOSE_WINDOW_REQUEST:
					quit = true;
					break;
			
				case wimp_MOUSE_CLICK:
					err = NULL;
					if (blk.pointer.w == vieww)
					{
						if (blk.pointer.buttons & wimp_CLICK_MENU)
						{
							err = xmessagetrans_make_menus(&msgs, viewmenu_defn, (char*)&viewmenu,
										sizeof(viewmenu), NULL);
							if (!err)
							{
								viewmenu.entries[VIEWMENU_INFO].sub_menu = (wimp_menu*)infow;
								viewmenu.entries[VIEWMENU_SAVE].sub_menu = (wimp_menu*)saveasw;
								update_menu_state();
								err = xwimp_create_menu((wimp_menu*)&viewmenu,
									blk.pointer.pos.x - 120, blk.pointer.pos.y + 22);
							}
						}
						else
							err = xwimp_set_caret_position(blk.pointer.w, (wimp_i)-1, 0, 0, 1<<25, -1);
					}
					else if (blk.pointer.w == infow && blk.pointer.i == INFO_WEB_SITE)
					{
						if (xosmodule_lookup("AcornURI", NULL, NULL, NULL, NULL, NULL))
						{
							static const char cmd[] = "RMRun System:Modules.Network.URI";
							const char *filename = cmd + 6;
							fileswitch_object_type obj_type;
							wimp_t child = 0;
							
							err = xosfile_read_stamped(filename, &obj_type, NULL, NULL, NULL, NULL, NULL);
							if (err || obj_type != osfile_IS_FILE)
								err = NULL;
							else
								err = xwimp_start_task(cmd, &child);
							if (!err && !child) err = lookup_error(error_CANT_START_URI);
						}
						
						if (!err)
						{
							char url[80];
							err = messages_lookup(url, sizeof(url), "_URL", NULL, NULL, NULL, NULL);
							if (!err) err = xuri_dispatch(0, url, ui_task, NULL, NULL, NULL);
						}
					}
					if (err) report_error(err);
					break;
			
				case wimp_KEY_PRESSED:
					switch (blk.key.c)
					{
						case 1:
							sound = !sound;
							if (!sound_available) sound = false;
							break;
						case 32:
							if (!space_pressed && got_cartridge)
							{
								space_pressed = true;
								full_screen();
							}
							else
								space_pressed = false;
							break;
						case wimp_KEY_TAB:
							{
								wimp_draw draw;
								osbool more;
								scale = !scale;
								fullscr_width = scale ? 640 : 320;
								fullscr_height = scale ? 480 : 240;
								err = createsprites();
								if (err) report_error(err);
								set_view_extent();
								draw.w = vieww;
								draw.box.x0 = SHRT_MIN;
								draw.box.y0 = SHRT_MIN;
								draw.box.x1 = SHRT_MAX;
								draw.box.y1 = SHRT_MAX;
								if (!xwimp_update_window(&draw, &more) && more)
								{
									display_init();
									do
									{
										display_image(display_buf);
									} while (!xwimp_get_rectangle(&draw, &more) && more);
								}
							}
							break;
						case 19:
							smooth = !smooth;
							break;

						case 'P':
						case 'p':
							paused = !paused;
							break;

						default:
							xwimp_process_key(blk.key.c);
							break;
					}
					// debounce space when returning to desktop
					if (blk.key.c != 32) space_pressed = false;
					break;
				
				case wimp_MENU_SELECTION:
					{
						wimp_pointer ptr;
						bool adjust_clicked;
						if (xwimp_get_pointer_info(&ptr))
							adjust_clicked = false;
						else
							adjust_clicked = (ptr.buttons & wimp_CLICK_ADJUST);

						switch (blk.selection.items[0])
						{
							case VIEWMENU_INFO:
								xwimp_create_menu((wimp_menu*)infow, ptr.pos.x, ptr.pos.y);
								adjust_clicked = false;
								break;
							case VIEWMENU_SAVE:
								xwimp_create_menu((wimp_menu*)saveasw, ptr.pos.x, ptr.pos.y);
								adjust_clicked = false;
								break;
							case VIEWMENU_FULL_SCREEN:
								if (got_cartridge) full_screen();
								break;
							case VIEWMENU_PAUSE:
								paused = !paused;
								break;
							case VIEWMENU_RESET:
								if (got_cartridge)
								{
									PicoReset();
								}
								break;
							case VIEWMENU_AUDIO:
								sound = !sound;
								if (!sound_available) sound = false;
								break;
						}

						if (adjust_clicked)
						{
							update_menu_state();
							xwimp_create_menu((wimp_menu*)&viewmenu, ptr.pos.x, ptr.pos.y);
						}
					}
					break;
				
				case wimp_USER_MESSAGE:
				case wimp_USER_MESSAGE_RECORDED:
					switch (blk.message.action)
					{
						case message_QUIT:
							quit = true;
							break;
	
						case message_DATA_LOAD:
						if (!got_cartridge)
						{
							wimp_message *message = &blk.message;
							switch (message->data.data_xfer.file_type)
							{
								case FILETYPE_SAVE_GAME:
									PmovFile = fopen(message->data.data_xfer.file_name, "rb");
									if (PmovFile)
									{
										PmovState();
										fclose(PmovFile);
										PmovFile = NULL;
									}
									else
									{
										err = (os_error*)"\0\0\0\0Cannot load game state '%s'\n"; //
										report_error(err);
									}
									break;

								default:
									err = load_cartridge(message->data.data_xfer.file_name);
									if (err)
										report_error(err);
									else
										got_cartridge = true;
									break;
							}
							/* send DataLoadAck */
							message->action = message_DATA_LOAD_ACK;
							message->your_ref = message->my_ref;
							err = xwimp_send_message(wimp_USER_MESSAGE, message, message->sender);
							if (err) report_error(err);
						}
						break;
	
						case message_MODE_CHANGE:
							err = get_screen_info();
							if (!err) err = createsprites();
							if (err)
							{
								report_error(err);
								quit = true;
							}
							break;
					}
					break;
			}
		}
		else
		{
			bool to_desktop = false;
			bits psr;
			int tmp;

			Emulate();

			if (pause_pressed)
			{
				if (!key_pressed(55)) pause_pressed = false;
			}
			else if (key_pressed(55))
			{
				pause_pressed = true;
				paused = !paused;
			}

			if (space_pressed)
			{
				if (!key_pressed(98)) space_pressed = false;
			}
			else if (key_pressed(98))
			{
				space_pressed = true;
				to_desktop = true;
			}

			if (!to_desktop && ((!xos_read_escape_state(&psr) && (psr & _C)) || key_pressed(112)))
				to_desktop = true;

			if (to_desktop)
			{
				multitasking = true;
				xosbyte(osbyte_ACKNOWLEDGE_ESCAPE, 0, 0);
				xosbyte(229,0,0);
				return_to_desktop();
			}
		}
	}

	PicoCartInsert(NULL,0);
	free(rom);
	rom = NULL;
	pico_active = false;
	PicoExit();
	return 0;
}
