/*
 * File: main-ros.c
 *
 * Abstract: Support for RISC OS versions of Angband, including support
 * for multitasking and dynamic areas.
 *
 * Authors: Musus Umbra, Andrew Sidwell, Ben Harrison, and others.
 *
 * Licences: Angband licence.
 */

#ifdef __riscos

#include "angband.h"

/*
 * Purpose: Support for RISC OS Angband 2.9.x onwards (and variants)
 *
 * NB: This code is still under continuous development - if you want to use
 * it for your own compilation/variant, please contact me so that I can
 * keep you up to date and give you support :)
 *
 * Prerequisites to compiling:
 *
 * DeskLib 2.30 or later  (earlier versions may be OK though)
 *
 * An ANSI C compiler (tested with Acorn's C/C++ and GCC, but should be OK
 *                     with any decent compiler)
 *
 * My binary distribution (for the templates and other bits)
 *
 * Note:
 *   The following symbols are *required* and *must* be defined properly.
 */

/*
 * VARIANT & VERSION
 *   These two get variant and version data from Angband itself; older
 *   variants may not have these defined and will have to be altered.
 */
#define VARIANT		"ToME"
#define VERSION		"2.2.2"

/*
 * PORTVERSION
 *   This is the port version; it appears in the infobox.
 */
#define PORTVERSION	"1.29-dev (2003-08-06)"

/*
 * RISCOS_VARIANT
 *  This must match the entry in the !Variant Obey file, and it must only
 *  contain characters that are valid as part of a RISC OS path variable.
 *  [eg. "Yin-Yangband" is not okay, "EyAngband" is.]
 */
#define RISCOS_VARIANT	"ToME"

/*
 * AUTHORS
 *  For the info box. [eg. "Ben Harrison"]
 */
#define AUTHORS		"DarkGod & co."

/*
 * PORTERS
 *  For the info box. [eg. "Musus Umbra"]
 */
#define PORTERS		"A. Sidwell"

/*
 * ICONNAME
 *  Iconbar icon sprite name eg. "!angband".  Note that this must be a valid
 *  sprite name; it may need modifying for long variant names.
 */
#define ICONNAME	"!"RISCOS_VARIANT

/*
 * PDEADCHK
 *   This should expand to an expression that is true if the player is dead.
 *   [eg. (p_ptr->is_dead) for Angband or (!alive || dead) for some Zangbands]
 */

#define PDEADCHK	(!alive)

/*
 * The following symbols control the (optional) file-cache:
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * NB: Variants that don't repeatedly read any files whilst running
 * (eg. vanilla, sang, etc) should NOT define USE_FILECACHE, etc. as
 * it causes a non-negligable amount of code to be compiled in.
 *
 * NB: The file-cache functions require that some code in files.c is modified
 * to use the cached_* functions.  This should be utterly trivial.
 *
 * NB: The returned handle from cached_fopen() is almost certainly *NOT*
 * a |FILE*| (although it may be if the cache cannot accomodate the file).
 *
 * Therefore, you *MUST* ensure that any file opened with cached_fopen()
 * is only ever accessed via cached_fgets() and cached_fclose().
 *
 * Failure to do so will result in, ahem, unpleasantness.  Extreme
 * unpleasantness.  "Him fall down, go boom."
 *
 * This /may/ change in the near future (ie. to apply caching in a
 * transparent manner), so do keep a backup of files.c (and any other files
 * you modify).  You always keep backups anyway, don't you?  Don't you?!
 */

/*
 * USE_FILECACHE
 *   if defined then some caching functions will be compiled for use by the
 *   various get_rnd_line(), etc. in files.c.  This could be used in a
 *   variety of places that read data repeatedly, but it's up to you to
 *   implement it then.
 */

/* #define USE_FILECACHE */

/*
 * SMART_FILECACHE
 *   This causes lines beginning with '#' (and blank lines) to be discarded
 *   when caching files.  This should help Zangband 2.2.5+ but could cause
 *   trouble for other variants.  If defined, then smart file caching will be
 *   on by default.
 */

/* #define SMART_FILECACHE */

/*
 * ABBR_FILECACHE
 *   ABBR_FILECACHE causes data read into file-cache to be compressed (using a
 *   simple set of abbreviations) by default.  This can be overridden using a
 *   command line option.  If this symbol is not defined then no compression
 *   code will be compiled and the user option will be ignored/unavailable.
 */

/* #define ABBR_FILECACHE */

/*
 * Note:
 *   The following symbols control debugging information.
 */

/*
 * FE_DEBUG_INFO
 *  If defined, some functions will be compiled to display some info. on the
 *  state of the front-end (accessible) from the '!' user menu.
 *
 *  NB: For actual releases you should NOT define this symbol since it causes
 *  a non-negligable amount of code/data to be sucked in.
 */
/* #define FE_DEBUG_INFO */

/*
 * USE_DA
 *  If defined, it enables the use of dynamic areas (these are still only
 *  used when the !Variant file allows it).  It is likely that this option
 *  will eventually be removed altogether as there is no major advantege
 *  to using DAs over just using the Wimpslot.
 */
#define USE_DA


/* Constants, etc. ---------------------------------------------------------*/

/* Deal with any weird file-caching symbols */
#ifndef USE_FILECACHE
# undef ABBR_FILECACHE
# undef SMART_FILECACHE
#endif

/* Maximum terminals */
#define MAX_TERM_DATA 8

/* Menu entry numbers */
#define IBAR_MENU_INFO			0
#define IBAR_MENU_SAVE			1
#define IBAR_MENU_FULLSCREEN	2
#define IBAR_MENU_GAMMA			3
#define IBAR_MENU_SOUND			4
#define IBAR_MENU_WINDOWS		5
#define IBAR_MENU_SAVECHOICES	6
#define IBAR_MENU_QUIT			7

#define TERM_MENU_INFO			0
#define TERM_MENU_SAVE			1
#define TERM_MENU_FONT			2
#define TERM_MENU_WINDOWS		3

/* Icon numbers */
#define SND_VOL_SLIDER			0
#define SND_VOL_DOWN			1
#define SND_VOL_UP				2
#define SND_ENABLE				3

#define GAMMA_ICN				0
#define GAMMA_DOWN				1
#define GAMMA_UP				2

#define SAVE_ICON				2
#define SAVE_PATH				1
#define SAVE_OK					0
#define SAVE_CANCEL				3

/* Position and size of the colours strip in the gamma window */
#define GC_XOFF 20
#define GC_YOFF -14
#define GC_WIDTH 512
#define GC_HEIGHT 72

/* Maximum and minimum allowed volume levels */
#define SOUND_VOL_MIN			16
#define SOUND_VOL_MAX			176

/*--------------------------------------------------------------------------*/


#undef rename
#undef remove

#include "Desklib:Event.h"
#include "Desklib:EventMsg.h"
#include "Desklib:Template.h"
#include "Desklib:Window.h"
#include "Desklib:Handler.h"
#include "Desklib:Screen.h"
#include "Desklib:Menu.h"
#include "Desklib:Msgs.h"
#include "Desklib:Icon.h"
#include "Desklib:Resource.h"
#include "Desklib:SWI.h"
#include "Desklib:Time.h"
#include "Desklib:Sound.h"
#include "Desklib:KeyCodes.h"
#include "Desklib:Kbd.h"
#include "Desklib:GFX.h"
#include "Desklib:ColourTran.h"
#include "Desklib:Error.h"
#include "Desklib:Coord.h"
#include "Desklib:Slider.h"
#include "Desklib:Hourglass.h"
#include "Desklib:Save.h"
#include "Desklib:Sprite.h"
#include "Desklib:KernelSWIs.h"
#include "DeskLib:Filing.h"    

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <stdarg.h>
#include <time.h>
#include <math.h>

/*--------------------------------------------------------------------------*/

/*
 | We use the hourglass around calls to Wimp_Poll in an attempt to stop
 | users thinking that the game has 'hung'.
 | Kamband/Zangband and the Borg in particular can have quite long delays at
 | times.
 */
#define Start_Hourglass \
	{ if ( use_glass && !glass_on ) { glass_on=1; Hourglass_Start(50); } }
#define Stop_Hourglass \
	{ if ( glass_on ) { glass_on=0; Hourglass_Off(); } }


/*--------------------------------------------------------------------------*/
/* Types																	*/
/*--------------------------------------------------------------------------*/

/*
 | A ZapRedraw block
 */
typedef struct
{
	union
	{
		unsigned int value;
		struct
		{
			unsigned int vdu:1;
			unsigned int double_height:1;
			unsigned int extension:1;
			unsigned int padding:29;
		}
		bits;
	}
	r_flags;

	int r_minx;	/* min x of redraw in pixels from LHS, incl */
	int r_miny;	/* min y of redraw in pixels from top, incl */
	int r_maxx;	/* max x of redraw in pixels from LHS, excl */
	int r_maxy;	/* max y of redraw in pixels from top, excl */

	void *r_screen;	/* DSA: address of screen to write to (0=>read) */
	int r_bpl;	/* DSA: bytes per raster line */

	int r_bpp;	/* log base 2 of bits per pixel */
	int r_charw;	/* width of a character in pixels */
	int r_charh;	/* height of a character in pixels */
	void *r_caddr;	/* DSA: ->character cache | VDU: ->font name */
	int r_cbpl;	/* DSA: #bytes/character line | VDU: x OS offset */
	int r_cbpc;	/* DSA: #bytes/character | VDU: y OS offset */

	int r_linesp;	/* line spacing (pixels) */

	void *r_data;	/* -> text to display */
	int r_scrollx;	/* see Redraw dox */
	int r_scrolly;	/* see Redraw dox */

	void *r_palette;	/* -> palette lookup table */
	int r_for;	/* foreground colour at start of line */
	int r_bac;	/* background colour at start of line */

	void *r_workarea;	/* -> word aligned workspace */

	int r_magx;	/* log2 x OS coords per pixel */
	int r_magy;	/* log2 y OS coords per pixel */

	int r_xsize;	/* width of screen in pixels */
	int r_ysize;	/* height of screen in pixels */

	int r_mode;	/* current screen mode */
}
ZapRedrawBlock;


/*
 | We cache font data using an array of 'font handles' (since there is a
 | known maximum no. of fonts required).
 | This is what a font 'handle' looks like:
 */
typedef struct
{
	char *name;	/* font name */
	int usage;	/* usage count */
	int w, h;	/* width, height */
	int f, l;	/* first and last character defined */
	void *bpp_1;	/* source bitmap */
	void *bpp_n;	/* bitmap for the current screen mode */
}
ZapFont;

/*
 | A struct to hold all the data relevant to a term window
 */
typedef struct
{
	term t;	/* The Term itself */
	window_handle w;	/* Window handle */
	ZapFont *font;	/* Font */
	wimp_box changed_box;	/* Area out of date */
	struct
	{
	wimp_point pos;	/* Cursor position */
	BOOL visible;	/* visibility flag */
	}
	cursor;
	char name[12];	/* Name to give menus opened from the term */
	int def_open;	/* Open by default? */
	wimp_box def_pos;	/* default position */
	wimp_point def_scroll;	/* default scroll offset */
	int unopened;	/* Has this window not been opened yet? */
}
term_data;



/*--------------------------------------------------------------------------*/
/* ZapRedraw SWI numbers													*/
/*--------------------------------------------------------------------------*/

#define SWI_ZapRedraw_ 0x48480
#define SWI_ZapRedraw_RedrawArea (SWI_ZapRedraw_ + 0x00)
#define SWI_ZapRedraw_GetPaletteEntry (SWI_ZapRedraw_ + 0x01)
#define SWI_ZapRedraw_RedrawRaster (SWI_ZapRedraw_ + 0x02)
#define SWI_ZapRedraw_ConvertBitmap (SWI_ZapRedraw_ + 0x03)
#define SWI_ZapRedraw_PrepareDataLine (SWI_ZapRedraw_ + 0x04)
#define SWI_ZapRedraw_AddCursor (SWI_ZapRedraw_ + 0x05)
#define SWI_ZapRedraw_FindCharacter (SWI_ZapRedraw_ + 0x06)
#define SWI_ZapRedraw_MoveBytes (SWI_ZapRedraw_ + 0x07)
#define SWI_ZapRedraw_CachedCharSize (SWI_ZapRedraw_ + 0x08)
#define SWI_ZapRedraw_ConvBitmapChar (SWI_ZapRedraw_ + 0x09)
#define SWI_ZapRedraw_CreatePalette (SWI_ZapRedraw_ + 0x0a)
#define SWI_ZapRedraw_InsertChar (SWI_ZapRedraw_ + 0x0b)
#define SWI_ZapRedraw_ReadSystemChars (SWI_ZapRedraw_ + 0x0c)
#define SWI_ZapRedraw_ReverseBitmaps (SWI_ZapRedraw_ + 0x0d)
#define SWI_ZapRedraw_ReadVduVars (SWI_ZapRedraw_ + 0x0e)
#define SWI_ZapRedraw_GetRectangle (SWI_ZapRedraw_ + 0x0f)
#define SWI_ZapRedraw_AddVduBitmaps (SWI_ZapRedraw_ + 0x10)
#define SWI_ZapRedraw_CacheFontChars (SWI_ZapRedraw_ + 0x11)
#define SWI_ZapRedraw_SpriteSize (SWI_ZapRedraw_ + 0x12)
#define SWI_ZapRedraw_RedrawWindow (SWI_ZapRedraw_ + 0x13)


/*
 | Other SWI numbers that aren't defined in DeskLib's SWI.h:
 */
#define SWI_OS_ScreenMode 0x65
#define SWI_OS_DynamicArea 0x66
#define SWI_ColourTrans_ReturnColourNumber 0x40744
#define SWI_Wimp_ReportError 0x400df
#define SWI_PlayIt_Volume 0x4d146



/*--------------------------------------------------------------------------*
 | File scope variables													 |
 *--------------------------------------------------------------------------*/
static int ftype = 0xffd;	/* hack so saved games get the right type */
static int filehandle[16];	/* we keep track of open files with this */
static int openfiles = 0;	/* how many files are currently open */

/*
 | Paths we use...
 */
static char resource_path[260] = "";	/* Path pointng to "!Angband.Lib." */
static char scrap_path[260] = "";	/* Path to create scrap files on */
static char choices_file[3][260] =
{ "", "", "" };	/* Choices paths (read/write, mirror, read) */
static char alarm_file[2][260] =
{ "", "" };	/* Alarm choices paths (read/write, mirror, read) */
/*
 | So we can use something more meaningful later...
 | NB: Mirror is only meaningful for Choices and we don't
 | even reserve space for alarm_file[CHFILE_MIRROR].
 */
#define CHFILE_WRITE 0
#define CHFILE_READ 1
#define CHFILE_MIRROR 2

/*
 | Other 'globals':
 */
static int initialised = 0;	/* Used to determine whether to try to save */
static int game_in_progress = 0;	/* if Quit (or core() is called),  etc. */

static byte a_palette[256][4];	/* a copy of the raw Angband palette */
static unsigned int palette[256];	/* palette as gamma'd bbggrrxx words */
static unsigned int zpalette[256];	/* And our version for ZapRedraw */
static double gamma = 1.0;	/* assume gamma of 1.0 if unspecified */

static int enable_sound = 0;	/* enable sound FX */
static int sound_volume = 127;	/* Full volume */
static int force_mono = 0;	/* force monochrome */
static int start_fullscreen = 0;	/* start up full screen (added in 1.18) */
static int hack_flush = 0;	/* Should TERM_XTRA_FLUSH wait for all keys to be released? */
static int flush_scrap = 1;	/* Should any scrapfiles (incl. filecache) be deleted at exit? */
static int max_file_cache_size = 64 << 10;
static unsigned int vfiletype;
static int allow_iclear_hack = 0;	/* Allow the hideously evil Iclear workaround thing */
static int alarm_type = 0;	/* is there an alarm set? */
static int alarm_h = 0, alarm_m = 0;	/* alarm time (midnight) */
static char alarm_message[80] = "Time for bed!";	/* the message to give */
static int alarm_disp = 0;	/* is the alarm being displayed? */
static int alarm_beep = 0;	/* should be beep? */
static const char *alarm_types[] =
{ "Off", "On (one-shot)", "On (repeating)", "On (one-shot)" };
static unsigned int alarm_lastcheck = 0;


/* A little macro to save some typing later: */
#define COLOUR_CHANGED(x) \
	( (angband_color_table[x][1]!=a_palette[x][1]) || \
	  (angband_color_table[x][2]!=a_palette[x][2]) || \
	  (angband_color_table[x][3]!=a_palette[x][3]) )

static int got_caret = 0;	/* Do we own the caret? */
static int key_pressed = 0;	/* 'Key has been pressed' Flag */
static int use_glass = 1;	/* use the hourglass between WimpPolls? */
static int glass_on = 1;	/* is the hourglass on? */
static int user_menu_active = FALSE;	/* set to TRUE when the user menu is active */

/* Font system variables */
static ZapFont fonts[MAX_TERM_DATA + 1];	/* The +1 is for the system font */

/* The system font is always font 0 */
#define SYSTEM_FONT (&(fonts[0]))

/* Term system variables */
static term_data data[MAX_TERM_DATA];	/* One per term */

#ifndef FULLSCREEN_ONLY
static char r_data[24 * (80 * 5 + 4) + 25 * 4];	/* buffer for ZapRedraw data */

/* Wimp variables */
static icon_handle ibar_icon;	/* Iconbar icon handle */
static window_handle info_box;	/* handle of the info window */
static window_handle gamma_win;	/* gamma correction window */
static window_handle sound_win;	/* sound options window */
static window_handle save_box;	/* The savebox */
static menu_ptr ibar_menu;	/* Iconbar menu */
static menu_ptr term_menu;	/* Term window menu */
static menu_ptr wind_menu;	/* windows (sub) menu */
static menu_ptr font_menu;	/* Font (sub)menu */

static save_saveblock *saveblk = NULL;	/* For the save box */

/* List of Wimp messages we want to be given */
static int message_list[] =
{
	message_MODECHANGE,
	message_PALETTECHANGE,

	/* For the savebox */
	message_MENUWARN,
	message_DATASAVEACK,

	message_PREQUIT,
	0
};


static term_data *menu_term;	/* term the last menu was opened for */

#endif /* FULLSCREEN_ONLY */

static ZapRedrawBlock zrb;	/* a redraw block */

/* Cursor colour */
#define CURSOR_COLOUR	255		/* Cursor's Angband colour */
#define CURSOR_RGB		0x00ffff00	/* if undefined, use bbggrrxx */

static int cursor_rgb = -1;	/* colour to use for cursor */

static int fullscreen_mode = 0;	/* screen mode in use */
static int old_screenmode = 0;	/* Mode we started out in */
static int *fullscreen_font = 0;	/* font data for fullscreen use */
static int *fullscreen_base = 0;	/* base address of screen */
static int fullscreen_height;	/* height of the fullscreen font */
static int fullscreen_topline;	/* raster offset of fullscreen */

#define KEYPRESS_QUIT    0x1cc	/* F12 gets back to the desktop */
#define TERM_TOPLINE_HR  32		/* vertical pixel offset in mode 27 */
#define TERM_TOPLINE_LR  16		/* vertical pixel offset in mode 12 */
#define TIME_LINE        26		/* Line to display the clock on */

/* text to display at the bottom left of the fullscreen display */
static const char *fs_quit_key_text = "Press f12 to return to the desktop";
static const char *alarm_cancel_text = "(Press ^Escape to cancel the alarm)";

/* Debugging flags, etc. */
static int log_g_malloc = 0;	/* Log calls to ralloc, etc */
static int show_sound_alloc = 0;	/* Log sound mappings, etc */

/* Activate file caching? */
#ifdef USE_FILECACHE
static int use_filecache = TRUE;
#else
static int use_filecache = FALSE;
#endif

/* Cripple some things to save memory */
static int minimise_memory = 0;

/* Forward declarations of some of the Full Screen Mode stuff */
static void enter_fullscreen_mode(void);
static void leave_fullscreen_mode(void);
static void set_keys(int claim);

/* Forwards declarations of the sound stuff */
static void initialise_sound(void);
static void play_sound(int event);

/* Forward declarations of Term hooks, etc. */ 
static void Term_init_acn(term *t);   
static errr Term_user_acn(int n);
           
#ifndef FULLSCREEN_ONLY
static errr Term_curs_acn(int x, int y);
static errr Term_text_acn(int x, int y, int n, byte a, cptr s);
static errr Term_xtra_acn(int n, int v);  
static errr Term_wipe_acn(int x, int y, int n);
static errr Term_xtra_acn_check(void);
static errr Term_xtra_acn_event(void);
static errr Term_xtra_acn_react(void);  
#endif /* FULLSCREEN_ONLY */

static errr Term_curs_acnFS(int x, int y);  
static errr Term_text_acnFS(int x, int y, int n, byte a, cptr s); 
static errr Term_xtra_acnFS(int n, int v);  
static errr Term_wipe_acnFS(int x, int y, int n);  
static errr Term_xtra_acn_checkFS(void);
static errr Term_xtra_acn_clearFS(void);  
static errr Term_xtra_acn_eventFS(void);    
static errr Term_xtra_acn_reactFS(int force);

#ifdef USE_DA
/* Forward declarations of the memory stuff */
static void init_memory(int, int);
#endif

/* Forward declarations of the alarm stuff */
static void check_alarm(void);
#ifndef FULLSCREEN_ONLY
static void trigger_alarm_desktop(void);
#endif /* FULLSCREEN_ONLY */
static void ack_alarm(void);
static void write_alarm_choices(void);
static void read_alarm_choices(void);


/* This just shows some debugging info (if enabled with FE_DEBUG_INFO) */
static void show_debug_info(void);



/* File-caching functions (if enabled at compile time) */
#ifdef USE_FILECACHE
FILE *cached_fopen(char *name, char *mode);
errr cached_fclose(FILE *fch);
errr cached_fgets(FILE *fch, char *buffer, int max_len);
#endif

/*
 | These functions act as malloc/free, but (if possible) using memory
 | in the 'Game' Dynamic Area created by init_memory()
 | We attach these functions to the ralloc_aux and rnfree_aux hooks
 | that z-virt.c provides.
 */     
#ifdef USE_DA
static vptr g_malloc(huge size);
static vptr g_free(vptr blk, huge);
#else  
  #define g_malloc(size) malloc(size);
  #define g_free(block, size) free(block);
#endif

/*
 | These functions act as malloc/free, but (if possible) using memory
 | in the 'Fonts' Dynamic Area created by init_memory()
 */
#ifdef USE_DA
static void* f_malloc(size_t size);
static void f_free(void *blk);
#else
  #define f_malloc(size) malloc(size);
  #define f_free(block) free(block);
#endif

/*
 | These two functions perpetrate great evil to stop IClear from mucking
 | with the cursor keys in fullscreen mode.
 */
static void iclear_hack(void);
static void remove_iclear_hack(void);

/*
 | We use this to locate the choices file(s)...
 */
static char *find_choices(int write);
static char *find_choices_mirror(void);
static char *find_alarmfile(int write);



/*
 | This function is supplied as a wrapper to the save_player function.
 |
 | Its purpose is to change the filename that the game will be saved with
 | the leafname "!!PANIC!!" so that panic saves that break the savefile
 | won't overwrite the original savefile.
 |
 | To get this to work, you'll need to ammend files.c and change the call
 | to save_player in the panic save function(s) (search for "panic save")
 | to a call to save_player_panic_acn.  You can declare a prototype for
 | the function if you like.
 */

extern int save_player_panic_acn(void)
{
	char *e, *l;

	/* Find the final / in the savefile name */
	for (l = e = savefile; *e; e++)
		if (*e == '/')
		{
			l = e + 1;
		}

	/* Write over the current leaf with the special panic one */
	strcpy(l, "!!PANIC!!");

	/* save the game */
	return save_player();
}


/*--------------------------------------------------------------------------*/
/* Error reporting, etc.													*/
/*--------------------------------------------------------------------------*/


/* Tell the user something important */
static void plog_hook(cptr str)
{
	Msgs_Report(1, "err.plog", str);
}

/* Tell the user something, then quit */
static void quit_hook(cptr str)
{
	/* str may be null */
	if (str) Msgs_Report(1, "err.quit", str);
	exit(0);
}

/* Tell the user something then crash ;) */
static void core_hook(cptr str)
{
	Msgs_Report(1, "err.core", str);

	if (game_in_progress && character_generated)
		save_player_panic_acn();

	quit(NULL);
}

static void debug(const char *fmt, ...)
{
	va_list ap;
	char buffer[260];
	va_start(ap, fmt);
	vsprintf(buffer, fmt, ap);
	va_end(ap);
	plog(buffer);
}



/*
static void oserror_handler( int sig )
{
	core(_kernel_last_oserror()->errmess);
}
*/


/*--------------------------------------------------------------------------*/
/* File handling															*/
/*--------------------------------------------------------------------------*/

static int myFile_Open(const char *name, int mode)
{
	int handle;
	if (SWI(2, 1, SWI_OS_Find, mode, name, /**/ &handle))
		return 0;
	return handle;
}

static int myFile_Size(const char *name)
{
	int size, type;
	if (SWI(2, 5, SWI_OS_File, 17, name, /**/ &type, 0, 0, 0, &size))
		return -2;
	return type ? size : -1;
}

static os_error *myFile_Close(const int handle)
{
	return SWI(2, 0, SWI_OS_Find, 0, handle);
}

static os_error *myFile_Seek(const int handle, const int offset)
{
	return SWI(3, 0, SWI_OS_Args, 1, handle, offset);
}

static int myFile_WriteBytes(const int handle, const void *buf, const int n)
{
	int ntf;
	if (SWI(4, 4, SWI_OS_GBPB, 2, handle, buf, n, /**/ NULL, NULL, NULL, &ntf))
		return n;
	return ntf;
}

static int myFile_ReadBytes(const int handle, void *buf, const int n)
{
	int ntf;
	if (SWI(4, 4, SWI_OS_GBPB, 4, handle, buf, n, /**/ NULL, NULL, NULL, &ntf))
		return n;
	return ntf;
}

static os_error *myFile_SetType(const char *n, const int type)
{
	return SWI(3, 0, SWI_OS_File, 18, n, type);
}


static int myFile_Extent(const int handle)
{
	int ext;
	if (SWI(2, 3, SWI_OS_Args, 2, handle, /**/ NULL, NULL, &ext))
		return -1;
	return ext;
}


/*
 | Determine if one file is newer than another.
 |
 | The filenames should be specified in RISC OS style.
 |
 | Returns -1 if 'a' is newer than 'b'.
 */
static int file_is_newer(const char *a, const char *b)
{
	os_error *e;
	struct
	{
	unsigned int msw;
	unsigned int lsw;
	}
	a_time;
	struct
	{
	unsigned int msw;
	unsigned int lsw;
	}
	b_time;
	int a_type, b_type;

	/* Get the datestamp of the 'a' file */
	e = SWI(2, 4, SWI_OS_File,
			/* In */
			17, (int)a,
			/* Out */
			&a_type,			/* object type */
			NULL, &(a_time.msw),	/* Load Addr */
			&(a_time.lsw)		/* Exec Addr */
		);
	if (e)
	{
		core(e->errmess);
	}


	/* Get the datestamp of the 'b' file */
	e = SWI(2, 4, SWI_OS_File,
			/* In */
			17, (int)b,
			/* Out */
			&b_type,			/* object type */
			NULL, &(b_time.msw),	/* Load Addr */
			&(b_time.lsw)		/* Exec Addr */
		);
	if (e)
	{
		core(e->errmess);
	}

	/* If 'b' doesn't exist then 'b' is OOD. */
	if (!b_type)
	{
		return -1;
	}
	/* If 'a' doesn't exist then 'b' isn't OOD. (?) */
	if (!a_type)
	{
		return 0;
	}

	/* Compare the timestamps (assume that the files are typed) */
	if ((a_time.msw & 0xff) >= (b_time.msw & 0xff))
		if ((a_time.lsw) > (b_time.lsw))
			return -1;			/* OOD */
	return 0;					/* Not OOD */
}


/*
 | As fprintf, but outout to all files (if their handles are non zero).
 | NB: void type.
 */
static void f2printf(FILE *a, FILE *b, const char *fmt, ...)
{
	va_list ap;
	char buffer[2048];
	va_start(ap, fmt);
	vsprintf(buffer, fmt, ap);
	va_end(ap);
	if (a)
	{
		fprintf(a, buffer);
	}
	if (b)
	{
		fprintf(b, buffer);
	}
	va_end(ap);
}




/*--------------------------------------------------------------------------*/
/* Clean up (ie. close files, etc). 										*/
/*--------------------------------------------------------------------------*/

static void final_acn(void)
{
	int i;

	for (i = 0; i < openfiles; i++)
		myFile_Close(filehandle[i]);

	if (fullscreen_mode)
	{
		/* Restore the screen mode */
		Wimp_SetMode(old_screenmode);

		/* Restore the various soft keys */
		set_keys(FALSE);

		/*
		   | Hack: Early WIMP versions do the "Press SPACE" thing, or something
		   | odd.  It's bloody annoying, whatever it is...
		 */
		if (event_wimpversion < 300)
			Wimp_CommandWindow(-1);
	}

	if (flush_scrap && *scrap_path)
	{
		char tmp[512];
		strcpy(tmp, scrap_path);
		tmp[strlen(tmp) - 1] = 0;	/* Remove trailing dot */
		SWI(4, 0, SWI_OS_FSControl, 27, tmp, 0, 1);	/* ie. "*Wipe <scrapdir> r~c~v~f" */
	}

#ifdef FULLSCREEN_ONLY
	Wimp_CommandWindow(-1);
#endif /* FULLSCREEN_ONLY */

	Stop_Hourglass;
}


/*--------------------------------------------------------------------------*
 | Various UNIX-like support funtions									   |
 *--------------------------------------------------------------------------*/

/*
 | Hack: determine whether filenames should be truncated to 10 chars or not.
 |
 | Needed since RO2 (and RO3 with Truncate configured off) will return
 | errors instead of automatically truncating long filenames.
 */
static int truncate_names(void)
{
	int r1, r2;

	/* First, check the OS version */
	OS_Byte(osbyte_READOSIDENTIFIER, 0x00, 0xff, &r1, &r2);

	/* Assume that we need to truncate if running under RO2 */
	if (r1 == 0xa1 || r1 == 0xa2)
		return TRUE;

	/* Okay, so we've got RO3 (or later), so check the CMOS RAM */
	OS_Byte(osbyte_READCMOSRAM, 28, 0, &r1, &r2);

	/* Bit 0 of byte 28 is the Truncate flag */
	return !(r2 & 1);
}


/*
 | The PathName translation is now done by two separate functions:
 | unixify_name() and riscosify_name().
 |
 | This is done because only the UNIX=>RISCOS translation should
 | ever affect the length of the leafname (ie. by truncating it to
 | 10 chars if necessary).
 |
 | Note that the two functions are identical but for the truncation
 | check so all that's really been done is that translate_name() now
 | takes an extra argument: 'trunc' that controls whether truncation
 | is applied, and riscosify and unixify just call translate_name().
 */
static char *translate_name(const char *path, int trunc)
{
	static char buf[260];
	char c, *p;

	/* Copy 'path' into 'buf', swapping dots and slashes */
	p = buf;					/* Output position */
	do
	{
		c = *path++;
		if (c == '/')
			c = '.';
		else if (c == '.')
			c = '/';
		*p++ = c;
	}
	while (c);					/* Terminator /is/ copied */

	/*
	   | When saving a game, the old game is renamed as
	   | "SavedGame.old", the new one is saved as "SavedGame.new",
	   | "SavedGame.old" is deleted, "SavedGame.new" is renamed
	   | as "SavedGame". This will go wrong on a Filecore based filing
	   | system if the saved game has a leafname > 8 chars.
	 */

	if ((p = strstr(buf, "/old")) == NULL)
	{
		p = strstr(buf, "/new");
	}
	if (!p)
	{
		ftype = 0xffd;
	}
	else
	{
		char *q = strrchr(buf, '.');
		if (q)
			if (p - q > 6)
			{
				memmove(q + 6, p, 5);
			}
		ftype = vfiletype;
	}

	/*
	   | Hack: Do we need to truncate the leafname?
	 */
	if (trunc)
	{
		if (truncate_names())
		{
			char *a, *b;
			/*
			   | Assume that only the leafname needs attention
			   | (this should be true for any variant)
			 */
			for (a = b = buf; *a; a++)
				if (*a == '.')
					b = a + 1;
			/*
			   | Now b points to the start of the leafname.
			   | If the leafname is >10 chars, write over the 10th with a
			   | terminator.
			 */
			if (strlen(b) > 10)
			{
				b[10] = 0;
			};
		}
	}

	return buf;
}


extern char *riscosify_name(const char *path)
{
	return translate_name(path, TRUE);
}

static char *unixify_name(const char *path)
{
	return translate_name(path, FALSE);
}


/*--------------------------------------------------------------------------*/


/* Open a file [as fopen()] but translate the requested filename first */

FILE *my_fopen(const char *f, const char *m)
{
	FILE *fp;
	char *n = riscosify_name(f);	/* translate for RO */

	/* Try to open the file */
	fp = fopen(n, m);

	/* If it succeded and the file was opened for binary output
	   | then set the type according to the 'ftype' hack.
	   | NB: This will fail on some filing systems.
	 */

	if (fp && strstr(m, "wb"))
	{
		myFile_SetType(n, ftype);
	}

	return fp;
}




/* Close a file, a la fclose() */

errr my_fclose(FILE *fp)
{
	/* Close the file, return 1 for an error, 0 otherwise */
	return fclose(fp) ? 1 : 0;
}


/* Open/Create a file */

int fd_make(cptr file, int mode)
{
	char *real_path;
	int handle;

	/* Translate the filename into a RISCOS one */
	real_path = riscosify_name(file);

	/* Try to OPENOUT the file (no path, error if dir or not found) */
	handle = myFile_Open(real_path, 0x8f);

	/* Check for failure */
	if (!handle)
	{
		return -1;
	}

	/* Try to set the filetype according to the ftype hack */
	myFile_SetType(real_path, ftype);

	/* We keep track of up to 16 open files at any given time */
	if (openfiles < 16)
		filehandle[openfiles++] = handle;
                                                                                
	return (handle);
}


/* Delete a file [as remove()] */
errr fd_kill(cptr file)
{
	return remove(riscosify_name(file)) ? 1 : 0;
}


/* Rename a file [as rename()] */
errr fd_move(cptr old, cptr new)
{
	char new_[260];
	strcpy(new_, riscosify_name(new));
	return rename(riscosify_name(old), new_) ? 1 : 0;
}

/* Open a file */
int fd_open(cptr path, int flags)
{
	int handle = 0;
	char *real_path = riscosify_name(path);

	switch (flags & 0x0f)
	{
		case O_RDONLY:			/* Read only */
			handle = myFile_Open(real_path, 0x4f);
			break;
		case O_WRONLY:			/* Write only */
		case O_RDWR:			/* Read/Write */
			handle = myFile_Open(real_path, 0xcf);
	}

	/* Check for failure */
	if (!handle)
	{
		return (-1);
	}

	/* Keep track of upto 16 open files... */
	if (openfiles < 16)
		filehandle[openfiles++] = handle;

	return (handle);
}


/* Close a file opened with fd_make or fd_open */
errr fd_close(int handle)
{
	int i;

	if (handle <= 0)
	{
		return -1;
	}							/* Illegal handle */

	/* Try to close the file */
	if (myFile_Close(handle))
	{
		return 1;
	}

	/* Mark the file as closed in our array of file handles */
	openfiles--;
	/* Find the entry in the array (if it exists) */
	for (i = 0; i < 16; i++)
		if (filehandle[i] == handle)
		{
			break;
		}
	/* Shuffle the remaining entries down */
	for (; i < openfiles; i++)
		filehandle[i] = filehandle[i + 1];

	return 0;					/* Sucess */
}



/* Read some bytes from a file */
errr fd_read(int handle, char *buf, huge nbytes)
{
	int unread;

	if (handle <= 0)
	{
		return -1;
	}							/* Illegal handle */
	unread = myFile_ReadBytes(handle, buf, (int)nbytes);

	return unread ? 1 : 0;
}


/* Write some bytes to a file */
errr fd_write(int handle, const char *buf, huge nbytes)
{
	int unwritten;

	if (handle <= 0)
	{
		return -1;
	}							/* Illegal handle */
	unwritten = myFile_WriteBytes(handle, (const void *)buf, (int)nbytes);

	return unwritten ? 1 : 0;
}


/* Seek in a file */
errr fd_seek(int handle, huge offset)
{
	os_error *e;

	if (handle <= 0)
	{
		return -1;
	}							/* Illegal handle */
	e = myFile_Seek(handle, (int)offset);

	return e ? 1 : 0;
}


/* RISC OS provides no file locking facilities, so: */
errr fd_lock(int handle, int what)
{
	return 0;
}


/* Get a temporary filename */
errr path_temp(char *buf, int max)
{

	/*
	   | New in 1.25 - use the scrap path we decided on earlier, or
	   | fall back on tmpnam() if that fails for some reason.
	 */
	if (*scrap_path)
	{
		time_t t;
		int m;
		char tmp[512];

		time(&t);
		for (m = 0; m < 80; m++)
		{
			sprintf(tmp, "%s0x%08x", scrap_path, (int)t + m);
			if (myFile_Size(tmp) == -1)
			{
				break;
			}
		}

		if (m < 80)
		{
			strncpy(buf, unixify_name(tmp), max);
			return 0;
		}
	}

	strncpy(buf, unixify_name(tmpnam(NULL)), max);
	return 0;
}



/*
 * Create a new path by appending a file (or directory) to a path
 *
 * This requires no special processing on simple machines, except
 * for verifying the size of the filename, but note the ability to
 * bypass the given "path" with certain special file-names.
 *
 * Note that the "file" may actually be a "sub-path", including
 * a path and a file.
 *
 * Note that this function yields a path which must be "parsed"
 * using the "parse" function above.
 */
errr path_build(char *buf, int max, cptr path, cptr file)
{
	/* Special file */
	if (file[0] == '~')
	{
		/* Use the file itself */
		strnfmt(buf, max, "%s", file);
	}

	/* Absolute file, on "normal" systems */
	else if (prefix(file, PATH_SEP) && !streq(PATH_SEP, ""))
	{
		/* Use the file itself */
		strnfmt(buf, max, "%s", file);
	}

	/* No path given */
	else if (!path[0])
	{
		/* Use the file itself */
		strnfmt(buf, max, "%s", file);
	}

	/* Path and File */
	else
	{
		/* Build the new path */
		strnfmt(buf, max, "%s%s%s", path, PATH_SEP, file);
	}

	/* Success */
	return (0);
}



/*--------------------------------------------------------------------------*/





/*--------------------------------------------------------------------------*/
/* Font Functions															*/
/*--------------------------------------------------------------------------*/

/*
 | Cache the system font as fonts[0]
 | Returns 1 for sucess or 0 for failure.
 | NB: The n_bpp data is *not* cached, just the 1bpp data and font info.
 | Also, the usage is never affected.
 */
static int cache_system_font(void)
{
	ZapFont *sys = SYSTEM_FONT;
	ZapRedrawBlock zrb;
	char work_area[16];
	int i;

	/* Cache the system font (as fonts[0]) */
	if (sys->bpp_1)
	{
		f_free(sys->bpp_1);
		sys->bpp_1 = 0;
	}
	sys->bpp_1 = f_malloc(8 * 256);	/* 2K */
	if (!sys->bpp_1)
	{
		return 0;
	}

	/* Mung so that undefined characters show up as inverted ?s */
	work_area[3] = '?';
	SWI(2, 0, SWI_OS_Word, 10, work_area + 3);
	for (i = 4; i < 12; i++)
		work_area[i] ^= 255;	/* invert colours */
	SWI(4, 0, SWI_ZapRedraw_ReverseBitmaps, 0, work_area + 4, work_area + 4, 8);
	for (i = 0; i < 0x20; i++)
		memcpy(((char *)sys->bpp_1) + i * 8, work_area + 4, 8);

	/* Read the system font */
	zrb.r_workarea = work_area;
	SWI(2, 0, SWI_ZapRedraw_ReadSystemChars, sys->bpp_1, &zrb);

	/* Set up some little bits of info */
	sys->name = (char *) "<System>";
	sys->w = sys->h = 8;
	sys->f = 0;
	sys->l = 255;

	return 1;
}



/*
 | Prepare the font system
 */
static void initialise_fonts(void)
{
	/* Initialise the array */
	memset(fonts, 0, sizeof(fonts));	/* Clear to zeroes */

	/* Cache the system font */
	cache_system_font();
	fonts[0].usage = 0;			/* No users */
}


#ifndef FULLSCREEN_ONLY
/*
 | Find a font (by name) in the array.
 | Returns 0 if the font isn't loaded, or a ZapFont* for it if it is.
 */
static ZapFont *find_font_by_name(char *name)
{
	int i;
	for (i = 0; i <= MAX_TERM_DATA; i++)
		if (fonts[i].name)
			if (!strcmp(fonts[i].name, name))
				return &(fonts[i]);
	return NULL;
}

/*
 | Find a free slot in the fonts array
 */
static ZapFont *find_free_font(void)
{
	int i;
	for (i = 1; i <= MAX_TERM_DATA; i++)
		if (!fonts[i].name)
		{
			return &(fonts[i]);
		}
	return NULL;
}



/*
 | Load a font from disc and set up the header info, etc.
 | NB: doesn't cache the nbpp data, just the 1bpp data.
 | (Sets usage to 1)
 | Returns NULL if failed.
 */
static ZapFont *load_font(char *name, ZapFont *f)
{
	int handle, extent;
	char path[260];
	struct
	{
		char id[8];
		int w, h, f, l, r1, r2;
	}
	header;
	char *font_path;
	char *t;
	char *real_name = name;	/* need to preserve this */

	/*
	   | 1.10 - the first element of the name determines the path to load
	   | the font from.
	 */

	/* The font paths start <RISCOS_VARIANT>$ */
	t = path + sprintf(path, "%s$", RISCOS_VARIANT);

	/* Copy the path specifier and move 'name' past it */
	for (; *name != '.'; *t++ = *name++) ;

	/* After this, the name now points to the font name proper */
	name++;

	/* Append the end of the path name */
	strcpy(t, "$FontPath");

	/* Get the path setting */
	font_path = getenv(path);
	if (!font_path || !*font_path)
		strcpy(path, "null:$.");
	else
	{
		strcpy(path, font_path);
		for (t = path; *t > ' '; t++)
			;
		if (t[-1] != '.' && t[-1] != ':')
		{
			*t++ = '.';
		}
		*t = 0;
	}
	strcat(path, name);


	/* Open the file */
	handle = myFile_Open(path, 0x4f);
	if (!handle)
	{
		return NULL;
	}

	/* Read the header */
	if (myFile_ReadBytes(handle, &header, sizeof(header)))
	{
		myFile_Close(handle);
		return NULL;
	}

	/* Check that it's a zapfont */
	if (strncmp(header.id, "ZapFont\r", 8))
	{
		myFile_Close(handle);
		return NULL;
	}

	/* Calculate the size of the 1bpp data */
	extent = myFile_Extent(handle) - sizeof(header);

	/* Allocate the storage for the 1bpp data */
	f->bpp_1 = f_malloc(extent);
	if (!f->bpp_1)
	{
		myFile_Close(handle);
		return NULL;
	}

	/* Load the 1bpp data */
	if (myFile_ReadBytes(handle, f->bpp_1, extent))
	{
		f_free(f->bpp_1);
		f->bpp_1 = 0;
		myFile_Close(handle);
		return NULL;
	}

	/* Close the file and set the header, etc. */
	myFile_Close(handle);
	f->name = f_malloc(strlen(real_name) + 1);
	if (!f->name)
	{
		f_free(f->bpp_1);
		f->bpp_1 = 0;
		return NULL;
	}

	strcpy(f->name, real_name);
	f->w = header.w;
	f->h = header.h;
	f->f = header.f;
	f->l = header.l;
	f->usage = 1;

	return f;
}




/*
 | Cache a font at a suitable number of bpp for the current mode
 | Returns 0 for failure, 1 for sucess.
 | If the call fails then the font's bpp_n entry will be NULL.
 */
static int cache_font_for_mode(ZapFont *f)
{
	ZapRedrawBlock b;
	char work_area[128];
	int size;

	if (!f)
	{
		return 0;
	}
	if (!f->bpp_1)
	{
		return 0;
	}

	b.r_workarea = work_area;
	SWI(2, 0, SWI_ZapRedraw_ReadVduVars, 0, &b);

	b.r_workarea = work_area;	/* Paranoia */
	b.r_charh = f->h;
	b.r_charw = f->w;
	SWI(4, 4, SWI_ZapRedraw_CachedCharSize, b.r_bpp, 0, f->w, f->h,
		NULL, NULL, &(b.r_cbpl), &(b.r_cbpc));

	size = 256 * b.r_cbpc;
	if (f->bpp_n)
	{
		f_free(f->bpp_n);
		f->bpp_n = NULL;
	}
	f->bpp_n = f_malloc(size);
	if (!f->bpp_n)
	{
		return 0;
	}

	b.r_workarea = work_area;	/* Paranoia */
	b.r_caddr = f->bpp_n;
	SWI(5, 0, SWI_ZapRedraw_ConvertBitmap, 0, &b, 0, 255, f->bpp_1);

	return 1;
}



/*
 | Stop using a font.
 | If the font's usage drops to zero then the font data is purged.
 */
static void lose_font(ZapFont *f)
{
	if (--f->usage)
	{
		/*debug("Losing font %s (still cached)",f->name); */
		return;
	}
	/*debug("Losing font %s (no longer in use)",f->name); */
	f_free(f->name);
	f_free(f->bpp_1);
	if (f->bpp_n)
	{
		f_free(f->bpp_n);
	}
	memset(f, 0, sizeof(ZapFont));
}


/*
 | Get a font.
 */
static ZapFont *find_font(char *name)
{
	ZapFont *f;

	/* Check to see if it's already loaded */
	f = find_font_by_name(name);
	if (f)
	{
		/*debug("Find font %s (already cached)",name); */
		f->usage++;
		if (f == SYSTEM_FONT)
		{
			if (!cache_system_font())
				core("Failed to cache system font!");
			if (!cache_font_for_mode(SYSTEM_FONT))
				core("Failed to cache system font!");
		}
		return f;
	}

	/* Ok, now check to see if there's a free slot for it */
	f = find_free_font();
	if (!f)
	{
		return NULL;
	}							/* Oh dear :( */

	/* Load the font */
	/*debug("Find font %s (loading)",name); */
	f = load_font(name, f);
	if (f)
	{
		if (!cache_font_for_mode(f))
			return NULL;
		return f;
	}
	return NULL;
}




/*
 | Cache the n_bpp data for all the active fonts (including system)
 */
static void cache_fonts(void)
{
	int i;
	for (i = 0; i <= MAX_TERM_DATA; i++)
		if (fonts[i].name)
			if (!cache_font_for_mode(&(fonts[i])))
				core("Failed to (re)cache font tables");
}






typedef struct
{
	int load, exec, size, attr, type;
	char name[4];	/* Actual size is unknown */
}
osgbpb10_block;


static char *leafname(char *path)
{
	char *s = path + strlen(path);
	while (--s > path)
		if (*s == '.' || *s == ':')
		{
			return s + 1;
		}
	return path;
}

/*
 | NB: This function is recursive.
 */
static menu_ptr make_zfont_menu(char *dir)
{
	int entries, entry;
	int read, offset;
	unsigned int max_width;
	menu_ptr m;
	menu_item *mi;
	char *temp;
	osgbpb10_block *item_info;
	char buffer[1024];	/* 1Kb buffer */

	/* Count the entries in the directory */
	entries = read = offset = 0;
	while (offset != -1)
	{
		if (SWI(7, 5, SWI_OS_GBPB, 10, dir, buffer, 77, offset, 1024, 0,
				NULL, NULL, NULL, &read, &offset))
		{
			offset = -1;
			read = 0;
		}
		entries += read;
	}

	if (!entries)
	{
		return NULL;
	}

	/* Allocate a big enough area of storage for the number of entries */
	m = f_malloc(sizeof(menu_block) + entries * sizeof(menu_item));
	if (!m)
	{
		return NULL;
	}
	memset(m, 0, sizeof(menu_block) + entries * sizeof(menu_item));

	/* Set up the menu header */
	strncpy(m->title, leafname(dir), 12);
	m->titlefore = 7;
	m->titleback = 2;
	m->workfore = 7;
	m->workback = 0;
	m->height = 44;
	m->gap = 0;
	mi = (menu_item *) (((int)m) + sizeof(menu_block));
	max_width = strlen(m->title);

	entry = 0;

	/* Read the entries */
	read = offset = 0;
	while (offset != -1)
	{
		if (SWI(7, 5, SWI_OS_GBPB, 10, dir, buffer, 77, offset, 1024, 0,
				NULL, NULL, NULL, &read, &offset))
		{
			offset = -1;
			read = 0;
			/*free(m);return NULL; */
		}

		item_info = (osgbpb10_block *) buffer;

		/* Create a menu item for each entry read (if it fits) */
		while (read-- > 0)
		{
			switch (item_info->type)
			{
				case 1:		/* File */
					if ((item_info->load & 0xffffff00) == 0xfffffd00)
					{
						/* Data file */
						mi[entry].submenu.value = -1;
						mi[entry].iconflags.data.text = 1;
						mi[entry].iconflags.data.filled = 1;
						mi[entry].iconflags.data.foreground = 7;
						mi[entry].iconflags.data.background = 0;
						strncpy(mi[entry].icondata.text, item_info->name, 12);
						if (strlen(mi[entry].icondata.text) > max_width)
							max_width = strlen(mi[entry].icondata.text);
						entry++;
					}
					break;
				case 2:		/* Directory */
				case 3:		/* Image */
				{
					menu_ptr sub;
					char new_path[260];
					if (strchr(":.", dir[strlen(dir) - 1]))
						sprintf(new_path, "%s%s", dir, item_info->name);
					else
						sprintf(new_path, "%s.%s", dir, item_info->name);
					sub = make_zfont_menu(new_path);
					if (sub)
					{
						/* Add the submenu */
						mi[entry].submenu.menu = sub;
						mi[entry].iconflags.data.text = 1;
						mi[entry].iconflags.data.filled = 1;
						mi[entry].iconflags.data.foreground = 7;
						mi[entry].iconflags.data.background = 0;
						strncpy(mi[entry].icondata.text, item_info->name, 12);
						if (strlen(mi[entry].icondata.text) > max_width)
							max_width = strlen(mi[entry].icondata.text);
						entry++;
					}
				}
					break;
			}
			temp = ((char *)item_info) + 20;
			while (*temp++) ;
			item_info = (osgbpb10_block *) ((((int)temp) + 3) & ~3);
		}
	}

	if (entry)
	{
		m->width = (max_width + 2) * 16;
		mi[entry - 1].menuflags.data.last = 1;
		/*
		   | We could possibly realloc() the storage to fit the
		   | actual no. of entries read, but this is probably more
		   | trouble than it's worth.
		 */
	}
	else
	{
		/*
		   | No point in returning an empty menu.
		 */
		f_free(m);
		m = NULL;
	}

	return m;
}

#endif /* FULLSCREEN_ONLY */




/*--------------------------------------------------------------------------*/

/*
 | Initialise the palette stuff
 */
static void initialise_palette(void)
{
	memset(a_palette, 0, sizeof(a_palette));
	memset(palette, 0, sizeof(palette));
	memset(zpalette, 0, sizeof(zpalette));
}



#ifndef FULLSCREEN_ONLY

/*
 | Cache the ZapRedraw palette
 */
static void cache_palette(void)
{
	static ZapRedrawBlock b;
	char workspace[128];
	int i;

	static double old_gamma = -1.0;

	/* Idiocy check: */
	if (gamma < 0.01)
	{
		plog("Internal error: Attempt to apply zero gamma - recovering...");
		gamma = 1.00;
	}

	if (gamma != old_gamma)
	{
		memset(a_palette, 0, sizeof(a_palette));
		old_gamma = gamma;
	}

	/* Go through the palette updating any changed values */
	for (i = 0; i < 256; i++)
	{
		if (COLOUR_CHANGED(i))
		{
			int r, g, b;
			r = (int)(255.0 *
					  pow(angband_color_table[i][1] / 255.0, 1.0 / gamma));
			g = (int)(255.0 *
					  pow(angband_color_table[i][2] / 255.0, 1.0 / gamma));
			b = (int)(255.0 *
					  pow(angband_color_table[i][3] / 255.0, 1.0 / gamma));
			palette[i] = (b << 24) | (g << 16) | (r << 8);
			a_palette[i][1] = angband_color_table[i][1];
			a_palette[i][2] = angband_color_table[i][2];
			a_palette[i][3] = angband_color_table[i][3];
		}
	}

	cursor_rgb = palette[CURSOR_COLOUR];

	/* Cache the ZapRedraw palette for it */
	b.r_workarea = workspace;
	if (b.r_mode != screen_mode)
		SWI(2, 0, SWI_ZapRedraw_ReadVduVars, 0, &b);
	SWI(5, 0, SWI_ZapRedraw_CreatePalette, 2, &b, palette, zpalette, 256);
}




/*--------------------------------------------------------------------------*/

/*
 | Functions for dealing with the SaveBox
 */

/*
 | Create the window and claim various handlers for it
 */
static void init_save_window(void)
{
	/* Create the window */
	save_box = Window_Create("save", template_TITLEMIN);

	/* Set the file icon */
	Icon_printf(save_box, SAVE_ICON, "file_%03x", vfiletype);

}
#endif /* FULLSCREEN_ONLY */

/*
 | Hack: can't use Str.h without defining HAS_STRICMP.  Rather than
 | require that the header files are altered we simply provide our
 | own strnicmp() function.
 */
static int my_strnicmp(const char *a, const char *b, int n)
{
	int i;

	n--;

	for (i = 0; i <= n; i++)
	{
		if (tolower((unsigned char)a[i]) != tolower((unsigned char)b[i]))
			return tolower((unsigned char)a[i]) - tolower((unsigned char)b[i]);

		if (a[i] == '\0')
			break;
	}

	return 0;
}

#ifndef FULLSCREEN_ONLY
/*
 | This is the handler called when a 'save' occurrs.
 | All it does is to update the game's own savefile setting and
 | then (if possible) save the character.
 */
static BOOL SaveHnd_FileSave(char *filename, void *ref)
{
	char old_savefile[1024];

	/* Hack: refuse to save if the character is dead */
	if (PDEADCHK)
	{
		Msgs_Report(0, "err.cheat");
		return FALSE;
	}

	/* Hack: disallow saves to <Wimp$Scrap>* */
	if (!my_strnicmp("<wimp$scrap>", filename, 12))
	{
		Msgs_Report(0, "err.scrap");
		return FALSE;
	}

	/* Preserve the old path, in case something goes wrong... */
	strcpy(old_savefile, savefile);

	/* Set the new path */
	strcpy(savefile, unixify_name(filename));

	/* Try a save (if sensible) */
	if (game_in_progress && character_generated)
	{
		if (!save_player())
		{
			Msgs_Report(0, "err.save", filename);
			strcpy(savefile, old_savefile);
			return FALSE;		/* => failure */
		}
	}

	/* Set the pathname icon */
	Icon_printf(save_box, SAVE_PATH, "%s", riscosify_name(savefile));

	/* Kill the menu */
	Wimp_CreateMenu((menu_block *) - 1, -1, -1);

	return TRUE;				/* => Success */
}



/*
 | Init the handlers for the savebox (eg. as a result of a menuwarning
 | being received for the savebox)
 */

static void init_savehandlers(void)
{
	if (saveblk)
	{
		Save_ReleaseSaveHandlers(saveblk);
		saveblk = 0;
	}

	saveblk = Save_InitSaveWindowHandler(save_box,	/* Window handle */
										 TRUE,	/* it's part of a menu */
										 FALSE,	/* not a window */
										 FALSE,	/* Don't auto release the handlers */
										 SAVE_ICON,	/* The file icon */
										 SAVE_OK,	/* The OK icon */
										 SAVE_CANCEL,	/* The cancel icon */
										 SAVE_PATH,	/* The pathname icon */
										 SaveHnd_FileSave,	/* Handler to "save the file" */
										 NULL,	/* No RAM transfer support */
										 NULL,	/* No 'result handler' */
										 100 << 10,	/* Est. size (irelevant anyway) */
										 vfiletype,	/* filetype (irelevant) */
										 NULL	/* ref */
		);
}


/*
 | Handle a MenuWarning message for the savebox
 */
static BOOL Hnd_SaveWarning(event_pollblock * pb, void *ref)
{
	os_error *e;

	init_savehandlers();

	/* Set the pathname */
	Icon_printf(save_box, SAVE_PATH, "%s", riscosify_name(savefile));

	/* Open the submenu */
	e = Wimp_CreateSubMenu((menu_block *) save_box,
						   pb->data.message.data.menuwarn.openpos.x,
						   pb->data.message.data.menuwarn.openpos.y);

	if (e)
	{
		Msgs_ReportFatal(0, "err.swi", __LINE__, e->errmess);
	}

	return TRUE;
}


/*--------------------------------------------------------------------------*/


/*
 | Initialise the r_data array
 | Mainly we just set up the line offset pointers and make sure that the
 | lines themselves are 'safe' by writing end-of-line codes to them.
 */
static void initialise_r_data(void)
{
	int *lo = (int *)r_data;
	char *ld;
	int j;
	for (j = 0; j < 24; j++)
	{
		lo[j] = 25 * 4 + (80 * 5 + 4) * j;	/* Offset of line */
		ld = r_data + lo[j];
		*ld++ = 0;				/* 0,2 ==     */
		*ld = 2;				/* end of line */
	}
	lo[j] = 0;					/* Terminate line index */
}



/*
 | Create the r_data array for a term
 | This is typically quite fast (1ms or so on a RPC700)
 | so we don't bother caching r_data for each term or using the
 | 'frosh' concept.
 */
static void make_r_data(term_data *t)
{
	char **c = t->t.old->c;	/* char array [24][80] */
	byte **a = t->t.old->a;	/* attr array [24][80] */
	char *o;
	int i, j, cf;

/* New code: */

	o = r_data + 25 * 4;		/* First byte of r_data after line index */

	if (force_mono)
	{
		for (j = 0; j < 24; j++)
		{
			/* Set up the line offset entry */
			((int *)r_data)[j] = o - r_data;

			for (i = 0; i < 80; i++)
				*o++ = a[j][i] != TERM_DARK ? c[j][i] : ' ';
			/* 0,2 => end of line */
			*o++ = 0;
			*o++ = 2;
		}
	}
	else
	{
		for (j = 0; j < 24; j++)
		{
			/* Set up the line offset entry */
			((int *)r_data)[j] = o - r_data;

			/* Each line starts in white */
			cf = TERM_WHITE;

			for (i = 0; i < 80; i++)
			{
				if (a[j][i] != cf)
				{
					/* 0,6 => change FG */
					*o++ = 0;
					*o++ = 6;
					cf = *o++ = a[j][i];
				}
				*o++ = c[j][i];
			}
			/* 0,2 => end of line */
			*o++ = 0;
			*o++ = 2;
		}
	}
}


/*
 | Set up 'zrb' for the current screen mode.
 */
static void set_up_zrb_for_mode(void)
{
	static char work_area[4096];
	zrb.r_workarea = work_area;
	zrb.r_palette = zpalette;
	zrb.r_linesp = 0;
	zrb.r_for = TERM_WHITE;
	zrb.r_bac = 0;
	zrb.r_data = r_data;
	SWI(2, 0, SWI_ZapRedraw_ReadVduVars, 0, &zrb);
}



/*
 | Set up the ZapRedrawBlock ready to redraw term 't'
 | (caches the r_data as part of the process)
 */
static void set_up_zrb(term_data *t)
{
	int fw, fh;

	zrb.r_flags.value = 0;

	/* Set font info up */
	fw = t->font->w;
	fh = t->font->h;
	SWI(4, 4, SWI_ZapRedraw_CachedCharSize, zrb.r_bpp, 0, fw, fh,
		NULL, NULL, &(zrb.r_cbpl), &(zrb.r_cbpc));
	zrb.r_caddr = (void *)(((int)t->font->bpp_n) - (t->font->f * zrb.r_cbpc));

	zrb.r_charw = fw;			/* Character size in pixels */
	zrb.r_charh = fh;

	if (t->font == SYSTEM_FONT)
		zrb.r_flags.bits.double_height = screen_eig.y == 1;
	else
		zrb.r_flags.bits.double_height = 0;

	make_r_data(t);				/* Cache the r_data */
}





static void redraw_window(window_redrawblock * rb, BOOL *more, term_data *t)
{
	int cx, cy, cw, ch;

	/* set GCOL for cursor colour */
	if (t->cursor.visible)
	{
		cw = zrb.r_charw << screen_eig.x;
		ch = -(zrb.r_charh << screen_eig.y);
		if (zrb.r_flags.bits.double_height)
		{
			ch *= 2;
		}
		cx = t->cursor.pos.x * cw;
		cy = t->cursor.pos.y * ch;
		cx += (rb->rect.min.x - rb->scroll.x);
		cy += (rb->rect.max.y - rb->scroll.y);
		cw -= (1 << screen_eig.x);
		ch += (1 << screen_eig.y);
		cy -= (1 << screen_eig.y);
	}

	while (*more)
	{
		SWI(2, 0, SWI_ZapRedraw_GetRectangle, rb, &zrb);
		SWI(2, 0, SWI_ZapRedraw_RedrawArea, NULL, &zrb);
		if (t->cursor.visible)
		{
			ColourTrans_SetGCOL(cursor_rgb, 0, 0);
			GFX_Move(cx, cy);
			GFX_DrawBy(cw, 0);
			GFX_DrawBy(0, ch);
			GFX_DrawBy(-cw, 0);
			GFX_DrawBy(0, -ch);
		}
		Wimp_GetRectangle(rb, more);
	}
}





static BOOL Hnd_Redraw(event_pollblock * pb, void *ref)
{
	term_data *t = (term_data *)ref;
	window_redrawblock rb;
	BOOL more;

	rb.window = t->w;
	Wimp_RedrawWindow(&rb, &more);

	set_up_zrb(t);

	redraw_window(&rb, &more, t);

	return TRUE;
}





static void refresh_window(term_data *t)
{
	window_redrawblock rb;
	BOOL more;
	int fw, fh;

	if ((t->changed_box.min.x >= t->changed_box.max.x) ||
		(t->changed_box.min.y >= t->changed_box.max.y))
		return;

	set_up_zrb(t);

	fw = zrb.r_charw << screen_eig.x;
	fh = -(zrb.r_charh << screen_eig.y);
	if (zrb.r_flags.bits.double_height)
	{
		fh *= 2;
	}

	rb.window = t->w;
	rb.rect.min.x = fw * t->changed_box.min.x;
	rb.rect.max.x = fw * t->changed_box.max.x;

	rb.rect.max.y = fh * t->changed_box.min.y;
	rb.rect.min.y = fh * t->changed_box.max.y;

	Wimp_UpdateWindow(&rb, &more);
	redraw_window(&rb, &more, t);

	t->changed_box.min.x = t->changed_box.min.y = 255;
	t->changed_box.max.x = t->changed_box.max.y = 0;
}



static void refresh_windows(void)
{
	int i;
	window_info info;
	for (i = 0; i < MAX_TERM_DATA; i++)
	{
		info.window = data[i].w;
		Wimp_GetWindowInfo(&info);
		if (info.block.flags.data.open)
			refresh_window(&(data[i]));
	}
}


/*
 | Set the size of a window.
 | If the window grows but has no scroll bars then it is re-sized to
 | the new extent.  If it shrinks then it is resized regardless.
 |
 | If the window isn't open then it is opened behind the backwindow,
 | resized and then closed again...  I /did/ have a reason for doing this
 | rather than simply recreating the window at the new size, but for the
 | life of me I can't remember what it was...
 */
static void set_window_size(window_handle w, int width, int height)
{
	window_state ws;
	int reclose;
/*
 *  int cw,ch;
 */

	Wimp_GetWindowState(w, &ws);
	Window_SetExtent(w, 0, -height, width, 0);

	reclose = !ws.flags.data.open;
	if (!(ws.flags.value & (0xf << 27)))
	{
		if (reclose)
		{
			ws.openblock.behind = -3;
			Wimp_OpenWindow(&(ws.openblock));
		}
		/* Removed - caused "pixel creep" :)
		 *  cw = ws.openblock.screenrect.max.x;
		 *  ch = ws.openblock.screenrect.max.y;
		 *  cw -= ws.openblock.screenrect.min.x;
		 *  ch -= ws.openblock.screenrect.min.y;
		 *  ws.openblock.screenrect.min.x -= ((width-cw)/2)-(1<<screen_eig.x);
		 *  ws.openblock.screenrect.min.y -= ((height-ch)/2)-(1<<screen_eig.y);
		 */
		ws.openblock.screenrect.max.x = ws.openblock.screenrect.min.x + width;
		ws.openblock.screenrect.max.y = ws.openblock.screenrect.min.y + height;
		Wimp_OpenWindow(&(ws.openblock));
		if (reclose)
		{
			Wimp_CloseWindow(w);
		}
	}
}















/*
 | Change the size of a window to suit the font displayed in it
 */
static void resize_term_for_font(term_data *t)
{
	int fw, fh;
	set_up_zrb(t);

	fw = zrb.r_charw << screen_eig.x;
	fh = zrb.r_charh << screen_eig.y;
	if (zrb.r_flags.bits.double_height)
	{
		fh *= 2;
	}

	/* Caulculate new size */
	fw *= 80;
	fh *= 24;

	set_window_size(t->w, fw, fh);
}



















static BOOL Hnd_Caret(event_pollblock * pb, void *ref)
{
	if (ref)
		got_caret = 1;
	else
		got_caret = 0;
	return TRUE;
}




/*
 | Attach a (named) font to the specified term.
 | If 'font' is NULL then the system font is attached.
 | The bpp_n data is calculated if necessary
 | returns:
 |	1 => the font was attached OK
 |	0 => the system font was substituted
 */
static int attach_font_to_term(term_data *t, char *font)
{
	if (t->font != SYSTEM_FONT)
	{
		lose_font(t->font);
	}
	if (font)
	{
		t->font = find_font(font);
	}
	if (!t->font)
	{
		t->font = SYSTEM_FONT;
		if (font)
		{
			Msgs_Report(1, "err.font_l", font);
		}
	}
	else
	{
		if (!t->font->bpp_n)
		{
			lose_font(t->font);
			t->font = SYSTEM_FONT;
			if (font)
			{
				Msgs_Report(1, "err.font_c", font);
			}
		}
	}
	resize_term_for_font(t);
	return !(t->font == SYSTEM_FONT);
}




/*--------------------------------------------------------------------------*/



/*
 | Create a menu of all the (probable!) fonts in the specified location
 | NB: Any file of type 'data' is considered a font.
 |
 | Subdirectories are recursively searched.
 |
 | 1.10 - Uses <variant>$FontPaths to get a (space separated) list of paths
 | to search.  For each path name, the menu text will be the name and the
 | path searched will be <variant>$<name>$FontPath
 |
 | Eg. (for angband):
 | Angband$FontPaths Zap Angband
 | Angband$Zap$FontPath ZapFonts:
 | Angband$Angband$FontPath Angband:xtra.fonts.
 */
static void make_font_menu(void)
{
	char *t;
	char buffer[260];
	char menu_buffer[260];
	int paths;
	int i;
	unsigned int max_width;
	const char *path[64];	/* pointers to path names */
	menu_item *mi;

	font_menu = NULL;

	/* Get the path (ie. dir) to look under */
	t = getenv(RISCOS_VARIANT "$FontPaths");

	/* Hack: cope if the path isn't set */
	if (!t)
	{
		t = "";
	}

	strcpy(buffer, t);

	/*
	   | Count how many paths there are, build an array of pointers to them
	   | and terminate them in the buffer
	 */
	paths = 1;					/* including the system font fake path '<System>' */
	for (t = buffer; *t; t++)
	{
		if (*t == ' ')
		{
			*t = 0;
		}
		else
		{
			if (t == buffer || !t[-1])
			{
				path[paths] = t;
				paths++;
			}
		}
	}

	/*
	   | Create the menu
	 */
	path[0] = SYSTEM_FONT->name;

	font_menu = f_malloc(sizeof(menu_block) + paths * sizeof(menu_item));
	if (!font_menu)
	{
		core("Out of memory (building font menu)");
	}
	memset(font_menu, 0, sizeof(menu_block) + paths * sizeof(menu_item));

	strncpy(font_menu->title, "Fonts", 12);
	font_menu->titlefore = 7;
	font_menu->titleback = 2;
	font_menu->workfore = 7;
	font_menu->workback = 0;
	font_menu->height = 44;
	font_menu->gap = 0;
	max_width = strlen(font_menu->title);

	mi = (menu_item *) (font_menu + 1);

	for (i = 0; i < paths; i++)
	{
		mi[i].submenu.value = -1;
		mi[i].iconflags.data.text = 1;
		mi[i].iconflags.data.filled = 1;
		mi[i].iconflags.data.foreground = 7;
		mi[i].iconflags.data.background = 0;
		strncpy(mi[i].icondata.text, path[i], 12);
		if (strlen(mi[i].icondata.text) > max_width)
			max_width = strlen(mi[i].icondata.text);
	}
	font_menu->width = (max_width + 2) * 16;
	mi[i - 1].menuflags.data.last = 1;

	/*
	   | Hack: add a dotted line after the system font entry if appropriate
	 */
	if (paths > 1) mi[0].menuflags.data.dotted = 1;

	/*
	   | Iterate over the paths, building the appropriate submenus
	 */
	for (i = 1; i < paths; i++)
	{
		menu_ptr sub_menu = NULL;

		sprintf(menu_buffer, "%s$%s$FontPath", RISCOS_VARIANT, path[i]);
		t = getenv(menu_buffer);
		/* Hack: cope if the path isn't defined */
		if (!t)
		{
			t = "";
		}

		/* Fudge so that the fontpath can be a path, not just a dir. */
		strcpy(menu_buffer, t);
		for (t = menu_buffer; *t > ' '; t++)
			;
		if (t[-1] == '.')
		{
			t--;
		}
		*t = 0;

		/* Build the menu.  Don't bother if the path variable was empty */
		if (*menu_buffer)
			sub_menu = make_zfont_menu(menu_buffer);

		if (!sub_menu)
		{
			mi[i].iconflags.data.shaded = 1;
		}
		else
		{
			mi[i].submenu.menu = sub_menu;
			/* Override the title of the 'root' sub-menu */
			strncpy(sub_menu->title, path[i], 12);
			/* Add the submenu to the main menu */
		}
	}

	return;
}

/* ----------------------------------------------- musus, xxxx-xx-xx ---
 * Create and set up the infobox.
 * --------------------------------------------------------------------- */
static void create_info_box(void)
{
	info_box = Window_Create("info", template_TITLEMIN);
	Icon_printf(info_box, 0, "%s %s", VARIANT, VERSION);
	Icon_SetText(info_box, 2, AUTHORS);
	Icon_SetText(info_box, 3, PORTERS);
	Icon_SetText(info_box, 7, PORTVERSION);

	return;
}

/*
 | Create the various menus
 */
static void init_menus(void)
{
	char buffer1[256];
	char buffer2[32];
	char *o;

	create_info_box();			/* For the Info> entries */
	make_font_menu();			/* Make the fonts menu */

	Msgs_Lookup("menu.ibar:Info|>Save As|Full screen,Gamma correction,Sound,"
				"Windows|Save choices|Quit (& save)", buffer1, 256);
	ibar_menu = Menu_New(VARIANT, buffer1);
	if (!ibar_menu) core("Can't create Iconbar menu!");

	Msgs_Lookup("menu.term:Info|>Save As|Font,Windows", buffer1, 256);
	term_menu = Menu_New(VARIANT, buffer1);
	if (!term_menu) core("Can't create Term menu!");

#ifndef OLD_TERM_MENU
	o = buffer1;
	o += sprintf(buffer1, "%s|", VARIANT);
	o += sprintf(o, "%s,%s,%s|", angband_term_name[1], angband_term_name[2],
				 angband_term_name[3]);
	sprintf(o, "%s,%s,%s,%s", angband_term_name[4], angband_term_name[5],
			angband_term_name[6], angband_term_name[7]);
#else
	Msgs_printf(buffer1, "menu.windows:%s|Term-1 (Mirror),Term-2 (Recall),"
				"Term-3 (Choice)|Term-4,Term-5,Term-6,Term-7", VARIANT);
#endif
	Msgs_Lookup("menu.winT:Windows", buffer2, 32);
	wind_menu = Menu_New(buffer2, buffer1);
	if (!wind_menu)
	{
		core("Can't create Windows menu!");
	}

	/* Now attach the various submenus to where they belong */
	Menu_AddSubMenu(ibar_menu, IBAR_MENU_INFO, (menu_ptr) info_box);
	Menu_AddSubMenu(ibar_menu, IBAR_MENU_GAMMA, (menu_ptr) gamma_win);
	Menu_AddSubMenu(ibar_menu, IBAR_MENU_SOUND, (menu_ptr) sound_win);
	Menu_AddSubMenu(ibar_menu, IBAR_MENU_WINDOWS, (menu_ptr) wind_menu);
	Menu_AddSubMenu(term_menu, TERM_MENU_INFO, (menu_ptr) info_box);
	Menu_AddSubMenu(term_menu, TERM_MENU_WINDOWS, wind_menu);

	/* Add the savebox */
	Menu_Warn(ibar_menu, IBAR_MENU_SAVE, TRUE, Hnd_SaveWarning, NULL);
	Menu_Warn(term_menu, TERM_MENU_SAVE, TRUE, Hnd_SaveWarning, NULL);

	if (font_menu)
		/* add the submenu */
		Menu_AddSubMenu(term_menu, TERM_MENU_FONT, font_menu);
	else
		/* If the font menu is buggered, shade its entry */
		/* unticked, shaded */
		Menu_SetFlags(term_menu, TERM_MENU_FONT, FALSE, TRUE);
}




static void grab_caret(void)
{
	caret_block cb;
	cb.window = data[0].w;
	cb.icon = -1;
	cb.height = 1 << 25;		/* Invisible */
	Wimp_SetCaretPosition(&cb);
}




/*
 | (Recursively) clear all ticks from the specified menu
 */
static void clear_all_menu_ticks(menu_ptr mp)
{
	menu_item *mi = (menu_item *) (mp + 1);

	do
	{
		if (mi->menuflags.data.ticked)
			mi->menuflags.data.ticked = 0;
		if (mi->submenu.value != -1)
			clear_all_menu_ticks(mi->submenu.menu);
		mi++;
	}
	while (mi[-1].menuflags.data.last != 1);
}






/*
 | Set the font menu's ticks to match the specifed font name.
 |
 | fm is the (sub) menu to scan down (recursing into it's submenus (if any))
 | fn is the font name to match
 | prefix is the menu text to be prepended to the menu entries due to
 | previous menus (eg. "08x16" will cause fn="08x16.fred" to match menu
 | entry "fred".
 |
 | NB: recursive.
 |
 */

static void set_font_menu_ticks(menu_ptr fm, char *fn, const char *prefix)
{
	char buffer[260];
	char *b_leaf;	/* -> menu 'leaf' text in buffer */
	int pl;	/* prefix string length */
	menu_item *mi = (menu_item *) (fm + 1);

	strcpy(buffer, prefix);
	pl = strlen(buffer);
	b_leaf = buffer + pl;

	do
	{
		/* Check for (substring) match */
		strncpy(b_leaf, mi->icondata.text, 12);

		/* Is it a sub-menu? */
		if (mi->submenu.value == -1)
		{
			/* No - must be an exact match */
			mi->menuflags.data.ticked = !strcmp(buffer, fn);
		}
		else
		{
			/* Yes - must be a partial match (with a dot on :) */
			strcat(b_leaf, ".");
			mi->menuflags.data.ticked =
				!strncmp(buffer, fn, pl + strlen(b_leaf));
			if (mi->menuflags.data.ticked)
				set_font_menu_ticks(mi->submenu.menu, fn, buffer);
			else
				clear_all_menu_ticks(mi->submenu.menu);
		}

		/* Next item */
		mi++;
	}
	while (mi[-1].menuflags.data.last != 1);	/* Until finished */
}











/*
 | Set ticks, etc. in the term_menu to reflect the current state of the
 | term 't'
 */
static void set_up_term_menu(term_data *t)
{
	int i;
	menu_ptr mp;
	menu_item *mi;
	window_state ws;

	/* First of all, set up menu title to be the term's title */
	strncpy(term_menu->title, t->name, 12);

	/* Now set the ticks in the Windows> submenu (cuz it's easy) */
	mp = wind_menu;				/* Windows submenu */
	mi = (menu_item *) (mp + 1);	/* First entry */
	for (i = 0; i < MAX_TERM_DATA; i++)
	{
		Wimp_GetWindowState(data[i].w, &ws);
		mi[i].menuflags.data.ticked = ws.flags.data.open;
	}

	/*
	   | Now, the tricky bit:  find out which font is selected in this
	   | term and tick it in the menu.  Untick all the rest.
	 */
	set_font_menu_ticks(font_menu, t->font->name, "");

	/* Shade the 'Save>' entry if saving isn't possible (yet) */
	if (game_in_progress && character_generated)
		Menu_SetFlags(term_menu, TERM_MENU_SAVE, 0, PDEADCHK);
	else
		Menu_SetFlags(term_menu, TERM_MENU_SAVE, 0, TRUE);

}



/*
 | Generic 'click' handler for windows - turns a drag on the window
 | into a move-window style drag.
 */
static BOOL Hnd_Click(event_pollblock * pb, void *ref)
{
	if (pb->data.mouse.button.data.dragselect ||
		pb->data.mouse.button.data.dragadjust)
	{
		drag_block b;
		b.window = pb->data.mouse.window;
		b.screenrect.min.x = b.screenrect.min.y = 0;
		b.screenrect.max.x = screen_size.x;
		b.screenrect.max.y = screen_size.y;
		b.type = drag_MOVEWINDOW;
		Wimp_DragBox(&b);
	}

	return TRUE;
}



/*
 | Handle a click on a Term window.
 */
static BOOL Hnd_TermClick(event_pollblock * pb, void *ref)
{
	term_data *t = (term_data *)ref;

	if (pb->data.mouse.button.data.menu)
	{
		menu_term = t;
		set_up_term_menu(t);
		Menu_Show(term_menu, pb->data.mouse.pos.x - 32,
				  pb->data.mouse.pos.y + 32);
	}
	else
	{
		grab_caret();
		Hnd_Click(pb, ref);
	}

	return TRUE;
}

#endif /* FULLSCREEN_ONLY */



static void mark_ood(term_data *t, int minx, int miny, int maxx, int maxy)
{
	if (t->changed_box.min.x > minx)
		t->changed_box.min.x = minx;
	if (t->changed_box.max.x < maxx)
		t->changed_box.max.x = maxx;
	if (t->changed_box.min.y > miny)
		t->changed_box.min.y = miny;
	if (t->changed_box.max.y < maxy)
		t->changed_box.max.y = maxy;
}


#ifndef FULLSCREEN_ONLY

/* Check for an event (ie. key press) */
static errr Term_xtra_acn_check(void)
{
	static int last_poll = 0;
	unsigned int curr_time;
	int bh, bl;

	/*
	   | Only poll the wimp if there's something in the keyboard buffer
	   | or every 10cs.  This is presumably so that we process as many
	   | keypresses as possible before any other application gets a go.
	 */

	/* Check the kbd buffer */
	SWI(3, 3, SWI_OS_Byte, 128, 255, 0, /**/ NULL, &bl, &bh);
	bl = (bl & 0xff) + (bh << 8);

	/* Check how long it is since we last polled */
	curr_time = Time_Monotonic();

	if ((bl > 0 && got_caret) || ((curr_time - last_poll) > 9))
	{
		last_poll = curr_time;
		Stop_Hourglass;
		Event_Poll();
		Start_Hourglass;
	}

	/*
	   | This allows the user to interrupt the borg.
	 */
	if (key_pressed) return key_pressed = 0;

	return 1;
}


/*
 | Wait for an event (ie. keypress)
 | Note that we idle poll once a second to allow us to implement the
 | alarm system.
 */
static errr Term_xtra_acn_event(void)
{
	Stop_Hourglass;

	while (!key_pressed && !fullscreen_font)
	{
		Event_PollIdle(100);
	}
	Start_Hourglass;

	return key_pressed = 0;
}



/* React to changes (eg. palette change) */
static errr Term_xtra_acn_react(void)
{
	int c;

	cache_palette();

	/* Mark the entirety of each window as out of date */
	for (c = 0; c < MAX_TERM_DATA; c++)
		mark_ood(&data[c], 0, 0, 80, 24);

	/* Force a redraw of the windows */
	refresh_windows();

	/* Success */
	return 0;
}



/* Do various things to a term */
static errr Term_xtra_acn(int n, int v)
{
	term_data *t = (term_data *)Term;

	switch (n)
	{
		case TERM_XTRA_CLEAR:	/* Clear the Term */
			/*for ( i=0; i<Term->hgt; i++ )
			   t->froshed[i] = 1; */
			mark_ood(t, 0, 0, Term->wid, Term->hgt);
			/*refresh_window( t ); - NB: Term isn't actually cleared yet! */
			/* Success */
			return 0;

		case TERM_XTRA_EVENT:	/* Wait/check for an event */
			if (v)
				return Term_xtra_acn_event();
			else
				return Term_xtra_acn_check();

		case TERM_XTRA_BORED:	/* Bored */
			return Term_xtra_acn_check();

		case TERM_XTRA_FLUSH:	/* Flush input */
			if (got_caret)
			{
				/* 1.21 - Hack: wait until no keys are pressed */
				if (hack_flush)
					for (v = 0; v != 0xff;)
						SWI(1, 2, SWI_OS_Byte, 122, 0, &v);
				SWI(3, 0, SWI_OS_Byte, 21, 0, 0);	/* Flush Kbd buffer */
			}
			return 0;

		case TERM_XTRA_FRESH:	/* Flush output */
			refresh_window(t);
			return 0;

		case TERM_XTRA_FROSH:	/* Ensure line 'v' is plotted */
			/* Doesn't do anything */
			return 0;

		case TERM_XTRA_SHAPE:	/* Set cursor visibility */
			t->cursor.visible = v ? TRUE : FALSE;
			mark_ood(t, t->cursor.pos.x, t->cursor.pos.y,
					 t->cursor.pos.x + 1, t->cursor.pos.y + 1);
			refresh_window(t);	/* needed? */
			return 0;

		case TERM_XTRA_NOISE:	/* Make a beep */
			Sound_SysBeep();
			return 0;

		case TERM_XTRA_REACT:	/* React to, eg. palette changes */
			return Term_xtra_acn_react();

		case TERM_XTRA_DELAY:	/* Delay for 'v' ms */
			if (v > 0)
			{
				unsigned int start = Time_Monotonic();
				v = (v + 5) / 10;	/* Round to nearest cs */
				GFX_Wait();
				while ((Time_Monotonic() - start) < v)
					;
			}
			return 0;

		case TERM_XTRA_SOUND:	/* Play a sound :) */
			if (enable_sound)
			{
				play_sound(v);
			}
			return 0;
			
		/* Subdirectory scan */
		case TERM_XTRA_SCANSUBDIR:
		{
		    filing_dirdata directory;
		    filing_direntry *entry;

			scansubdir_max = 0;

			if (Filing_OpenDir(riscosify_name(scansubdir_dir), &directory, sizeof(filing_direntry), readdirtype_DIRENTRY) != NULL)
			{
			  Error_Report(0, "Couldn't open directory \"%s\"", riscosify_name(scansubdir_dir));
			  return 0;
			}

			while ((entry = Filing_ReadDir(&directory)) != NULL)
			{
				if (entry->objtype == filing_DIRECTORY)
				{    
					string_free(scansubdir_result[scansubdir_max]);
					scansubdir_result[scansubdir_max] = string_make(entry->name);
					++scansubdir_max;
				}
			}

			Filing_CloseDir(&directory);

			return 0;
		}

		/* Return current "time" in milliseconds */
		case TERM_XTRA_GET_DELAY:
				{
			Term_xtra_long = Time_Monotonic() * 100;

			return 0;
				}

		/* Rename main window */
		case TERM_XTRA_RENAME_MAIN_WIN:
		{
			Window_SetTitle(data[0].w, angband_term_name[0]);
			return 0;
		}

		default:
			return 1;			/* Unsupported */
	}
}



/* Move (but don't necessarily display) the cursor */
static errr Term_curs_acn(int x, int y)
{
	term_data *t = (term_data *)Term;

	if (t->cursor.visible)
		mark_ood(t, t->cursor.pos.x, t->cursor.pos.y,
				 t->cursor.pos.x + 1, t->cursor.pos.y + 1);

	t->cursor.pos.x = x;
	t->cursor.pos.y = y;

	if (t->cursor.visible)
		mark_ood(t, t->cursor.pos.x, t->cursor.pos.y,
				 t->cursor.pos.x + 1, t->cursor.pos.y + 1);

	return 0;
}



/*
 | NB: these two are very simple since we use the Term's contents
 | directly to generate the r_data for ZapRedraw.
 */

/* Erase 'n' characters at (x,y) */
static errr Term_wipe_acn(int x, int y, int n)
{
	mark_ood((term_data *)Term, x, y, x + n, y + 1);
	return 0;
}

/* Write 'n' characters from 's' with attr 'a' at (x,y) */
static errr Term_text_acn(int x, int y, int n, byte a, cptr s)
{
	mark_ood((term_data *)Term, x, y, x + n, y + 1);
	return 0;
}

#endif /* FULLSCREEN_ONLY */


/* Initialise one of our terms */
static void Term_init_acn(term *t)
{
	term_data *term = (term_data *)t;

	/* Ludicrous changed box settings :) */
	term->changed_box.min.x = 256;
	term->changed_box.min.y = 256;
	term->changed_box.max.x = 0;
	term->changed_box.max.y = 0;
}



static void term_data_link(term_data *td, int k)
{
	term *t = &(td->t);

	/* Initialise the term */
	term_init(t, 80, 24, k);

	/* Set flags and hooks */
	t->attr_blank = TERM_WHITE;
	t->char_blank = ' ';

	/* Experiment (FS mode requires them) */
	t->always_text = TRUE;
	t->never_frosh = TRUE;
	/* Experiment (FS mode requires them) */
              
#ifdef FULLSCREEN_ONLY  
	t->wipe_hook  = Term_wipe_acnFS;
	t->xtra_hook  = Term_xtra_acnFS;
	t->curs_hook  = Term_curs_acnFS;
	t->text_hook  = Term_text_acnFS;
#else
	t->init_hook = Term_init_acn;
	t->xtra_hook = Term_xtra_acn;
	t->wipe_hook = Term_wipe_acn;
	t->curs_hook = Term_curs_acn;
	t->text_hook = Term_text_acn;
	t->user_hook = Term_user_acn;
#endif /* FULLSCREEN_ONLY */

	t->data = td;

	Term_activate(t);
}


#ifndef FULLSCREEN_ONLY

/* Open default windows (ie. as set in choices) at the appropriate sizes */
static void show_windows(void)
{
	int i;
	for (i = MAX_TERM_DATA; i-- > 0;)
	{
		if (!data[i].unopened)
		{
			if (data[i].def_open)
				Window_Show(data[i].w, open_WHEREVER);
		}
		else
		{
			if (data[i].def_open)
			{
				window_openblock ob;
				ob.window = data[i].w;
				ob.screenrect = data[i].def_pos;
				ob.scroll = data[i].def_scroll;
				ob.behind = -1;
				Wimp_OpenWindow(&ob);
				data[i].unopened = 0;
			}
		}
	}
}


/*
 | 'ref' is used to indicate whether this close is being forced by some other
 | part of the code (eg. the Windows> submenu code).  This is used to modify
 | the 'adjust doesn't close other terms' behavior.
 */

static BOOL Hnd_MainClose(event_pollblock * pb, void *ref)
{
	int i;
	window_state ws;
	mouse_block mb;

	/* New in 1.08: don't close other Terms if closed with adjust */
	Wimp_GetPointerInfo(&mb);
	if (ref || mb.button.data.adjust)
	{
		Wimp_CloseWindow(data[0].w);
	}
	else
	{
		/* Close all the terms, but mark the open ones as 'def_open' */
		for (i = 0; i < MAX_TERM_DATA; i++)
		{
			Wimp_GetWindowState(data[i].w, &ws);
			if (!ws.flags.data.open)
			{
				data[i].def_open = 0;
			}
			else
			{
				Wimp_CloseWindow(data[i].w);
				data[i].def_open = 1;
			}
		}
	}

	return TRUE;
}


static BOOL Hnd_PaletteChange(event_pollblock * pb, void *ref)
{
	cache_palette();
	return TRUE;
}



static BOOL Hnd_ModeChange(event_pollblock * pb, void *ref)
{
	int i;
	Screen_CacheModeInfo();
	set_up_zrb_for_mode();		/* (re)set up the redraw block */
	cache_palette();			/* (re)cache the palette */
	cache_fonts();				/* (re)cache the fonts */
	/* Enforce sizes (eg. if screen_eig.y has changed) */
	for (i = 0; i < MAX_TERM_DATA; i++)
		resize_term_for_font(&(data[i]));
	return TRUE;
}




static BOOL Hnd_Keypress(event_pollblock * pb, void *ref)
{
	static const char hex[] = "0123456789ABCDEF";
	int c = pb->data.key.code;
	/* Check whether this key was pressed in Term 0 */
	if (pb->data.key.caret.window == data[0].w)
	{
		switch (c)
		{
			case keycode_F12:
			case keycode_SHIFT_F12:
			case keycode_CTRL_F12:
			case keycode_CTRL_SHIFT_F12:
				/* Never intercept these */
				break;

			case 27:			/* handle escape specially */
				if (Kbd_KeyDown(inkey_CTRL))
				{
					ack_alarm();
					return TRUE;
				}

			/* Send everything else onto the Term package */
			default:  
				/* Take care of "special" keypresses */
				switch (c)
				{
					case keycode_TAB:
					{
						c = '\t';
						break;
					}
                   
					case keycode_PAGEUP:
					{
						c = '9';
						break;
					}

					case keycode_PAGEDOWN:
					{
						c = '3';
						break;
					}         

					case keycode_COPY:
					{
						c = '1';
						break;
					}          

					case keycode_HOME:
					{
						c = '7';
						break;
					}
				}
				/* Pass to the angband engine */
				/* Allow shift & ctrl to modify the keypad keys */
				if (c >= '0' && c <= '9')
				{
					kbd_modifiers m = Kbd_GetModifiers(FALSE);
					if (m.shift)
					{
						c |= 0x800;
					}
					if (m.ctrl)
					{
						c |= 0x400;
					}
					/* Could maybe add ALT as 0x1000 ??? */
				}

				/* Keys >255 have to be send as escape sequences (31=escape) */
				if (c > 255 || c == 31)
				{
					Term_keypress(31);
					Term_keypress(hex[(c & 0xf00) >> 8]);
					Term_keypress(hex[(c & 0x0f0) >> 4]);
					Term_keypress(hex[(c & 0x00f)]);
					c = 13;
				}
				Term_keypress(c);
				key_pressed = 1;
				/*if ( c==27 ) { escape_pressed = 1; } */
				return TRUE;
		}
	}

	Wimp_ProcessKey(c);
	return TRUE;
}


/*--------------------------------------------------------------------------*/
/* Gamma correction window stuff											*/
/*--------------------------------------------------------------------------*/

static void redraw_gamma(window_redrawblock * rb, BOOL *more)
{
	int i, y, x, h, w;
	int bx, by;
	int dither;

	bx = Coord_XToScreen(GC_XOFF, (convert_block *) & (rb->rect));
	by = Coord_YToScreen(GC_YOFF, (convert_block *) & (rb->rect));

	h = GC_HEIGHT / 4;
	w = GC_WIDTH / 16;

	x = bx;

	while (*more)
	{
		for (i = 0; i < 16; i++)
		{
			y = by;
			for (dither = 0; dither < 2; dither++)
			{
				/* Solid block: */
				ColourTrans_SetGCOL(palette[i], dither << 8, 0);
				GFX_RectangleFill(x, y, w, -h);
				y -= h;
				/* Dot on black: */
				ColourTrans_SetGCOL(palette[0], dither << 8, 0);
				GFX_RectangleFill(x, y, w, -h);
				ColourTrans_SetGCOL(palette[i], dither << 8, 0);
				GFX_RectangleFill(x + (w / 2) - 2, y - (h / 2), 2, 2);
				y -= h;
			}
			x += w;
		}
		Wimp_GetRectangle(rb, more);
	}
}


static void update_gamma(void)
{
	window_redrawblock rb;
	BOOL more;

	rb.window = gamma_win;
	rb.rect.min.x = GC_XOFF;
	rb.rect.min.y = GC_YOFF - GC_HEIGHT;
	rb.rect.max.y = GC_XOFF + GC_WIDTH + screen_delta.x;
	rb.rect.max.y = GC_YOFF + screen_delta.y;

	Wimp_UpdateWindow(&rb, &more);
	if (more)
	{
		redraw_gamma(&rb, &more);
	}
}



static BOOL Hnd_RedrawGamma(event_pollblock * pb, void *ref)
{
	window_redrawblock rb;
	BOOL more;

	rb.window = pb->data.openblock.window;
	Wimp_RedrawWindow(&rb, &more);
	if (more)
	{
		redraw_gamma(&rb, &more);
	}

	return TRUE;
}


static BOOL Hnd_GammaClick(event_pollblock * pb, void *ref)
{
	int up = (ref == 0);

	if (up)
	{
		if (gamma < 9.0)
		{
			gamma += 0.05;
			Icon_SetDouble(gamma_win, 0, gamma, 2);
			Term_xtra_acn_react();
			update_gamma();
		}
	}
	else
	{
		if (gamma > 0.05)
		{
			gamma -= 0.05;
			Icon_SetDouble(gamma_win, GAMMA_ICN, gamma, 2);
			Term_xtra_acn_react();
			update_gamma();
		}
	}

	/* Hack: if the user menu is active then force it to redraw */
	if (user_menu_active)
	{
		Term_keypress(18);
		key_pressed = 1;
	}

	return TRUE;
}

/*
 | Reflect the current options in the gamma window
 */
static void set_gamma_window_state(void)
{
	if (minimise_memory) return;

	Icon_SetDouble(gamma_win, 0, gamma, 2);
}


static void init_gamma_window(void)
{                                 
	if (minimise_memory) return;
	
	Template_UseSpriteArea(resource_sprites);
	gamma_win = Window_Create("gamma", template_TITLEMIN);
	Template_UseSpriteArea(NULL);
	Event_Claim(event_REDRAW, gamma_win, event_ANY, Hnd_RedrawGamma, 0);
	Event_Claim(event_CLICK, gamma_win, GAMMA_DOWN, Hnd_GammaClick, (void *)1);
	Event_Claim(event_CLICK, gamma_win, GAMMA_UP, Hnd_GammaClick, (void *)0);
	set_gamma_window_state();
}


/*--------------------------------------------------------------------------*/
/* Sound options window stuff												*/
/*--------------------------------------------------------------------------*/

static slider_info volume_slider;


/*
 | Reflect the current sound config in the sound options window
 */
static void set_sound_window_state(void)
{
	if (minimise_memory) return;
	
	Icon_SetSelect(sound_win, SND_ENABLE, enable_sound);
	Slider_SetValue(&volume_slider, sound_volume, NULL, NULL);

	if (sound_volume > 127)
		volume_slider.colour.foreground = colour_RED;
	else
		volume_slider.colour.foreground = colour_GREEN;
}



/*
 | The sound slider has been dragged, so update the sound volume setting
 */
static int update_volume_from_slider(slider_info *si, void *ref)
{
	sound_volume = Slider_ReadValue(si);

	if (sound_volume > 127)
		volume_slider.colour.foreground = colour_RED;
	else
		volume_slider.colour.foreground = colour_GREEN;

	return 0;
}


/*
 | Handle redraw events for the sound options window
 */
static BOOL Hnd_RedrawSnd(event_pollblock * pb, void *ref)
{
	window_redrawblock redraw;
	BOOL more;

	redraw.window = pb->data.openblock.window;
	Wimp_RedrawWindow(&redraw, &more);

	while (more)
	{
		Slider_Redraw(((slider_info *) ref), &redraw.cliprect);
		Wimp_GetRectangle(&redraw, &more);
	}
	return (TRUE);
}


/*
 | Handle clicks on the sound options window
 */
static BOOL Hnd_SndClick(event_pollblock * pb, void *ref)
{
	int icn = pb->data.mouse.icon;
	int adj = pb->data.mouse.button.data.adjust;
	slider_info *si = (slider_info *) ref;

	switch (icn)
	{
			/* Bump arrows for the slider: */
		case SND_VOL_DOWN:
			adj = !adj;

		case SND_VOL_UP:
			adj = adj ? -1 : 1;
			sound_volume += adj;
			if (sound_volume < SOUND_VOL_MIN)
			{
				sound_volume = SOUND_VOL_MIN;
			}
			if (sound_volume > SOUND_VOL_MAX)
			{
				sound_volume = SOUND_VOL_MAX;
			}
			set_sound_window_state();
			break;

			/* The slider itself */
		case SND_VOL_SLIDER:
			Icon_ForceRedraw(sound_win, SND_VOL_SLIDER);
			Slider_Drag(si, NULL, NULL, NULL);
			break;

			/* The enable/disable icon */
		case SND_ENABLE:
			enable_sound = !enable_sound;
			Icon_SetSelect(sound_win, SND_ENABLE, enable_sound);
			if (enable_sound)
			{
				initialise_sound();
			}
			break;
	}

	/* Hack: if the user menu is active then force it to redraw */
	if (user_menu_active)
	{
		Term_keypress(18);
		key_pressed = 1;
	}

	return TRUE;
}



/*
 | Set the sound options window up, ready to rock :)
 */
static void init_sound_window(void)
{
	Template_UseSpriteArea(resource_sprites);
	sound_win = Window_Create("sound", template_TITLEMIN);
	Template_UseSpriteArea(NULL);

	Event_Claim(event_REDRAW, sound_win, event_ANY, Hnd_RedrawSnd,
				(void *)&volume_slider);
	Event_Claim(event_CLICK, sound_win, event_ANY, Hnd_SndClick,
				(void *)&volume_slider);

	/* Set up the slider info */
	volume_slider.window = sound_win;
	volume_slider.icon = SND_VOL_SLIDER;
	volume_slider.limits.min = SOUND_VOL_MIN;
	volume_slider.limits.max = SOUND_VOL_MAX;
	volume_slider.colour.foreground = colour_GREEN;
	volume_slider.colour.background = colour_WHITE;
	volume_slider.border.x = 8;
	volume_slider.border.y = 8;
	volume_slider.update = update_volume_from_slider;

	set_sound_window_state();
}





/*--------------------------------------------------------------------------*/











/*
 | A font has been selected.
 | At this point, menu_term is a pointer to the term for which the
 | menu was opened.
 */
static void handle_font_selection(int *s)
{
	char name[260];
	os_error *e;
	char *r;
	menu_ptr mp = font_menu;
	int *mis;

	/* Follow the >s to the entry specified */
	for (mis = s; *mis != -1; mis++)
		mp = ((menu_item *) (mp + 1))[*mis].submenu.menu;

	/*
	   | Now, check to see if we've hit a leaf entry.
	   | NB: If the entry isn't a leaf entry then the first entry in its submenu
	   | is used instead
	 */
	if (((int)mp) != -1)
	{
		mis[0] = 0;
		mis[1] = -1;
		mp = ((menu_item *) (mp + 1))[0].submenu.menu;
	}

	if (((int)mp) != -1)
		return;

	e = Wimp_DecodeMenu(font_menu, s, name);
	if (e)
	{
		plog(e->errmess);
		return;
	}

	/* Make sure that the string is NULL terminated */
	for (r = name; *r >= ' '; r++)
		;
	*r = 0;

	attach_font_to_term(menu_term, name);
	mark_ood(menu_term, 0, 0, 80, 24);
	refresh_window(menu_term);
}


#endif /* FULLSCREEN_ONLY */




static void load_choices(void)
{
	FILE *fp = NULL;
	char *cf;
	int i;
	char buffer[260];

	cf = find_choices(FALSE);
	if (*cf)
		fp = fopen(cf, "r");

	/* Implement default choices */
	data[0].def_open = 1;
	data[0].unopened = 1;		/* ie. force def_pos */
	data[0].def_pos.min.x = (screen_size.x - 1280) / 2;
	data[0].def_pos.max.x = (screen_size.x + 1280) / 2;
	data[0].def_pos.min.y = (screen_size.y - 768) / 2 - 32;
	data[0].def_pos.max.y = (screen_size.y + 768) / 2 - 32;
	data[0].def_scroll.x = data[0].def_scroll.y = 0;
	for (i = 1; i < MAX_TERM_DATA; i++)
	{
		data[i].def_open = 0;
		data[i].unopened = 1;	/* ie. force def_pos */
		data[i].def_pos.min.x = (screen_size.x - 1280) / 2;
		data[i].def_pos.max.x = (screen_size.x + 1280) / 2;
		data[i].def_pos.min.y = (screen_size.y - 768) / 2;
		data[i].def_pos.max.y = (screen_size.y + 768) / 2;
		data[i].def_scroll.x = data[i].def_scroll.y = 0;
	}

	if (fp)
	{
		const char *t_;
		char *o_;

		if (!fgets(buffer, sizeof(buffer), fp))
		{
			fclose(fp);
			return;
		}
		if (strcmp(buffer, "[Angband config, Musus' port]\n"))
		{
			fclose(fp);
			return;
		}

		/* Load choices */
		while (fgets(buffer, sizeof(buffer), fp))
		{
			t_ = strtok(buffer, " ");	/* Term number (or keyword, "Gamma", etc.) */
			o_ = strtok(NULL, "\n");	/* argument string */
			if (!o_)
			{
				o_ = "";
			}					/* missing (or null) argument? */
			if (t_)
			{
				if (!strcmp(t_, "Gamma"))
					gamma = atof(o_);
				else if (!strcmp(t_, "Monochrome"))
					force_mono = !strcmp(o_, "on");
				else if (!strcmp(t_, "Sound"))
					enable_sound = !strcmp(o_, "on");
				else if (!strcmp(t_, "Volume"))
					sound_volume = atoi(o_);
				else if (!strcmp(t_, "FullScreen"))
					start_fullscreen = !strcmp(o_, "on");
				else if (!strcmp(t_, "Hourglass"))
					use_glass = !strcmp(o_, "on");
				else if (!strcmp(t_, "HackFlush"))
					hack_flush = !strcmp(o_, "on");
				else if (!strcmp(t_, "AlarmTimeH"))
					alarm_h = atoi(o_);
				else if (!strcmp(t_, "AlarmTimeM"))
					alarm_m = atoi(o_);
				else if (!strcmp(t_, "AlarmText"))
					strcpy(alarm_message, o_);
				else if (!strcmp(t_, "AlarmBeep"))
					alarm_beep = !strcmp(o_, "on");
				else if (!strcmp(t_, "AlarmType"))
				{
					int i;
					for (i = 0; i < 4; i++)
						if (!strcmp(alarm_types[i], o_))
							alarm_type = i;
				}
				else if (isdigit((unsigned char)*t_))
				{
					int t = atoi(t_);
					if (t >= 0 && t < MAX_TERM_DATA)
					{
						char *f_, *x0_, *y0_, *x1_, *y1_, *sx_, *sy_;
						o_ = strtok(o_, " ");	/* first word */
						f_ = strtok(NULL, " ");	/* font name */
						x0_ = strtok(NULL, " ");	/* x posn (min) */
						y0_ = strtok(NULL, " ");	/* y posn (min) */
						x1_ = strtok(NULL, " ");	/* x posn (max) */
						y1_ = strtok(NULL, " ");	/* y posn (max) */
						sx_ = strtok(NULL, " ");	/* x scroll offset */
						sy_ = strtok(NULL, "\n");	/* y scroll offset */
						data[t].def_open = (t == 0) || atoi(o_);
						data[t].def_pos.min.x = atoi(x0_);
						data[t].def_pos.min.y = atoi(y0_);
						data[t].def_pos.max.x = atoi(x1_);
						data[t].def_pos.max.y = atoi(y1_);
						data[t].def_scroll.x = atoi(sx_);
						data[t].def_scroll.y = atoi(sy_);
						data[t].unopened = 1;	/* ie. force def_pos */
#ifndef FULLSCREEN_ONLY
						attach_font_to_term(&(data[t]), f_);
#endif /* FULLSCREEN_ONLY */						
					}
				}
			}
		}
		fclose(fp);
	}

#ifndef FULLSCREEN_ONLY
	/*
	   | Fudge so that the main term is *always* fullsize
	 */
	{
		int fw, fh;

		set_up_zrb(&(data[0]));
		fw = zrb.r_charw << screen_eig.x;
		fh = zrb.r_charh << screen_eig.y;
		if (zrb.r_flags.bits.double_height)
		{
			fh *= 2;
		}
		fw *= 80;
		fh *= 24;
		data[0].def_pos.max.x = data[0].def_pos.min.x + fw;
		data[0].def_pos.max.y = data[0].def_pos.min.y + fh;
		data[0].def_scroll.x = 0;
		data[0].def_scroll.y = 0;
	}
#endif /* FULLSCREEN_ONLY */


}




static void save_choices(void)
{
	FILE *fp = NULL;
	FILE *fpm = NULL;
	char *cf;
	int i;

	write_alarm_choices();

	cf = find_choices(TRUE);
	if (!*cf)
	{
		plog("Failed to locate writable choices file!");
		return;
	}

	fp = fopen(cf, "w");
	if (!fp)
	{
		plog("Can't write choices file");
		return;
	}

	fpm = fopen(find_choices_mirror(), "w");

	f2printf(fp, fpm, "[Angband config, Musus' port]\n");
	f2printf(fp, fpm, "Gamma %.2lf\n", gamma);
	f2printf(fp, fpm, "Monochrome %s\n", force_mono ? "on" : "off");
	f2printf(fp, fpm, "Sound %s\n", enable_sound ? "on" : "off");
	f2printf(fp, fpm, "Volume %d\n", sound_volume);
	f2printf(fp, fpm, "FullScreen %s\n", start_fullscreen ? "on" : "off");
	f2printf(fp, fpm, "Hourglass %s\n", use_glass ? "on" : "off");
	f2printf(fp, fpm, "HackFlush %s\n", hack_flush ? "on" : "off");

	for (i = 0; i < MAX_TERM_DATA; i++)
	{
		window_state ws;
		Wimp_GetWindowState(data[i].w, &ws);
		f2printf(fp, fpm, "%d %d %s ", i, ws.flags.data.open,
				 data[i].font->name);
		f2printf(fp, fpm, "%d ", ws.openblock.screenrect.min.x);
		f2printf(fp, fpm, "%d ", ws.openblock.screenrect.min.y);
		f2printf(fp, fpm, "%d ", ws.openblock.screenrect.max.x);
		f2printf(fp, fpm, "%d ", ws.openblock.screenrect.max.y);
		f2printf(fp, fpm, "%d %d\n", ws.openblock.scroll.x,
				 ws.openblock.scroll.y);
	}

	fclose(fp);

	if (fpm)
	{
		fclose(fpm);
	}
}

/*
 | Update the Alarm choices file to reflect changed alarm settings.
 */
static void write_alarm_choices(void)
{
	FILE *fp;
	char *cf;

	/* Open the choices file for reading */
	cf = find_alarmfile(TRUE);
	if (!*cf)
	{
		plog("Can't determine Alarm file location!");
		return;
	}

	fp = fopen(cf, "w");
	if (!fp)
	{
		plog("Can't write Alarm file");
		return;
	}

	/* Write the new alarm options */
	fprintf(fp, "AlarmType %s\n", alarm_types[alarm_type]);
	fprintf(fp, "AlarmTimeH %d\n", alarm_h);
	fprintf(fp, "AlarmTimeM %d\n", alarm_m);
	fprintf(fp, "AlarmText %s\n", alarm_message);
	fprintf(fp, "AlarmBeep %s\n", alarm_beep ? "on" : "off");

	fclose(fp);
}

/*
 | Read the Alarm choices file.
 */
static void read_alarm_choices(void)
{
	char buffer[260];
	FILE *fp;
	char *cf;

	cf = find_alarmfile(FALSE);
	if (!*cf)
	{
		return;
	}

	fp = fopen(cf, "r");
	if (fp)
	{
		const char *t_, *o_;
		/* Load choices */
		while (fgets(buffer, sizeof(buffer), fp))
		{
			t_ = strtok(buffer, " ");	/* Keyword */
			o_ = strtok(NULL, "\n");	/* argument string */
			if (!o_)
			{
				o_ = "";
			}					/* missing (or null) argument? */
			if (t_)
			{
				if (!strcmp(t_, "AlarmTimeH"))
					alarm_h = atoi(o_);
				else if (!strcmp(t_, "AlarmTimeM"))
					alarm_m = atoi(o_);
				else if (!strcmp(t_, "AlarmText"))
					strcpy(alarm_message, o_);
				else if (!strcmp(t_, "AlarmBeep"))
					alarm_beep = !strcmp(o_, "on");
				else if (!strcmp(t_, "AlarmType"))
				{
					int i;
					for (i = 0; i < 4; i++)
						if (!strcmp(alarm_types[i], o_))
							alarm_type = i;
				}
			}
		}
		fclose(fp);
	}
}



#ifndef FULLSCREEN_ONLY
/*
 | Handle selections from the term menu(s)
 */
static BOOL Hnd_TermMenu(event_pollblock * pb, void *ref)
{
	mouse_block mb;
	int i;

	Wimp_GetPointerInfo(&mb);

	switch (pb->data.selection[0])
	{
		case TERM_MENU_INFO:	/* Info> */
			break;
		case TERM_MENU_FONT:	/* Font> */
			/* Sub item selected? */
			if (pb->data.selection[1] == -1)
			{
				break;
			}
			handle_font_selection(pb->data.selection + 1);
			break;
		case TERM_MENU_WINDOWS:	/* Windows> */
			if (pb->data.selection[1] == -1)
			{
				break;
			}
			i = pb->data.selection[1];
			{
				window_state ws;
				Wimp_GetWindowState(data[i].w, &ws);
				if (ws.flags.data.open)
				{
					if (!i)
						Hnd_MainClose(NULL, (void *)TRUE);
					else
						Window_Hide(data[i].w);
				}
				else
				{
					if (!i)
					{
						show_windows();
						grab_caret();
					}
					else
					{
						if (!data[i].unopened)
						{
							Window_Show(data[i].w, open_WHEREVER);
						}
						else
						{
							window_openblock ob;
							ob.window = data[i].w;
							ob.screenrect = data[i].def_pos;
							ob.scroll = data[i].def_scroll;
							ob.behind = -1;	/* could use data[0].w; ? */
							Wimp_OpenWindow(&ob);
							data[i].unopened = 0;
						}
					}
				}
			}
			break;
	}

	if (mb.button.data.adjust)
	{
		set_up_term_menu(menu_term);
		Menu_ShowLast();
	}

	return TRUE;
}






/*
 | Handle selections from the iconbar menu
 */
static BOOL Hnd_IbarMenu(event_pollblock * pb, void *ref)
{
	mouse_block mb;
	Wimp_GetPointerInfo(&mb);

	switch (pb->data.selection[0])
	{
		case IBAR_MENU_INFO:	/* Info> */
			break;
		case IBAR_MENU_FULLSCREEN:	/* Full screen */
			/* Do Full Screen mode */
			enter_fullscreen_mode();
			break;
		case IBAR_MENU_GAMMA:	/* Gamma correction */
			break;
		case IBAR_MENU_SOUND:	/* Sound */
			/*
			   | enable_sound = !enable_sound;
			   | if ( enable_sound ) { initialise_sound(); }
			   | Menu_SetFlags( ibar_menu, IBAR_MENU_SOUND, enable_sound, 0 );
			   | set_sound_window_state();
			 */
			break;
		case IBAR_MENU_WINDOWS:	/* Windows> */
			/*
			   | Hack: pass it off as the equivalent selection from
			   | the term menu.
			 */
			pb->data.selection[0] = TERM_MENU_WINDOWS;
			return Hnd_TermMenu(pb, ref);
			break;
		case IBAR_MENU_SAVECHOICES:	/* Save choices */
			save_choices();
			break;
		case IBAR_MENU_QUIT:	/* Quit */
			if (game_in_progress && character_generated)
				save_player();
			quit(NULL);
			break;
	}

	if (mb.button.data.adjust)
		Menu_ShowLast();

	return TRUE;
}

 
 

/*
 * Handler for NULL events (should this check the alarm in the desktop?
 */
static BOOL Hnd_null(event_pollblock *event, void *ref)
{
	/* Really no need to check the alarm more than once per second. */
	if (alarm_type && Time_Monotonic() > alarm_lastcheck + 100)
	{
		check_alarm();
	}

	return TRUE;
}

 
 
 




static BOOL Hnd_MenuSel(event_pollblock * pb, void *ref)
{
	if (menu_currentopen == ibar_menu)
		return Hnd_IbarMenu(pb, ref);
	else if (menu_currentopen == term_menu)
		return Hnd_TermMenu(pb, ref);
	return FALSE;
}


static BOOL Hnd_IbarClick(event_pollblock * pb, void *ref)
{
	if (pb->data.mouse.button.data.menu)
	{
		set_gamma_window_state();
		set_sound_window_state();

		/* Hack: shade the Save> option if appropriate */
		if (game_in_progress && character_generated)
			Menu_SetFlags(ibar_menu, IBAR_MENU_SAVE, 0, PDEADCHK);
		else
			Menu_SetFlags(ibar_menu, IBAR_MENU_SAVE, 0, TRUE);

		/*
		   | Hack: set up the Term menu as if it was opened over the main
		   | window (so that the Windows> submenu is set correctly)
		 */
		menu_term = (term_data *)&data[0];
		set_up_term_menu(menu_term);

		Menu_Show(ibar_menu, pb->data.mouse.pos.x, -1);
		return TRUE;
	}

	if (pb->data.mouse.button.data.select)
	{
		show_windows();
		grab_caret();
		return TRUE;
	}

	if (pb->data.mouse.button.data.adjust)
	{
		enter_fullscreen_mode();
		return TRUE;
	}

	return FALSE;
}






/*
 | Handler for PreQuit messages (eg. at shutdown).
 */
static BOOL Hnd_PreQuit(event_pollblock * b, void *ref)
{
	BOOL shutdown = (b->data.message.data.words[0] & 1) == 0;
	task_handle originator = b->data.message.header.sender;
	unsigned int quitref;
	message_block mb;
	char buffer1[64];
	os_error e;
	int ok;

	if (!(game_in_progress && character_generated))
		return TRUE;			/* ignore, we're OK to die */

	/* Stop the shutdown/quit */
	memcpy(&mb, &(b->data.message), 24);
	quitref = mb.header.yourref;
	mb.header.yourref = mb.header.myref;
	Wimp_SendMessage(event_ACK, &mb, originator, 0);

	/*
	   | We handle this differently depending on the version of the Wimp;
	   | newer versions give us much more flexibility.
	 */
	if (event_wimpversion < 350)
	{
		/*
		   | Older versions - use 'OK' and 'Cancel'.
		   | There is no "Save & Quit" button.
		 */
		Msgs_Lookup("err.shuttitl:Query from %s", e.errmess, 64);
		sprintf(buffer1, e.errmess, VARIANT);
		Msgs_Lookup("err.shutdown:Unsaved game; are you sure you want to quit?",
					e.errmess, 260);
		e.errnum = 0;
		SWI(3, 2, SWI_Wimp_ReportError, &e, 3 | 16, buffer1, NULL, &ok);

		if (ok != 1)
			return TRUE;		/* no! Pleeeeeease don't kill leeeeddle ol' me! */
	}
	else
	{
		/*
		   | Newer version: can add buttons to the dialog.
		   | we add a 'Save and Quit' button to allow the shutdown to
		   | continue /after/ saving.
		 */
		int flags;
		char buttons[64];

		Msgs_Lookup("err.shutbuts:Save & quit,Don't quit,Quit anyway",
					buttons, 64);
		Msgs_Lookup("err.shuttitl:Query from %s", e.errmess, 64);
		sprintf(buffer1, e.errmess, VARIANT);
		Msgs_Lookup("err.shutdown:Unsaved game; are you sure you want to quit?",
					e.errmess, 260);
		e.errnum = 0;

		flags = 0 | 16 | 256 | (4 << 9);

		SWI(6, 2, SWI_Wimp_ReportError, &e, flags, buffer1, ICONNAME, 0,
			buttons, NULL, &ok);

		if (ok == 4)
			return TRUE;		/* no! Pleeeeeease don't kill leeeeddle ol' me! */

		if (ok == 3)
			save_player();		/* Save & Quit */
	}


	/* RO2 doesn't use the shudown flag */
	if (shutdown && event_wimpversion >= 300 && mb.header.size >= 24)
	{
		key_block kb;
		kb.code = 0x1fc;		/* restart shutdown sequence */
		Wimp_SendMessage(event_KEY, (message_block *) & kb, originator, 0);
	}

	/* "Time... to die." */
	Event_CloseDown();
	exit(0);
	return TRUE;				/* The one great certainty (sic) */
}

#endif /* FULLSCREEN_ONLY */




static void initialise_terms(void)
{
	char t[80];
	int i;

#ifndef FULLSCREEN_ONLY
	if (!minimise_memory)
	{
		/* Create a window for each term.  Term 0 is special (no scroll bars) */
		data[0].w = Window_Create("angband", sizeof(angband_term_name[0]));
		data[0].font = SYSTEM_FONT;
		data[0].def_open = 1;
		data[0].unopened = 1;
		sprintf(t, "%s %s", VARIANT, VERSION);
		Window_SetTitle(data[0].w, t);
		strncpy(data[0].name, VARIANT, 12);
		Event_Claim(event_KEY, data[0].w, event_ANY, Hnd_Keypress,
					(void *)&(data[0]));
		Event_Claim(event_REDRAW, data[0].w, event_ANY, Hnd_Redraw,
					(void *)&(data[0]));
		Event_Claim(event_CLICK, data[0].w, event_ANY, Hnd_TermClick,
					(void *)&(data[0]));
		Event_Claim(event_CLOSE, data[0].w, event_ANY, Hnd_MainClose, NULL);
	
		for (i = 1; i < MAX_TERM_DATA; i++)
		{
			data[i].w = Window_Create("term", template_TITLEMIN);
			data[i].font = SYSTEM_FONT;
			data[i].def_open = 0;
			data[i].unopened = 1;
#ifndef OLD_TERM_MENU
			sprintf(t, "%s (%s %s)", angband_term_name[i], VARIANT, VERSION);
#else
			sprintf(t, "Term-%d (%s %s)", i, VARIANT, VERSION);
#endif
			Window_SetTitle(data[i].w, t);
			strncpy(data[i].name, t, 12);
			Event_Claim(event_CLICK, data[i].w, event_ANY, Hnd_TermClick,
						(void *)&(data[i]));
			Event_Claim(event_REDRAW, data[i].w, event_ANY, Hnd_Redraw,
						(void *)&(data[i]));
		}
	}
#endif /* FULLSCREEN_ONLY */

	term_data_link(&(data[0]), 256);

	for (i = 1; i < MAX_TERM_DATA; i++)
	{
		term_data_link(&(data[i]), 16);
		angband_term[i] = &(data[i].t);
	}

	angband_term[0] = &(data[0].t);
	Term_activate(&(data[0].t));
}




/*
 | Hack(ish) - determine the version of RISC OS
 */
static int os_version(void)
{
	int osv;
	SWI(3, 2, SWI_OS_Byte, 129, 0, 255, NULL, &osv);
	switch (osv)
	{
		case 0xa0: return 120;
		case 0xa1: return 200;
		case 0xa2: return 201;
		case 0xa3: return 300;
		case 0xa4: return 310;
		case 0xa5: return 350;
		case 0xa6: return 360;
		case 0xa7: return 370;
		case 0xa8: return 400;
		default: return 370;
	}
	return -1;					/* -sigh- */
}

#ifndef FULLSCREEN_ONLY
/*
 | Determine whether the current screen mode is "high-res"
 | (ie. should we use the "Sprites22" or the "Sprites" file.
 */
static int sprites22(void)
{
	int yeig;

	OS_ReadModeVariable(-1, modevar_YEIGFACTOR, &yeig);

	return yeig < 2;
}

/*
 | Determine whether we should use 2D or 3D templates.
 | 2D templates *must* be used under Wimp <3.00, but under RO3 we
 | use the CMOS settings to decide.
 */
static int templates2d(void)
{
	int r1, r2;

	if (event_wimpversion < 300)
		return TRUE;

	/* The 3D bit is bit 0 of byte 140 */
	OS_Byte(osbyte_READCMOSRAM, 140, 0, &r1, &r2);

	return !(r2 && 1);
}
#endif /* FULLSCREEN_ONLY */



static unsigned int htoi(char *s)
{
	static const char hex[] = "0123456789ABCDEF";
	unsigned int v = 0;
	while (*s)
	{
		char *m;
		int d = toupper((unsigned char)*s++);
		m = strchr(hex, d);
		if (!m)
		{
			return v;
		}
		v = (v << 4) + (m - hex);
	}
	return v;
}

static int read_unsigned(char *t)
{
	int r;
	if (SWI(2, 3, SWI_OS_ReadUnsigned, 2, t, NULL, NULL, &r))
		r = 0;
	return r;
}

/*
 | Scan the string at 'n', replacing dodgy characters with underbars
 */
static void sanitise_name(char *n)
{
	for (; *n; n++)
	{
		if (strchr("\"$%^&*\\\'@#.,", *n))
			*n = '_';
	}
}


/*
 | Ensure that the path to a given object exists.
 | Ie. if |p| = "a.b.c.d" then we attempt to
 | create directories a, a.b and a.b.c if they don't
 | already exist.
 | Note that 'd' may be absent.
 */
static int ensure_path(char *p)
{
	char tmp[260];
	char *l = tmp;

	while (*p)
	{
		if (*p == '.')
		{
			*l = 0;
			if (SWI(5, 0, SWI_OS_File, 8, tmp, 0, 0, 77))
				return 0;		/* Eeek! */
		}
		*l++ = *p++;
	}

	return 1;
}


/*
 * Set up the Scrap, Choices and Alarm paths, trying for
 * Choices:blah...,etc.  by preference, but falling back on lib/xtra
 * if need be.
 */
static void init_paths(void)
{
	char tmp[512];
	char subpath[128];
	char *v;
	char *t;

	/* Form the sub-path we use for both Choices and Scrap dirs: */
	v = subpath + sprintf(subpath, "%s", VARIANT);
	sanitise_name(subpath);
	sprintf(v, ".%s", VERSION);
	sanitise_name(v + 1);

	/* Do the Scrap path first: */
	*scrap_path = 0;

	/* Try for Wimp$ScrapDir... */
	t = getenv("Wimp$ScrapDir");
	if (t && *t)
	{
		sprintf(tmp, "%s.AngbandEtc.%s.", t, subpath);
		if (ensure_path(tmp))
		{
			strcpy(scrap_path, tmp);
		}
	}

	/* Couldn't use Wimp$ScrapDir, so fall back on lib.xtra.scrap */
	if (!*scrap_path)
	{
		sprintf(tmp, "%sxtra.scrap.", resource_path);
		if (ensure_path(tmp))
		{
			strcpy(scrap_path, tmp);
		}
	}

	/* Now set up the Choices and Alarm files: */

	/* Read only Choices file is always lib.xtra.Choices */
	sprintf(choices_file[CHFILE_READ], "%sXtra.Choices", resource_path);
	/* Default writable Choices file is the same */
	strcpy(choices_file[CHFILE_WRITE], choices_file[CHFILE_READ]);
	/* No default mirror Choices file */
	strcpy(choices_file[CHFILE_MIRROR], "");

	/* Read only Alarm file is always  lib.xtra.Alarm */
	sprintf(alarm_file[CHFILE_READ], "%sXtra.Alarm", resource_path);
	/* Default writable Alarm file is the same */
	strcpy(alarm_file[CHFILE_WRITE], alarm_file[CHFILE_READ]);

	/* Try to use Choices$Path, etc. for the others... */

	t = getenv("Choices$Write");	/* Ie. where choices should be written */
	if (t && *t)
	{
		/* Choices file: */
		sprintf(tmp, "%s.AngbandEtc.%s", t, subpath);
		if (ensure_path(tmp))
		{
			/* Use for writable file: */
			strcpy(choices_file[CHFILE_WRITE], tmp);
			/* Form 'mirror' filename: same path but with a fixed leafname */
			strcpy(v + 1, "Default");
			sprintf(tmp, "%s.AngbandEtc.%s", t, subpath);
			strcpy(choices_file[CHFILE_MIRROR], tmp);
		}

		/* Alarm file (doesn't involve subpath) */
		sprintf(tmp, "%s.AngbandEtc.Global.Alarm", t);
		if (ensure_path(tmp))
		{
			/* Use for read/writable file */
			strcpy(alarm_file[CHFILE_WRITE], tmp);
		}
	}
}







/*
 * Return the appropriate (full) pathname.
 *
 * For write ops, the read/write file is returned.
 *
 * For read ops, either the read/write file, the mirror file,
 * or the read only file will be returned as appropriate.
 */
static char *find_choices(int write)
{
	if (write)
		return choices_file[CHFILE_WRITE];

	if (myFile_Size(choices_file[CHFILE_WRITE]) > 0)
		return choices_file[CHFILE_WRITE];

	if (myFile_Size(choices_file[CHFILE_MIRROR]) > 0)
		return choices_file[CHFILE_MIRROR];

	return choices_file[CHFILE_READ];
}

static char *find_choices_mirror(void)
{
	return choices_file[CHFILE_MIRROR];
}

static char *find_alarmfile(int write)
{
	if (write)
		return alarm_file[CHFILE_WRITE];

	if (myFile_Size(alarm_file[CHFILE_WRITE]) > 0)
		return alarm_file[CHFILE_WRITE];

	return alarm_file[CHFILE_READ];
}




int main(int argc, char *argv[])
{
	int i, j;
	int start_full = 0;
	char *arg_savefile = 0;
	char *t;
#ifdef USE_DA
	int da_font = 1, da_game = 1;
#endif

	atexit(final_acn);			/* "I never did care about the little things." */

	Start_Hourglass;

	/* Parse arguments */
	for (i = 1; i < argc; i++)
	{
		if (argv[i][0] == '-')
		{
			switch (tolower((unsigned char)argv[i][1]))
			{
				case 'm':
				{
					/* Minimise Memory */
					minimise_memory = 1;

					/* Break */
					break;
				}
				case 'c':		/* -c[a][s][f][<n>] */
					for (j = 2; argv[i][j]; j++)
					{
						int on = isupper((unsigned char)argv[i][j]);

						switch (tolower((unsigned char)argv[i][j]))
						{
#ifdef ABBR_FILECACHE
							case 'a': abbr_filecache =
									on;
								break;
							case 'f': abbr_tmpfile =
									on;
								break;
#endif
#ifdef SMART_FILECACHE
							case 's': smart_filecache =
									on;
								break;
#endif

							case 'p': flush_scrap =
									!on;
								break;

							default:
								if (isdigit((unsigned char)argv[i][j]))
								{
									max_file_cache_size =
										atoi(argv[i] + j) << 10;
									while (isdigit((unsigned char)argv[i][++j]))
										;
									if (max_file_cache_size <= 0)
									{
										use_filecache = 0;
									}
									j--;
								}
								else
								{
									fprintf(stderr, "Unrecognised option: -c%s",
											argv[i] + j);
									exit(EXIT_FAILURE);
								}
						}
					}
					break;
				case 'w':		/* -waitrelease */
					hack_flush = 1;
					break;
				case 'e':		/* -Evil */
					allow_iclear_hack = 1;
					break;
				case 's':		/* -s<savefile> */
					if (argv[i][2])
						arg_savefile = argv[i] + 2;
					break;
				case 'f':		/* -fullscreen */
					start_full = 1;
					break;
				case 'h':		/* -hourglass */
					use_glass = 1;
					break;
				case 't':		/* -T<filetype> */
					if (argv[i][2])
						vfiletype = htoi(argv[i] + 2);
					break;
#ifdef USE_DA
				case 'd':		/* -df, -dg, -dc or -d : disable DAs */
					switch (tolower((unsigned char)argv[i][2]))
					{
						case 0:	/* -d => disable both */
							da_font = da_game = 0;
							break;
						case 'f':	/* -df => disable font only */
							da_font = 0;
							break;
						case 'g':	/* -dg => disable game only */
							da_game = 0;
							break;
					}
					break;
#endif
				case '%':		/* -%<debug_opts> */
				{
					int v = read_unsigned(argv[i] + 2);
					log_g_malloc = v & 1;
					show_sound_alloc = v & 2;
				}
					break;
				default:
					fprintf(stderr, "Unrecognised option: %s", argv[i]);
					exit(EXIT_FAILURE);
			}
		}
	}
          
	/* 1.27 - new handling of -minimise-memory: */
#ifndef FULLSCREEN_ONLY
	if (minimise_memory)
#endif /* FULLSCREEN_ONLY */
	{
		start_full = 1;
		fs_quit_key_text = "(fullscreen only mode)";
	}
#ifdef USE_DA
	init_memory(da_font, da_game);	/* Set up dynamic areas, etc. if possible */

	/* Install memory allocation hooks */
	ralloc_aux = g_malloc;
	rnfree_aux = g_free;
#endif

	/* Install replacement error reporting routines */
	quit_aux = quit_hook;
	plog_aux = plog_hook;
	core_aux = core_hook;

	/* Expand the (Angband) resource path */
	t = getenv(RISCOS_VARIANT "$Path");
	if (!t || !*t) Msgs_ReportFatal(0, "A resources path could not be formed.");
	strcpy(resource_path, t);

	/* Decide where scrap, choices and alarm files live: */
	init_paths();

	/* Hack: if no savefile specified, use a default */
	if (!arg_savefile)
	{
		arg_savefile = malloc(strlen(resource_path) + 32);
		if (!arg_savefile)
		{
			Msgs_ReportFatal(0, "err.mem");
		}
		sprintf(arg_savefile, "%s%s", resource_path, ">Save.Savefile");
	}

	/* This crap appears here so that plog() will work properly before
	   init_acn() is called... */
	Resource_Initialise(RISCOS_VARIANT);
	Msgs_LoadFile("Messages");

#ifndef FULLSCREEN_ONLY
	if (!minimise_memory)
	{
		/* This is a hack to only call Event_Initialise3 under RO3 */
		if (os_version() < 300)
			Event_Initialise(RISCOS_VARIANT);
		else
			Event_Initialise3(RISCOS_VARIANT, 300, message_list);
	
		EventMsg_Initialise();
	
		/*
		   | This is a possible workaround for the FP regs getting
		   | bolloxed in the ! menu because the compiler sets them
		   | up before a call to Wimp_Poll if CSE optimisation is on.
		   | At the moment I've just turned off CSE for the function
		   | affected.
		   |
		   | event_mask.data.keepfpregisters = 1;
		 */
	
		/* Load Templates */
		Template_Initialise();
		if (templates2d())
			Template_LoadFile("Templates2");
		else
			Template_LoadFile("Templates");
	
		/* Load Sprites */
		if (sprites22())
			resource_sprites =
				Sprite_LoadFile("<" RISCOS_VARIANT "$Dir>.Sprites22");
		else
			resource_sprites = Sprite_LoadFile("<" RISCOS_VARIANT "$Dir>.Sprites");
	}
#endif /* FULLSCREEN_ONLY */

	Screen_CacheModeInfo();

	/* Initialise some ZapRedraw stuff */
	initialise_palette();
	initialise_fonts();			/* Set up the fonts */
#ifndef FULLSCREEN_ONLY
	initialise_r_data();		/* Set up the r_data buffer */
    
	if (!minimise_memory)
	{
		/* Initialise some Wimp specific stuff */
		init_gamma_window();
		init_sound_window();
		init_save_window();
		init_menus();
		ibar_icon = Icon_BarIcon(ICONNAME, iconbar_RIGHT);
	
		/* Global handlers */
		Event_Claim(event_OPEN, event_ANY, event_ANY, Handler_OpenWindow, NULL);
		Event_Claim(event_CLOSE, event_ANY, event_ANY, Handler_CloseWindow, NULL);
		Event_Claim(event_GAINCARET, event_ANY, event_ANY, Hnd_Caret, (void *)1);
		Event_Claim(event_LOSECARET, event_ANY, event_ANY, Hnd_Caret, (void *)0);
		Event_Claim(event_MENU, event_ANY, event_ANY, Hnd_MenuSel, NULL);
		Event_Claim(event_CLICK, window_ICONBAR, ibar_icon, Hnd_IbarClick, NULL);
		Event_Claim(event_CLICK, event_ANY, event_ANY, Hnd_Click, NULL);
		EventMsg_Claim(message_PALETTECHANGE, event_ANY, Hnd_PaletteChange, NULL);
		EventMsg_Claim(message_MODECHANGE, event_ANY, Hnd_ModeChange, NULL);
		EventMsg_Claim(message_PREQUIT, event_ANY, Hnd_PreQuit, NULL);
	
		/* Initialise the sound stuff */
		initialise_sound();
	}
#endif /* FULLSCREEN_ONLY */

	/* Initialise some Angband stuff */
	initialise_terms();
	load_choices();
	read_alarm_choices();
	init_file_paths(unixify_name(resource_path));

	Start_Hourglass;			/* Paranoia */

	/* Hack - override the saved options if -F was on the command line */
	start_fullscreen |= start_full;

	/* hack so that the cursor is yellow if undefined */
	if (palette[CURSOR_COLOUR] == palette[0])
	{
		angband_color_table[CURSOR_COLOUR][1] = (CURSOR_RGB & 0xff00) >> 8;
		angband_color_table[CURSOR_COLOUR][2] = (CURSOR_RGB & 0xff0000) >> 16;
		angband_color_table[CURSOR_COLOUR][3] = (CURSOR_RGB & 0xff000000) >> 24;
	}

	/* Catch nasty signals */
	signals_init();
	/* use pref-acn.prf */
	ANGBAND_SYS = "acn";

#ifndef FULLSCREEN_ONLY
	if (start_fullscreen)
	{
#endif /* FULLSCREEN_ONLY */
		enter_fullscreen_mode();   
#ifndef FULLSCREEN_ONLY
	}
	else
	{
		Start_Hourglass;		/* Paranoia */
		Hnd_ModeChange(NULL, NULL);	/* Caches the various fonts/palettes */
		show_windows();
		grab_caret();

		Event_Claim(event_NULL, event_ANY, event_ANY, Hnd_null, NULL);

		/* Wait for a null poll so that the windows can appear */
		do
		{
			Event_Poll();
		}
		while (event_lastevent.type != 0);
	}
#endif /* FULLSCREEN_ONLY */

	/* Initialise Angband */
	Start_Hourglass;			/* Paranoia */

	strncpy(savefile, unixify_name(arg_savefile), sizeof(savefile));
	savefile[sizeof(savefile) - 1] = '\0';

	use_sound = 1;
	init_angband();
	initialised = 1;
	game_in_progress = 1;
/*	pause_line(23);*/
	flush();
	/* Stop_Hourglass; */
	play_game(FALSE);

	if (fullscreen_mode) leave_fullscreen_mode();
    
	Stop_Hourglass;

	quit(NULL);

	return 0;

	debug("to stop the 'unused' warning :)");
}













/*--------------------------------------------------------------------------*/
/* Stuff below here is for the full screen display							*/
/*--------------------------------------------------------------------------*/

static errr Term_xtra_acn_checkFS(void);
static errr Term_xtra_acn_eventFS(void);
static errr Term_xtra_acn_reactFS(int force);
static errr Term_curs_acnFS(int x, int y);
static errr Term_xtra_acn_clearFS(void);
static errr Term_xtra_acnFS(int n, int v);
static errr Term_wipe_acnFS(int x, int y, int n);
static errr Term_text_acnFS(int x, int y, int n, byte a, cptr s);
static void bored(void);
static void redraw_areaFS(int x, int y, int w, int h);
static void draw_cursor(int x, int y);

/*
 | We use this to keep the mouse in the same place on return from full-screen
 | mode.
 */
static wimp_point old_mouse_posn;

/*
 | Take a copy of the current mode descriptor/number and return either
 | a pointer to it (as an int) if it's a new mode, or the mode number.
 | NB: A static pointer is used and the descriptor returned is only
 | valid until the next call to this function.
 |
 | Basically, a replacement for OS_Byte 135 / OS_ScreenMode1, IYSWIM.
 */
static int current_mode(void)
{
	static void *descriptor = NULL;
	int mode;
	int size;
	int i;
	int *vals;

	if (descriptor)
	{
		free(descriptor);
		descriptor = NULL;
	}

	SWI(1, 3, SWI_OS_Byte, 135, NULL, NULL, &mode);
	if (mode < 256)
	{
		return mode;
	}

	vals = (int *)(mode + 20);
	for (i = 0; vals[i] != -1; i += 2)
		;

	size = 24 + 8 * i;			/* Size of data */
	descriptor = malloc(size);
	if (!descriptor)
	{
		core("Out of memory!");
	}
	memcpy(descriptor, (void *)mode, size);

	return (int)descriptor;
}



/*
 | Select the best mode we can for full screen.
 | Returns 12 for (low-res, ie. mode 12) or 27 for high-res,
 | or a pointer to a mode descriptor (as an int).
 */
static int select_fullscreen_mode(void)
{
	static struct
	{
		int flags, x, y, l2bpp, hz, term;
	}
	desc;
	int mode = 0;

	desc.flags = 1;				/* format 0 */
	desc.x = 640;
	desc.y = 480;				/* 640x480 */
	desc.l2bpp = 2;				/* 16 colours */
	desc.hz = -1;				/* best we can get */
	desc.term = -1;	/* don't fuss about modevars */

	SWI(1, 1, SWI_OS_CheckModeValid, &desc, &mode);
	if (mode != (int)&desc)
	{
		SWI(1, 1, SWI_OS_CheckModeValid, 27, /**/ &mode);
		if (mode != 27)
		{
			SWI(1, 1, SWI_OS_CheckModeValid, 12, /**/ &mode);
			if (mode != 12)
			{
				mode = 0;
			}
		}
	}

	return mode;
}


/*
 | Change screen mode
 */
static void change_screenmode(int to)
{
	if (SWI(2, 0, SWI_OS_ScreenMode, 0, to))
	{
		if (to < 256)
		{
			GFX_VDU(22);
			GFX_VDU(to);
		}
		else
		{
			/* Finished with my woman / cos she couldn't help me with ... */
			core("Eeek! mode isn't valid, but it /should/ be...");
		}
	}
}


/*
 | Constrain the mouse pointer to a point - this means that the damn
 | hourglass won't move around with the mouse :)
 */
static void constrain_pointer(void)
{
	mouse_block ptr;
	wimp_rect r;
	int ys = screen_eig.y == 1 ? 32 : 64;	/* Cope with dbl height glass */

	Screen_CacheModeInfo();		/* Make sure we know the screen size */
	r.min.x = r.max.x = screen_size.x - 32;
	r.min.y = r.max.y = screen_size.y - ys; 

	/* Retrieve and store old (wimp) pointer position */
	Wimp_GetPointerInfo(&ptr);
	old_mouse_posn = ptr.pos;

	Pointer_RestrictToRect(r);

	/* Turn the pointer off also */
	SWI(2, 0, SWI_OS_Byte, 106, 0);
}

static void release_pointer()
{             
	wimp_rect r;

	r.min.x = r.max.x = old_mouse_posn.x;
	r.min.y = r.max.y = old_mouse_posn.y;

	Pointer_RestrictToRect(r);
	
	Pointer_Unrestrict();
	 
	/* Turn the pointer back on also */
	SWI(2, 0, SWI_OS_Byte, 106, 1);
}
 
 



/*
 | Convert a 1bpp bitmap into a 4bpp bitmap (bit flipped)
 */
static int byte_to_word_flipped(int b)
{
	int w;
	if (b & 128)
	{
		w = 0xf0000000;
	}
	else
	{
		w = 0;
	}
	if (b & 64)
	{
		w |= 0x0f000000;
	}
	if (b & 32)
	{
		w |= 0x00f00000;
	}
	if (b & 16)
	{
		w |= 0x000f0000;
	}
	if (b & 8)
	{
		w |= 0x0000f000;
	}
	if (b & 4)
	{
		w |= 0x00000f00;
	}
	if (b & 2)
	{
		w |= 0x000000f0;
	}
	if (b & 1)
	{
		w |= 0x0000000f;
	}
	return w;
}




/*
 | try to load the fallback fullscreen font and convert it to 4bpp
 */
static int cache_zapfontHR(void)
{
	int handle;
	unsigned int extent;
	char buffer[260];
	struct
	{
		char id[8];
		int w, h, f, l, r1, r2;
	}
	zfh;
	int *op;
	char *ip;
	int l, i;

	/* Try to open the file */
	sprintf(buffer, "%s%s", resource_path, "xtra.FullScreen");
	handle = myFile_Open(buffer, 0x4f);
	if (!handle)
	{
		return 0;
	}

	/* Check file's extent */
	extent = myFile_Extent(handle);
	if (extent > sizeof(zfh) + 256 * 16)
	{
		myFile_Close(handle);
		return 0;
	}

	/* Load the header */
	if (myFile_ReadBytes(handle, &zfh, sizeof(zfh)))
	{
		myFile_Close(handle);
		return 0;
	}

	/* Check font size */
	if ((zfh.w != 8) || (zfh.h > 16))
	{
		myFile_Close(handle);
		return 0;
	}

	/* Load the 1bpp data */
	if (myFile_ReadBytes(handle, fullscreen_font, extent - sizeof(zfh)))
	{
		myFile_Close(handle);
		return 0;
	}

	myFile_Close(handle);

	l = zfh.l > 255 ? 255 : zfh.l;

	if (zfh.h > 8)
	{
		op = (int *)(((int)fullscreen_font) + (l + 1) * zfh.h * 4);
		ip = (char *)(((int)fullscreen_font) + (l + 1) * zfh.h -
					  (zfh.f * zfh.h));
		while (l-- >= zfh.f)
		{
			for (i = 0; i < zfh.h; i++)
			{
				*--op = byte_to_word_flipped(*--ip);
			}
		}
		fullscreen_height = zfh.h;
	}
	else
	{
		op = (int *)(((int)fullscreen_font) + (l + 1) * zfh.h * 8);
		ip = (char *)(((int)fullscreen_font) + (l + 1) * zfh.h -
					  (zfh.f * zfh.h));
		while (l-- >= zfh.f)
		{
			for (i = -zfh.h; i < zfh.h; i++)
			{
				int t = byte_to_word_flipped(*--ip);
				*--op = t;
				*--op = t;
			}
		}
		fullscreen_height = zfh.h * 2;
	}

	fullscreen_topline = TERM_TOPLINE_HR;
	fullscreen_topline += ((16 - fullscreen_height) * 13);

	return 1;
}





static int cache_fs_fontHR(void)
{
	ZapFont *src = SYSTEM_FONT;
	int c;
	int *op;
	char *ip;

	/* Allocate the storage for the font */
	fullscreen_font = f_malloc(256 * 4 * 16);
	if (!fullscreen_font)
	{
		return 0;
	}
	op = (int *)fullscreen_font;

	/* Check to see if the main term's font is suitable (ie. 8x16 or 8x8) */
	if ((data[0].font->w == 8) && (data[0].font->h <= 16))
		src = data[0].font;

	/*
	   | Hack: if we're forced to use the system font, try to load the
	   | 'fullscreen' font from lib.xtra.  If that fails, then I guess we're
	   | stuck with the system font.
	 */

	if (src == SYSTEM_FONT)
		if (cache_zapfontHR())
		{
			return 1;
		}

	ip = (char *)(src->bpp_1);

	/* Now, create the font */
	if (src->h > 8)
	{
		int e = src->h * (src->l > 256 ? 256 : src->l);
		op += (src->f * src->h);
		for (c = src->f * src->h; c < e; c++)
			*op++ = byte_to_word_flipped(*ip++);
		fullscreen_height = src->h;
	}
	else
	{
		int e = src->h * (src->l > 256 ? 256 : src->l);
		op += (src->f * src->h) * 2;
		for (c = src->f * src->h; c < e; c++)
		{
			int t = byte_to_word_flipped(*ip++);
			*op++ = t;
			*op++ = t;
		}
		fullscreen_height = src->h * 2;
	}

	fullscreen_topline = TERM_TOPLINE_HR;
	fullscreen_topline += ((16 - fullscreen_height) * 13);

	return 1;
}





static int cache_fs_fontLR(void)
{
	ZapFont *src = SYSTEM_FONT;
	int c, e;
	int *op;
	char *ip;

	/* Allocate the storage for the font */
	fullscreen_font = f_malloc(256 * 4 * 8);
	if (!fullscreen_font)
	{
		return 0;
	}
	op = (int *)fullscreen_font;

	/* Check to see if the main term's font is suitable (ie. 8x8) */
	if ((data[0].font->w == 8) && (data[0].font->h <= 8))
		src = data[0].font;

	ip = (char *)(src->bpp_1);

	/* Now, create the font */
	e = src->h * (src->l > 256 ? 256 : src->l);
	op += (src->f * src->h);
	for (c = src->f * src->h; c < e; c++)
		*op++ = byte_to_word_flipped(*ip++);

	fullscreen_height = src->h;
	fullscreen_topline = TERM_TOPLINE_LR;
	fullscreen_topline += ((8 - fullscreen_height) * 13);

	return 1;
}



static void set_keys(int claim)
{
	static int old_c_state;
	static int old_f_state[8];
	int i;

	if (claim)
	{
		/* Cursors/copy act as function keys */
		/* f0-f9, cursors, generate 0x80-0x8f */
		/* sh-f0-f9,cursors, generate 0x90-0x9f */
		/* ctrl f0-f9,cursors, generate 0xa0-0xaf */
		/* sh-c-f0-f9,cursors, generate 0xb0-0xbf */
		/* f10-f12 generate 0xca-0xcc */
		/* shift f10-f12 generate 0xda-0xdc */
		/* ctrl f10-f12 generate 0xea-0xec */
		/* ctrlshift f10-f12 generate 0xfa-0xfc */

		SWI(3, 2, SWI_OS_Byte, 4, 2, 0, /**/ NULL, &old_c_state);

		for (i = 0; i < 4; i++)
		{
			SWI(3, 2, SWI_OS_Byte, 225 + i, 0x80 + (i * 0x10), 0, NULL,
				old_f_state + i);
			SWI(3, 2, SWI_OS_Byte, 221 + i, 0xc0 + (i * 0x10), 0, NULL,
				old_f_state + i + 4);
		}
	}
	else
	{
		SWI(3, 0, SWI_OS_Byte, 4, old_c_state, 0);
		for (i = 0; i < 4; i++)
		{
			SWI(3, 0, SWI_OS_Byte, 225 + i, old_f_state[i], 0);
			SWI(3, 0, SWI_OS_Byte, 221 + i, old_f_state[i + 4], 0);
		}
	}
}






/*
 | Enter the full screen mode.
 |
 | Full screen display uses either mode 27 (if supported) and 8x16 fonts
 | (or system font 'twiddled' to double height), or mode 12 (if mode 27
 | is unavailable) and the system font (or an 8x8 font).
 |
 */

static void enter_fullscreen_mode(void)
{
	int vduvars[2] =
	{ 149, -1 };
	int i;

	/* New in 1.18 - protect against 're-entracy' */
	if (fullscreen_font)
		return;

	/* New in 1.20 - hack IClear out of the way */
	if (allow_iclear_hack)
		iclear_hack();

	/* Choose the mode we want */
	fullscreen_mode = select_fullscreen_mode();

	if (!fullscreen_mode)
	{
		plog("Unable to select a suitable screen mode (27 or 12)");
		return;
	}

	if (!((fullscreen_mode == 12) ? cache_fs_fontLR() : cache_fs_fontHR()))
	{
		plog("Unable to cache a font for full screen mode");
		return;
	}

	/* Read the current screen mode */
	/* SWI( 1,3, SWI_OS_Byte, 135, NULL, NULL, &old_screenmode ); */
	old_screenmode = current_mode();

	Stop_Hourglass;

	/* Change to the chosen screen mode */
	change_screenmode(fullscreen_mode);

	/* Restrict the pointer */
	constrain_pointer();

	/* Remove the cursors */
	SWI(0, 0, SWI_OS_RemoveCursors);

	Start_Hourglass;

	/* Get the base address of screen memory */
	SWI(2, 0, SWI_OS_ReadVduVariables, vduvars, vduvars);
	fullscreen_base = (int *)(vduvars[0]);

	/* Fudge the Term interface */
	for (i = 0; i < MAX_TERM_DATA; i++)
	{
		term *t = &(data[i].t);
		t->xtra_hook = Term_xtra_acnFS;
		t->wipe_hook = Term_wipe_acnFS;
		t->curs_hook = Term_curs_acnFS;
		t->text_hook = Term_text_acnFS;
	}

	/* Grab the palette */
	Term_xtra_acn_reactFS(TRUE);

	/* Make sure that the keys work properly */
	set_keys(TRUE);

	/* refresh the term */
	/*Term_activate( &(data[0].t) ); */
	redraw_areaFS(0, 0, 80, 24);
	if (data[0].cursor.visible)
		draw_cursor(data[0].cursor.pos.x, data[0].cursor.pos.y);

	/* Display a reminder of how to get back... */
	/* Hack: disable force_mono */
	i = force_mono;
	force_mono = 0;
	Term_text_acnFS(0, TIME_LINE, strlen(fs_quit_key_text), 8,
					fs_quit_key_text);
	force_mono = i;
}




static void leave_fullscreen_mode(void)
{
	int i;

	/* New in 1.18 - protect against 're-entracy' */
	if (!fullscreen_font)
		return;

	/* Restore the Term interface */
	for (i = 0; i < MAX_TERM_DATA; i++)
	{
#ifndef FULLSCREEN_ONLY
		term *t = &(data[i].t);
		t->xtra_hook = Term_xtra_acn;
		t->wipe_hook = Term_wipe_acn;
		t->curs_hook = Term_curs_acn;
		t->text_hook = Term_text_acn;
#endif		
		mark_ood(&(data[i]), 0, 0, 80, 24);
	}

	/* Deallocate the font */
	f_free(fullscreen_font);
	fullscreen_font = 0;
	fullscreen_mode = 0;

	Stop_Hourglass;

	/* Restore the screen mode */
	Wimp_SetMode(old_screenmode);

	/* Restore the pointer */
	release_pointer();

	Start_Hourglass;

	/* Restore the various soft keys */
	set_keys(FALSE);

	/* New in 1.20 Remove the IClear hack */
	if (allow_iclear_hack)
		remove_iclear_hack();

#ifndef FULLSCREEN_ONLY
	/* Refresh the windows - this probably isn't necessary anyway */
	if (!minimise_memory)
		refresh_windows();
#endif /* FULLSCREEN_ONLY */
}





static void fs_writechars(int x, int y, int n, const char *chars, char attr)
{
	int *scr, *scrb;
	int *cdat;
	int j;
	unsigned int fgm;

	if (force_mono)
	{
		if (attr != TERM_DARK)
		{
			attr = TERM_WHITE;
		}
	}
	fgm = (unsigned int)zpalette[(unsigned int) attr];

	scrb = (int *)(((int)fullscreen_base) + y * fullscreen_height * 320
				   + x * 4 + 320 * fullscreen_topline);

	while (n--)
	{
		scr = scrb++;
		cdat = (int *)(((int)fullscreen_font)
					   + (*chars++) * (fullscreen_height << 2));
		for (j = 0; j < fullscreen_height; j++)
		{
			*scr = *cdat++ & fgm;
			scr += 80;
		}
	}
}


static void fs_writechar(int x, int y, char c, char attr)
{
	int *scrb;
	int *cdat;
	int j;
	unsigned int fgm;

	if (force_mono)
	{
		if (attr != TERM_DARK)
		{
			attr = TERM_WHITE;
		}
	}
	fgm = (unsigned int)zpalette[(unsigned int) attr];

	scrb = (int *)(((int)fullscreen_base) + y * fullscreen_height * 320
				   + x * 4 + 320 * fullscreen_topline);
	cdat = (int *)(((int)fullscreen_font) + (c * (fullscreen_height << 2)));
	for (j = 0; j < fullscreen_height; j++)
	{
		*scrb = *cdat++ & fgm;
		scrb += 80;
	}
}



static void draw_cursorHR(int x, int y)
{
	ColourTrans_SetGCOL(cursor_rgb, 0, 0);
	GFX_Move(x * 16,
			 959 - y * (fullscreen_height * 2) - fullscreen_topline * 2);
	GFX_DrawBy(14, 0);
	GFX_DrawBy(0, -(fullscreen_height * 2 - 2));
	GFX_DrawBy(-14, 0);
	GFX_DrawBy(0, fullscreen_height * 2 - 2);
}

static void draw_cursorLR(int x, int y)
{
	ColourTrans_SetGCOL(cursor_rgb, 0, 0);
	GFX_Move(x * 16,
			 1023 - y * (fullscreen_height * 4) - fullscreen_topline * 4);
	GFX_DrawBy(14, 0);
	GFX_DrawBy(0, -(fullscreen_height * 4 - 4));
	GFX_DrawBy(-14, 0);
	GFX_DrawBy(0, fullscreen_height * 4 - 4);
}





static void draw_cursor(int x, int y)
{
	if (fullscreen_mode == 12)
		draw_cursorLR(x, y);
	else
		draw_cursorHR(x, y);
}



static void redraw_areaFS(int x, int y, int w, int h)
{
	int i, j;
	for (j = y; j < y + h; j++)
		for (i = x; i < x + w; i++)
			fs_writechar(i, j, data[0].t.old->c[j][i], data[0].t.old->a[j][i]);
}



static int wimp_code(int c)
{
	/* shift/ctrl keypad? */
	if (c >= '0' && c <= '9')
	{
		kbd_modifiers m = Kbd_GetModifiers(FALSE);
		if (m.shift)
		{
			c |= 0x800;
		}
		if (m.ctrl)
		{
			c |= 0x400;
		}
		return c;
	}
	if (c == 9)
	{
		return 0x18a;
	}							/* Tab */
	if (c <= 127)
	{
		return c;
	}							/* normal ASCII/ctrl */
	if (c >= 0x80 && c <= 0xff)
	{
		return c + 0x100;
	}							/* f0-f9, etc. */

	return -1;					/* unknown */
}




static void do_keypress(int code)
{
	static const char hex[] = "0123456789ABCDEF";

	if (code == KEYPRESS_QUIT && !minimise_memory)
	{
#ifdef FULLSCREEN_ONLY
		Sound_SysBeep();
#else
		leave_fullscreen_mode();
#endif
		return;
	}

	if (code == 27)
	{
		if (Kbd_KeyDown(inkey_CTRL))
		{
			ack_alarm();
			return;
		}
	}

	if (code <= 255)
	{
		Term_keypress(code);
	}
	else
	{
		Term_keypress(31);
		Term_keypress(hex[(code & 0xf00) >> 8]);
		Term_keypress(hex[(code & 0x0f0) >> 4]);
		Term_keypress(hex[(code & 0x00f)]);
		Term_keypress(13);
	}
}




static errr Term_xtra_acn_checkFS(void)
{
	int bh, bl;
	int c;

	Stop_Hourglass;

	bored();

	SWI(3, 3, SWI_OS_Byte, 128, 255, 0, /**/ NULL, &bl, &bh);
	bl = (bl & 0xff) + (bh << 8);

	if (bl > 0)
	{
		SWI(0, 1, SWI_OS_ReadC, &c);
		bl = wimp_code(c);
		if (bl >= 0)
		{
			do_keypress(bl);
		}
	}

	Start_Hourglass;

	return 0;
}




static errr Term_xtra_acn_eventFS(void)
{
	int c;
	int w = -1;

	Stop_Hourglass;

	for (w = -1; w == -1;)
	{
		int bh, bl;
		do
		{
			bored();
			SWI(3, 3, SWI_OS_Byte, 128, 255, 0, /**/ NULL, &bl, &bh);
			bl = (bl & 0xff) + (bh << 8);
		}
		while (!bl);

		SWI(0, 1, SWI_OS_ReadC, &c);
		w = wimp_code(c);
		if (w >= 0)
		{
			do_keypress(w);
		}
	}

	Start_Hourglass;

	return 0;
}



/*
 * React to changes
 */
static errr Term_xtra_acn_reactFS(int force)
{
	unsigned int i;
	int p, r, g, b;

	static double old_gamma = -1.0;

	if (gamma != old_gamma)
	{
		force = 1;
		old_gamma = gamma;
	}

	/* Set the screen colours */
	for (i = 0; i < 16; i++)
	{
		if (COLOUR_CHANGED(i) || force)
		{
			r = (int)(255.0 *
					  pow(angband_color_table[i][1] / 255.0, 1.0 / gamma));
			g = (int)(255.0 *
					  pow(angband_color_table[i][2] / 255.0, 1.0 / gamma));
			b = (int)(255.0 *
					  pow(angband_color_table[i][3] / 255.0, 1.0 / gamma));
			GFX_VDU(19);
			GFX_VDU(i);
			GFX_VDU(16);
			GFX_VDU(r);
			GFX_VDU(g);
			GFX_VDU(b);

			palette[i] = (b << 24) | (g << 16) | (r << 8);
			p = i;
			p |= (p << 4);
			p |= (p << 8);
			p |= (p << 16);
			zpalette[i] = p;

			a_palette[i][1] = angband_color_table[i][1];
			a_palette[i][2] = angband_color_table[i][2];
			a_palette[i][3] = angband_color_table[i][3];

			/* Find any higher colour numbers and make them "wrong" */
			for (p = 16; p < 256; p++)
				if ((zpalette[p] & 0xf) == i)
					a_palette[p][1] = angband_color_table[p][1] + 2;
		}
	}


	/* Go through the palette updating any changed values */
	for (i = 16; i < 256; i++)
	{
		if (COLOUR_CHANGED(i) || force)
		{
			r = (int)(255.0 *
					  pow(angband_color_table[i][1] / 255.0, 1.0 / gamma));
			g = (int)(255.0 *
					  pow(angband_color_table[i][2] / 255.0, 1.0 / gamma));
			b = (int)(255.0 *
					  pow(angband_color_table[i][3] / 255.0, 1.0 / gamma));
			p = (b << 24) | (g << 16) | (r << 8);
			palette[i] = p;
			SWI(1, 1, SWI_ColourTrans_ReturnColourNumber, palette[i], &p);
			p |= (p << 4);
			p |= (p << 8);
			p |= (p << 16);
			zpalette[i] = p;
			a_palette[i][1] = angband_color_table[i][1];
			a_palette[i][2] = angband_color_table[i][2];
			a_palette[i][3] = angband_color_table[i][3];
		}
	}

	cursor_rgb = palette[CURSOR_COLOUR];

	return 0;
}


static errr Term_curs_acnFS(int x, int y)
{
	if (Term == &(data[0].t))
	{
		if (data[0].cursor.visible)
			redraw_areaFS(data[0].cursor.pos.x, data[0].cursor.pos.y, 1, 1);
		data[0].cursor.pos.x = x;
		data[0].cursor.pos.y = y;
		if (data[0].cursor.visible)
			draw_cursor(x, y);
	}
	return 0;
}

static errr Term_xtra_acn_clearFS(void)
{
	char e[80];
	int j;

	if (Term == &(data[0].t))
	{
		for (j = 0; j < 80; j++)
			e[j] = ' ';

		GFX_Wait();

		for (j = 0; j < 24; j++)
			fs_writechars(0, j, 80, e, 0);
	}

	return 0;
}







static errr Term_xtra_acnFS(int n, int v)
{
	term_data *t = (term_data *)Term;

	switch (n)
	{
		case TERM_XTRA_CLEAR:
			if (t == (&data[0]))
				Term_xtra_acn_clearFS();
			return 0;

		case TERM_XTRA_EVENT:
			if (v)
				return Term_xtra_acn_eventFS();
			else
				return Term_xtra_acn_checkFS();

		case TERM_XTRA_BORED:
			bored();
			return Term_xtra_acn_checkFS();

		case TERM_XTRA_FLUSH:
			/* 1.21 - Hack: wait until no keys are pressed */
			if (hack_flush)
				for (v = 0; v != 0xff;)
					SWI(1, 2, SWI_OS_Byte, 122, 0, &v);
			SWI(3, 0, SWI_OS_Byte, 21, 0, 0);	/* Flush Kbd buffer */
			return 0;

		case TERM_XTRA_FRESH:
			return 0;

		case TERM_XTRA_FROSH:
			return 0;

		case TERM_XTRA_SHAPE:
			if (t == (&data[0]))
			{
				t->cursor.visible = v;
				if (v)
					draw_cursor(t->cursor.pos.x, t->cursor.pos.y);
				else
					redraw_areaFS(t->cursor.pos.x, t->cursor.pos.y, 1, 1);
			}
			return 0;

		case TERM_XTRA_NOISE:
			Sound_SysBeep();
			return 0;

		case TERM_XTRA_REACT:
			return Term_xtra_acn_reactFS(FALSE);

		case TERM_XTRA_DELAY:
			if (v > 0)
			{
				int start = Time_Monotonic();
				v = (v + 5) / 10;	/* Round to nearest cs */
				GFX_Wait();
				while ((Time_Monotonic() - start) < v)
					;
			}
			return (0);

		case TERM_XTRA_SOUND:	/* Play a sound :) */
			if (enable_sound)
			{
				play_sound(v);
			}
			return 0;

		/* Subdirectory scan */
		case TERM_XTRA_SCANSUBDIR:
		{
		    filing_dirdata directory;
		    filing_direntry *entry;

			scansubdir_max = 0;

			if (Filing_OpenDir(riscosify_name(scansubdir_dir), &directory, sizeof(filing_direntry), readdirtype_DIRENTRY) != NULL)
			{
			  Error_Report(0, "Couldn't open directory \"%s\"", riscosify_name(scansubdir_dir));
			  return 0;
			}

			while ((entry = Filing_ReadDir(&directory)) != NULL)
			{
				if (entry->objtype == filing_DIRECTORY)
				{    
					string_free(scansubdir_result[scansubdir_max]);
					scansubdir_result[scansubdir_max] = string_make(entry->name);
					++scansubdir_max;
				}
			}

			Filing_CloseDir(&directory);

			return 0;
		}

		/* Return current "time" in milliseconds */
		case TERM_XTRA_GET_DELAY:
		{
			Term_xtra_long = Time_Monotonic() * 100;

			return 0;
		}

		/* Rename main window */
		case TERM_XTRA_RENAME_MAIN_WIN:
		{
			Window_SetTitle(data[0].w, angband_term_name[0]);
			return 0;
		}

		default:
			return 1;
	}
}

static errr Term_wipe_acnFS(int x, int y, int n)
{
	if (Term == &(data[0].t))
		while (n--)
			fs_writechar(x++, y, ' ', 0);
	return 0;
}

static errr Term_text_acnFS(int x, int y, int n, byte a, cptr s)
{
	if (Term == &(data[0].t))
		fs_writechars(x, y, n, s, (char)a);
	return 0;
}



static void bored()
{
	static int last = -1;
	char ts[80];
	time_t ct;
	struct tm *lt;
	unsigned int l;
	int ofm;
	static int alarm_flash = 1;

	/* Really no need to check the alarm more than once per second. */
	if (alarm_type && Time_Monotonic() > alarm_lastcheck + 100)
	{
		check_alarm();
	}

	l = Time_Monotonic();
	if ((l - last) < (alarm_flash ? 25 : 50))
	{
		return;
	}
	last = l;

	time(&ct);
	lt = localtime(&ct);
	l = strftime(ts, 80, "%c %Z", lt);

	/* Hack: disable force_mono around printing the time */
	ofm = force_mono;
	force_mono = 0;

	/* Hack: Is the alarm supposed to be going off? */
	if (alarm_disp || alarm_flash)
	{
		char blk[60];
		int c = 8;
		if (!alarm_disp)
		{
			alarm_flash = 11;
		}
		switch (alarm_flash / 2)
		{
			case 4: sprintf(blk, "%-57s", alarm_cancel_text);
				break;
			case 5: sprintf(blk, "%-57s", fs_quit_key_text);
				break;
			default:
				c = alarm_flash & 1 ? TERM_RED : TERM_WHITE;
				sprintf(blk, "%02d:%02d %-51s", alarm_h, alarm_m,
						alarm_message);
		}
		fs_writechars(0, TIME_LINE, 57, blk, c);
		if (++alarm_flash > 11)
		{
			alarm_flash = 0;
		}
	}

	/* Display time */
	fs_writechar(79 - l, TIME_LINE, ' ', 0);
	fs_writechars(80 - l, TIME_LINE, l, ts, 8);

	force_mono = ofm;
}






#ifdef USE_DA
/*--------------------------------------------------------------------------*/
/* (Simple) Heap management (using OS_Heap)									*/
/*--------------------------------------------------------------------------*/

typedef void *heap;

static os_error *Heap_Initialise(heap h, size_t size)
{
	return SWI(4, 0, SWI_OS_Heap, 0, h, 0, size);
}

static void *Heap_Claim(heap h, size_t size)
{
	void *fred;
	os_error *e;
	e = SWI(4, 3, SWI_OS_Heap, 2, h, 0, size, NULL, NULL, &fred);
	return e ? NULL : fred;
}

static os_error *Heap_Release(heap h, void *block)
{
	return SWI(3, 0, SWI_OS_Heap, 3, h, block);
}

static int Heap_ChangeHeapSize(heap h, int resize_by)
{
	int by;
	SWI(4, 4, SWI_OS_Heap, 5, h, 0, resize_by, 0, 0, 0, &by);
	return by;
}



/*--------------------------------------------------------------------------*/
/* Stuff below here is for using Dynamic areas (under RO3.5+)				*/
/*--------------------------------------------------------------------------*/

static int game_area = -1;	/* The DA the game is using */
static int font_area = -1;	/* The DA the fonts are using */

static void *game_area_base;	/* base address of game area */
static void *font_area_base;	/* base address of font area */

static int font_area_size;	/* size of the fonts' DA */
static int font_heap_size;	/* size of the fonts' heap */
static int game_area_size;	/* size of the game's DA */
static int game_heap_size;	/* size of the game's heap */

#define MAX_F_DA_SIZE (2<<20)	/* Max size of font area (2Mb) */
#define MAX_G_DA_SIZE (4<<20)	/* Max size of game area (4Mb) */
#define SHRINK_GRAN (4<<10)		/* Try to recalaim wastage > this (4Kb) */


/*
 | Free dynamic areas when we exit
 */
static void cleanup_memory(void)
{
	if (game_area != -1)
	{
		SWI(2, 0, SWI_OS_DynamicArea, 1, game_area);
		game_area = -1;
	}

	if (font_area != -1)
	{
		SWI(2, 0, SWI_OS_DynamicArea, 1, font_area);
		font_area = -1;
	}

}



/*
 | Set up the memory allocation stuff.
 | We check to see if DAs are possible and if so initialise two:
 | one for the game's use (via the rnalloc() hooks) and one for
 | our own use (for fonts, etc).
 |
 | Each area is created 16Kb in size, with a max size of 2/4Mb.
 |
 | If 'daf' is TRUE, an area is created for the fonts.
 | If 'dag' is TRUE, an area is created for the game.
 */
static void init_memory(int daf, int dag)
{       
	os_error *e = NULL;
	
	if (!daf)
	{
		/* Paranoia */
		font_area = -1;
		font_area_base = 0;
	}
	else
	{
		e = SWI(9, 4, SWI_OS_DynamicArea, 0,	/* Create */
				-1,				/* Let OS allocate no. */
				16 << 10,		/* Initial size */
				-1,				/* Let OS allocate address */
				1 << 7,			/* Cacheable, bufferable, RW */
				MAX_F_DA_SIZE,	/* Max size */
				0,				/* handler */
				0,				/* handler workspace */
				VARIANT " font data",	/* Name */
				/* */
				NULL,			/* r0 */
				&font_area,		/* area number allocated */
				NULL,			/* r2 */
				&font_area_base	/* base address of area */
			);

		if (e)
		{
			game_area = font_area = -1;
			game_area_base = font_area_base = 0;	/* paranoia */
			return;
		}
		else
		{
			e = SWI(2, 3, SWI_OS_DynamicArea, 2, font_area,
					NULL, NULL, &font_area_size);
			if (e)
			{
				Error_ReportFatal(e->errnum, "%d:%s", e->errmess);
			}

			e = Heap_Initialise((heap) font_area_base, font_area_size);
			if (e)
			{
				Error_ReportFatal(e->errnum, "%d:%s", e->errmess);
			}
			font_heap_size = font_area_size;
		}
	}

	/* Make sure DA(s) are removed when we quit */
	atexit(cleanup_memory);

	if (!dag)
	{
		/* Paranoia */
		game_area = -1;
		game_area_base = 0;
	}
	else
	{
		e = SWI(9, 4, SWI_OS_DynamicArea, 0,	/* Create */
				-1,				/* Let OS allocate no. */
				16 << 10,		/* Initial size */
				-1,				/* Let OS allocate address */
				1 << 7,			/* Cacheable, bufferable, RW */
				MAX_G_DA_SIZE,	/* Max size */
				0,				/* handler */
				0,				/* handler workspace */
				VARIANT " game data",	/* Name */
				/* */
				NULL,			/* r0 */
				&game_area,		/* area number allocated */
				NULL,			/* r2 */
				&game_area_base	/* base address of area */
			);

		if (e)
		{
			game_area = -1;
			game_area_base = 0;	/* paranoia */
		}
		else
		{
			e = SWI(2, 3, SWI_OS_DynamicArea, 2, game_area,
					NULL, NULL, &game_area_size);
			if (e)
			{
				Error_ReportFatal(e->errnum, "%d:%s", e->errmess);
			}

			e = Heap_Initialise((heap) game_area_base, game_area_size);
			if (e)
			{
				Error_ReportFatal(e->errnum, "%d:%s", e->errmess);
			}
			game_heap_size = game_area_size;
		}
	}
}

static int grow_dynamicarea(int area, int by)
{
	os_error *e;
	e = SWI(2, 2, SWI_OS_ChangeDynamicArea, area, by, /**/ NULL, &by);
	/* Can't check errors since a 'failed' shrink returns one... */
	return by;
}


/*
 | Try to shrink the font-cache heap and area as much as possible.
 */
static void f_shrink_heap(void)
{
	int s;
	/* Shrink the heap as far as possible */
	font_heap_size -=
		Heap_ChangeHeapSize((heap) font_area_base, -MAX_F_DA_SIZE);
	/* Shrink the dynamic area if necessary */
	s = font_area_size - font_heap_size;
	if (s >= SHRINK_GRAN)
		font_area_size -= grow_dynamicarea(font_area, -s);
}

/*
 | Allocate a block of memory in the font heap
 */
static void *f_malloc(size_t size)
{
	void *c;
	int s;
	if (font_area == -1)
	{
		return malloc(size);
	}
	c = Heap_Claim((heap) font_area_base, size);

	if (!c)
	{
		/* The Claim failed.  Try to grow the area by the size of the block */
		s = grow_dynamicarea(font_area, size + 64);	/* 64 is overkill */
		if (!s)
		{
			return NULL;
		}
		font_area_size += s;
		s = font_area_size - font_heap_size;
		font_heap_size += Heap_ChangeHeapSize((heap) font_area_base, s);
		c = Heap_Claim((heap) font_area_base, size);
		if (c)
		{
			f_shrink_heap();
		}
	}
	return c;
}


/*
 | Free a block of memory in the font heap
 */
static void f_free(void *blk)
{
	os_error *e;
	if (font_area == -1)
	{
		free(blk);
		return;
	}
	e = Heap_Release((heap) font_area_base, blk);
	if (e)
		Msgs_ReportFatal(e->errnum, "err.swi", __LINE__, e->errmess);
	f_shrink_heap();
}





/*
 | Allocate a block of memory in the game heap
 */
static vptr g_malloc(huge size)
{
	void *c;
	int s;

	if (game_area == -1)
	{
		return malloc((size_t) size);
	}
	c = Heap_Claim((heap) game_area_base, (size_t) size + 4);
	if (!c)
	{
		/* The Claim failed.  Try to grow the area by the size of the block */
		s = grow_dynamicarea(game_area, (size_t) size + 64);	/* 64 is overkill */
		if (!s)
		{
			return NULL;
		}
		game_area_size += s;
		s = game_area_size - game_heap_size;
		game_heap_size += Heap_ChangeHeapSize((heap) game_area_base, s);
		c = Heap_Claim((heap) game_area_base, (size_t) size + 4);
	}

	if (c)
	{
		strcpy((char *)c, "MUSH");
		c = (void *)(((int)c) + 4);
	}

	if (log_g_malloc)
		fprintf(stderr, "ralloc(%ld) == %p\n", (long)size, c);

	return c;
}


/*
 | Free a block of memory in the game heap
 |
 | The 'len' is to be compatible with z-virt.c (we don't need/use it)
 | Returns NULL.
 */
static vptr g_free(vptr blk, huge size)
{
	os_error *e;
	int s;

	if (game_area == -1)
	{
		free(blk);
		return NULL;
	}

	if (log_g_malloc)
		fprintf(stderr, "rnfree(%p)\n", blk);

	if (strncmp(((char *)blk) - 4, "MUSH", 4))
		core("game heap corrupt / bad attempt to free memory");

	blk = (void *)(((int)blk) - 4);

	e = Heap_Release((heap) game_area_base, blk);
	if (e)
		Msgs_ReportFatal(e->errnum, "err.swi", __LINE__, e->errmess);

	/* Shrink the heap as far as possible */
	game_heap_size -=
		Heap_ChangeHeapSize((heap) game_area_base, -MAX_G_DA_SIZE);
		
	/* Shrink the dynamic area if necessary */
	s = game_area_size - game_heap_size;
	if (s >= SHRINK_GRAN)
		game_area_size -= grow_dynamicarea(game_area, -s);

	return NULL;
}


#endif /* USE_DA */



/*--------------------------------------------------------------------------*/

/*
 | New to 1.04: Sound support :)
 |
 | We use the PlayIt module (for convenience).
 |
 | The Lib/xtra/sound/sound.cfg file is used to map sample names onto
 | event names.
 |
 | Since textual names are used in the .cfg file, we need to have a lookup
 | table to translate them into numbers.  At present we use the
 | angband_sound_name array defined in variable.c
 |
 | Since there can be multiple sounds for each event we need to use a
 | list to store them.
 */

/* NB: This will be clipped to 10 under RISC OS 2 */
#define MAX_SAMPNAME_LEN 64




/*
 | The list format:
 */
typedef struct samp_node
{
	char sample_name[MAX_SAMPNAME_LEN + 1];	/* Sample name */
	struct samp_node *next;	/* -> next node */
}
SampNode;

typedef struct samp_info
{
	int samples;	/* # samples for this event */
	SampNode *samplist;	/* list of sample names */
}
SampInfo;


/*
 | Just need an array of SampInfos
 */
static SampInfo sample[SOUND_MAX];

/*
 | This flag will only be set non-zero if the SampInfo array is
 | valid.
 */
static int sound_initd = 0;


static void read_sound_config(void)
{
	int i;
	char buffer[2048];
	FILE *f;
	int max_sampname_len = truncate_names()? 10 : MAX_SAMPNAME_LEN;
	FILE *dbo = NULL;

	if (show_sound_alloc)
	{
		sprintf(buffer, "%s%s", resource_path, "sndmap/out");
		dbo = fopen(buffer, "w");
		if (!dbo)
		{
			core("can't create sndmap/out debugging file");
		}
	}

	if (!sound_initd)
	{
		/* Initialise the sample array */
		for (i = 0; i < SOUND_MAX; i++)
		{
			sample[i].samples = 0;
			sample[i].samplist = NULL;
		}
		sound_initd = 1;
	}
	else
	{
		/* Deallocate the sample lists */
		for (i = 0; i < SOUND_MAX; i++)
		{
			SampNode *si = sample[i].samplist;
			sample[i].samples = 0;
			sample[i].samplist = NULL;
			while (si)
			{
				SampNode *ns = si->next;
				free(si);
				si = ns;
			}
		}
	}


	/* Open the config file */
	sprintf(buffer, "%sSound:%s", RISCOS_VARIANT, "sound/cfg");
	f = fopen(buffer, "r");

	/* No cfg file => no sounds */
	if (!f)
	{
		if (show_sound_alloc)
		{
			fprintf(dbo, "** Can't open cfg file '%s'\n", buffer);
			fclose(dbo);
		}
		return;
	}

	/* Parse the file */
	while (fgets(buffer, sizeof(buffer), f))
	{
		char *sample_name;
		int event_number;

		/* Skip comments and lines that begin with whitespace */
		if (*buffer == '#' || isspace((unsigned char)*buffer))
		{
			continue;
		}

		/* Hack: ignore any line beginning '[' (section marker) */
		if (*buffer == '[')
		{
			continue;
		}

		/* Place a NULL after the event name and find the first sample name */
		sample_name = buffer;
		while (*sample_name && !isspace((unsigned char)*sample_name))
			sample_name++;

		/* Bad line? */
		if (*sample_name == 0)
		{
			continue;
		}						/* just ignore it */

		/* Terminate the sample name */
		*sample_name++ = 0;

		/* Look up the event name to get the event number */
		for (event_number = SOUND_MAX - 1; event_number >= 0; event_number--)
			if (!strcmp(buffer, angband_sound_name[event_number]))
				break;

		/* No match -> just ignore the line */
		if (event_number < 0)
		{
			if (show_sound_alloc)
				fprintf(dbo, "* Ignoring unknown event '%s'\n", buffer);
			continue;
		}

		/* Find the = */
		while (*sample_name && *sample_name != '=')
			sample_name++;

		/* Bad line? */
		if (*sample_name == 0)
		{
			continue;
		}						/* just ignore it */

		/* Skip the '=' */
		sample_name++;


		/*
		   | Now we find all the sample names and add them to the
		   | appropriate list in the sample mapping array
		 */

		while (*sample_name)
		{
			char *s;
			SampNode *sn;

			/* Find the start of the next word */
			while (isspace((unsigned char)*sample_name) && *sample_name)
				sample_name++;

			/* End of line? */
			if (!*sample_name)
			{
				break;
			}

			/* Find the end of the sample name */
			s = sample_name;	/* start of the name */
			while (!isspace((unsigned char)*sample_name) && *sample_name)
				sample_name++;

			/* Hack: shorten sample names that are too long */
			if ((sample_name - s) > max_sampname_len)
				s[max_sampname_len] = ' ';

			/* Allocate a node in the sample list for the event */
			if ((sn = malloc(sizeof(SampNode))) == NULL)
				core("Out of memory (scanning sound.cfg)");

			/* Link the node to the list */
			sn->next = sample[event_number].samplist;
			sample[event_number].samplist = sn;

			/* Imcrement the sample count for that event */
			sample[event_number].samples++;

			/*
			   | Copy the sample name into the node, converting it into
			   | RISC OS style as we go.
			 */
			for (i = 0; !isspace((unsigned char)s[i]) && s[i]; i++)
			{
				if (s[i] == '.')
					sn->sample_name[i] = '/';
				else if (s[i] == '/')
					sn->sample_name[i] = '.';
				else
					sn->sample_name[i] = s[i];
			}
			/*
			   | The sample name '*' is special and means "no new sound"
			   | so don't store a filename for these mappings.
			 */
			if (i == 1 && sn->sample_name[0] == '*')
			{
				i = 0;
			}
			sn->sample_name[i] = 0;
		}
	}

	/* Close the file */
	fclose(f);

	if (show_sound_alloc)
	{
		int i;
		SampNode *l;

		for (i = 0; i < SOUND_MAX; i++)
		{
			fprintf(dbo, "\n\nEvent '%s'", angband_sound_name[i]);
			fprintf(dbo, " (%d sounds)\n", sample[i].samples);
			for (l = sample[i].samplist; l; l = l->next)
				fprintf(dbo, "\t%s\n", l->sample_name);
		}
		fclose(dbo);
	}

}



/*
 | Try to make sure that PlayIt is loaded.
 | This requires AngSound rel. 4
 */
static void check_playit(void)
{
	if (SWI(2, 0, SWI_OS_Module, 18, "PlayIt"))
	{
		int t;
		SWI(2, 1, SWI_OS_File, 17, "Angsound:LoadPlayIt", &t);
		if (t == 1)
			SWI(1, 0, SWI_OS_CLI,
				"RMEnsure PlayIt 0.00 Run AngSound:LoadPlayIt");
	}
}




static void initialise_sound(void)
{
	/* Load the configuration file */
	Hourglass_On();
	read_sound_config();
	check_playit();
	Hourglass_Off();
}



static void play_sample(char *leafname)
{
	char buffer[260];

	strcpy(buffer, "%playit_stop");

	if (!SWI(1, 0, SWI_OS_CLI, buffer))
	{
		SWI(1, 0, SWI_PlayIt_Volume, sound_volume);
		sprintf(buffer, "%%playit_play %sSound:%s", RISCOS_VARIANT, leafname);
		SWI(1, 0, SWI_OS_CLI, buffer);
	}

	return;
}



static void play_sound(int event)
{
	/* Paranoia */
	if (!sound_initd)
	{
		return;
	}

	/* Paranoia */
	if (event < 0 || event >= SOUND_MAX)
		return;

	/* msg_format("Sound '%s'",angband_sound_name[event]); */

	/* Choose a sample */
	if (sample[event].samples)
	{
		int s = rand() % sample[event].samples;
		SampNode *sn = sample[event].samplist;
		while (s--)
		{
			sn = sn->next;
			if (!sn)
			{
				plog("Adny botched the sound config - please send him a copy of your sound/cfg file.");
			}
		}
		if (*(sn->sample_name))
			play_sample(sn->sample_name);
	}
}




/*--------------------------------------------------------------------------*/

/*
 | This stuff is for the Term_user hook
 */



static void display_line(int x, int y, int c, const char *fmt, ...)
{
	va_list ap;
	char buffer[260];

	va_start(ap, fmt);
	vsprintf(buffer, fmt, ap);
	Term_putstr(x, y, -1, c, buffer);
	va_end(ap);
}



/*
 | Let the user change the alarm message
 */
static void do_alarm_message_input(int y)
{
	int k;
	int inspos = strlen(alarm_message);
	char old_message[52];

	strcpy(old_message, alarm_message);

	do
	{
		display_line(26, y, TERM_YELLOW, "%-51s", alarm_message);
		Term_gotoxy(26 + inspos, y);
		k = inkey();
		switch (k)
		{
			case 21:			/* ^U */
				*alarm_message = 0;
				inspos = 0;
				break;
			case 128:  case 8:	/* delete */
				if (inspos > 0)
				{
					alarm_message[--inspos] = 0;
				}
				break;
			case 27:			/* escape */
				strcpy(alarm_message, old_message);
				k = 13;
				break;
			default:
				if (k > 31 && k < 127 && inspos < 50)
				{
					alarm_message[inspos++] = k;
					alarm_message[inspos] = 0;
				}
		}
	}
	while (k != 13);

	display_line(26, y, TERM_WHITE, "%-51s", alarm_message);
}


#define tum_col(X)  ((X) ? TERM_L_BLUE : TERM_WHITE )
#define tum_onoff(X)  ((X) ? "On " : "Off")

static errr Term_user_acn(int n)
{
	int cursor_state;
	int optn = 0;
	int k, adj;
	int redraw_mung = 0;
	int max_opt = 11;
	int alarm_modified = 0;	/* Will be true if the alarm choices need to be (re)saved */

	/*
	   | Hack: let the desktop front end know that
	   | the user menu is active...
	 */
	user_menu_active = TRUE;


	/*
	   | This is thanks to Norcroft CC...  it seems to want
	   | to set up FP regs nice and early and then relies
	   | on them remaining constant over the function call.
	   | The trouble is that we implicitly call Wimp_Poll
	   | whilst waiting for a key press...
	 */
	event_mask.data.keepfpregisters = 1;

	/*
	   | Hack: alarm type 1 /looks/ the same as type 3 but doesn't get
	   | cancelled as a type 3 would.  This allows alarms to go off and
	   | be cancelled without affecting the alarm type whilst it's being
	   | set up here.
	 */
	if (alarm_type == 3)
	{
		alarm_type = 1;
	}

	/*
	   | Store the screen
	 */
	Term_activate(&(data[0].t));
	Term_save();
	Term_get_cursor(&cursor_state);
	Term_set_cursor(TRUE);

	do
	{
		redraw_mung = 0;
		Term_clear();
		display_line(2, 1, TERM_YELLOW, "%s %s", VARIANT, VERSION);
		display_line(2, 2, TERM_SLATE, "Front-end %s", PORTVERSION);
		display_line(2, 4, TERM_WHITE,
					 "Use cursor up/down to select an option then cursor left/right to alter it.");
		display_line(2, 5, TERM_WHITE,
					 "Hit 'S' to save these settings (alarm settings are saved automatically).");
		display_line(2, 6, TERM_WHITE, "Hit ESC to return to the game.");

		for (k = 0; k < 16; k++)
			display_line(31 + k * 3, 8, k, "##", k);

		do
		{
			display_line(2, 8, tum_col(optn == 0),
						 "     Gamma correction : %.2lf", gamma);
			display_line(2, 9, tum_col(optn == 1), "     Force monochrome : %s",
						 tum_onoff(force_mono));
			display_line(2, 10, tum_col(optn == 2), "        Sound effects : %s",
						 tum_onoff(enable_sound));
			display_line(2, 11, tum_col(optn == 3), "  Sound effect volume : ");
			display_line(26, 11,
						 sound_volume > 127 ? TERM_RED : tum_col(optn == 3),
						 "%-3d", sound_volume);
			display_line(30, 11, tum_col(optn == 3), "(127 = full volume)");
			display_line(2, 12, tum_col(optn == 4), "     Start fullscreen : %s",
						 tum_onoff(start_fullscreen));
			display_line(30, 12, tum_col(optn == 4),
						 "(also selects fullscreen/desktop now)");
			display_line(2, 13, tum_col(optn == 5), "        Use hourglass : %s",
						 tum_onoff(use_glass));
			display_line(2, 14, tum_col(optn == 6),
						 "'Hard' input flushing : %s", tum_onoff(hack_flush));

			display_line(7, 16, tum_col(optn == 7), "      Alarm type : %-20s",
						 alarm_types[alarm_type]);
			display_line(7, 17, TERM_WHITE, "            Time : ");
			display_line(26, 17, tum_col(optn == 8), "%02d", alarm_h);
			display_line(28, 17, TERM_WHITE, ":");
			display_line(29, 17, tum_col(optn == 9), "%02d", alarm_m);
			display_line(7, 18, tum_col(optn == 10), "         Message : %-51s",
						 alarm_message);
			display_line(7, 19, tum_col(optn == 11), "            Beep : %s",
						 tum_onoff(alarm_beep));

#ifdef FE_DEBUG_INFO
			display_line(2, 23, tum_col(optn == 23), "Show debug info");
			max_opt = 12;
#endif

			switch (optn)
			{
				case 12: Term_gotoxy(2, 23);
					break;
				case 11: Term_gotoxy(26, 19);
					break;
				case 10: Term_gotoxy(26, 18);
					break;
				case 9: Term_gotoxy(29, 17);
					break;
				case 8: Term_gotoxy(26, 17);
					break;
				case 7: Term_gotoxy(26, 16);
					break;
				default: Term_gotoxy(26, optn + 8);
			}

			k = inkey();
			adj = (k == '4' || k == 'h') ? -1 : (k == '6' || k == 'l') ? 1 : 0;

			switch (k)
			{
				case 18:		/* Hack: force the screen to update */
					redraw_mung = 1;
					k = 27;
					break;
				case 's':  case 'S':
					save_choices();
					display_line(2, 23, TERM_YELLOW, "Options saved.	   ");
					Term_fresh();
					Term_xtra(TERM_XTRA_DELAY, 750);
					Term_erase(2, 23, 60);
					break;
				case '8':  case 'k':
					if (--optn < 0)
					{
						optn = max_opt;
					}
					break;
				case '2':  case 'j':
					if (++optn > max_opt)
					{
						optn = 0;
					}
					break;
				case 13:  case 32:  case 't':	/* Allow return, space and t to toggle some options */
				case '4':  case 'h':
				case '6':  case 'l':
				{
					switch (optn)
					{
						case 0:	/* Gamma correction */
							gamma += adj * 0.05;
							if (gamma > 9.00)
							{
								gamma = 9.00;
							}
							if (gamma < 0.05)
							{
								gamma = 0.05;
							}
							Term_xtra(TERM_XTRA_REACT, 0);
#ifndef FULLSCREEN_ONLY
							set_gamma_window_state();
#endif /* FULLSCREEN_ONLY */							
							/* flush(); */
							Term_fresh();
							break;
						case 1:	/* Force monochrome */
							force_mono = !force_mono;
							if (fullscreen_font)
								redraw_areaFS(0, 0, 80, 24);
							else
								Term_xtra(TERM_XTRA_REACT, 0);
							/* flush(); */
							Term_fresh();
							break;
						case 2:	/* Sound enable / disable */
							enable_sound = !enable_sound;
#ifndef FULLSCREEN_ONLY
							set_sound_window_state();
#endif /* FULLSCREEN_ONLY */
							if (enable_sound)
							{
								initialise_sound();
							}
							break;
						case 3:	/* Sound volume */
							sound_volume += adj;
							if (sound_volume < SOUND_VOL_MIN)
								sound_volume = SOUND_VOL_MIN;
							if (sound_volume > SOUND_VOL_MAX)
								sound_volume = SOUND_VOL_MAX;
#ifndef FULLSCREEN_ONLY
							set_sound_window_state();
#endif /* FULLSCREEN_ONLY */
							break;
						case 4:	/* Start fullscreen */
							start_fullscreen = !start_fullscreen;
							if (start_fullscreen)
								enter_fullscreen_mode();
							else if (!minimise_memory)
								leave_fullscreen_mode();
							break;
						case 5:	/* Start fullscreen */
							use_glass = !use_glass;
							if (!use_glass)
							{
								if (glass_on)
								{
									Hourglass_Off();
								}
								glass_on = 0;
							}
							break;
						case 6:	/* hack flush */
							hack_flush = !hack_flush;
							break;
						case 7:	/* Alarm on/off */
							alarm_type += adj;
							if (adj)
							{
								alarm_modified = 1;
							}
							if (alarm_type > 2)
							{
								alarm_type = 0;
							}
							if (alarm_type < 0)
							{
								alarm_type = 2;
							}
							if (!alarm_type && alarm_disp)
							{
								ack_alarm();
							}	/* XXXXX Cancel an already active alarm? */
							break;
						case 8:	/* Alarm hours */
							alarm_h += adj;
							if (adj)
							{
								alarm_modified = 1;
							}
							if (alarm_h < 0)
							{
								alarm_h += 24;
							}
							if (alarm_h > 23)
							{
								alarm_h -= 24;
							}
							if (alarm_disp)
							{
								ack_alarm();
							}
							break;
						case 9:	/* Alarm minutes */
							alarm_m += adj;
							if (adj)
							{
								alarm_modified = 1;
							}
							if (alarm_m < 0)
							{
								alarm_m += 60;
							}
							if (alarm_m > 59)
							{
								alarm_m -= 60;
							}
							if (alarm_disp)
							{
								ack_alarm();
							}
							break;
						case 10:
							alarm_modified = 1;
							do_alarm_message_input(18);
							break;
						case 11:
							alarm_modified = 1;
							alarm_beep = !alarm_beep;
							break;
						case 12:
							show_debug_info();
							redraw_mung = 1;
							k = 27;
							break;
					}
				}
			}
		}
		while (k != 27);
	}
	while (redraw_mung);

	/* Rehack the alarm type: */
	if (alarm_type == 1)
	{
		alarm_type = 3;
	}

	if (alarm_modified)
	{
		write_alarm_choices();
	}

	Term_set_cursor(cursor_state);

	/* Restore the screen */
	Term_load();

	/* Don't need to preserve FP regs any more */
	event_mask.data.keepfpregisters = 0;

	/*
	   | Hack: tell the desktop front end that we're done.
	 */
	user_menu_active = FALSE;

	return 0;
}




/*--------------------------------------------------------------------------*/

#ifdef USE_FILECACHE

/*
 | 'Random' File-cacheing for *band.
 |
 | Rewritten since as of Zang 225 the mechanism for handling
 | these files has changed dramatically and the old system
 | is no longer viable.
 |
 | These new functions basically provide an alternative to the
 | normal my_fopen() (or fopen()) and my_fgets() functions.
 |
 | To use the file caching it is therefore necessary to alter
 | files.c to call cached_fopen(), cached_fclose() and cached_fgets()
 | rather than the normal functions.
 |
 | Note that these funtions will only work for files that are intended
 | to be read as a series of \n terminated lines of ASCII text using my_fgets().
 |
 */

/*
 | Hack: use the game's dynamic area if possible:
 */
#define fc_malloc(X) (g_malloc(X))
#define fc_free(X) (g_free(X,0))

#ifndef ABBR_FILECACHE
/*
 | Make these to do nothing.  They'll never
 | be called anyway.  Having them present makes
 | for neater code later on (ie. we use a variable
 | rather than the pre-processor to decide whether
 | to do compression).
 */
static int compress_string(char *os, char *s)
{
	core("main-acn internal logic error 001");
	return 0;
}
static int decompress_string(char *d, char *s, int max_len)
{
	core("main-acn internal logic error 002");
	return 0;
}
static int compressed_length(char *s)
{
	core("main-acn internal logic error 003");
	return 0;
}

#else

/*
 | When caching files we try to use some abbreviations.
 | We use both whole words and pairs of letters.
 | NB: For this to work, the file must contain only
 | 7 bit characters.
 */
static char *abbrv_w[] =
{
	/* These words all begin with a space */
	" of ", " the ", " you ", " to ", " a ", " says", " is ", " that ", " and ",
	" your ", " are ", " it ", " be ", " for ", " me", " will ", " in ",
	" not ", " this ", " have ", " can ", " on ", " my ", " with ", " say ",
	" all", " by ", " get ", " but ", " just ", " die", " as ", " time ",
		" if ",
	" like ",
	/* These words do not */
	"I ", "The ", "You ", "They ", "It ", "don", 0
};

/* Number of words */
#define FC_ABBRV_NUMWORDS 41

/* Number of them that don't start with a space */
#define FC_ABBRV_NONSPC   6

/*
 | NB: No letter pair may start with \0.
 */
static char abbrv_lp[] =
	"e  ttht s heiner aoure'\0, anonf  sd y r  ongofator.\0"
	"n arllstha wes m ieaisen bl  yndtoo yometele d f hve"
	"ayuralitneelN: chig ilroassaseliti lraa otedbede 'ri" "..u  nntno!'ee\0\0";


/*
 | Compress the given string using the abbreviation tables above.
 | Returns compressed length *including* terminator (it may
 | be part of an abbreviation, you see...)
 | Note that we can compress the string in-place, ie. 'os' may be
 | the same as 's'.
 */
static int compress_string(char *os, char *s)
{
	char *o, *f, *d;
	int i;

	o = os;

	while (*s)
	{
		int fw, lw;
		if (*s == ' ')
		{
			fw = 0;
			lw = FC_ABBRV_NUMWORDS - FC_ABBRV_NONSPC;
		}
		else
		{
			fw = FC_ABBRV_NUMWORDS - FC_ABBRV_NONSPC;
			lw = FC_ABBRV_NUMWORDS;
		}
		for (i = fw; i < lw; i++)
		{
			d = abbrv_w[i];		/* Word to check against */
			for (f = s; *f && *f == *d; f++, d++)
				;
			if (*d == 0)		/* Match? */
			{
				s = *f ? f : f - 1;	/* Update string pointer */
				*o++ = 128 + i;	/* store code */
				break;			/* Quit looking for words */
			}
		}

		/* Do we need to check the letter pairs? */
		if (i == lw)
		{
			for (i = 0; abbrv_lp[i]; i += 2)
			{
				if (s[0] == abbrv_lp[i] && s[1] == abbrv_lp[i + 1])
				{
					*o++ = 128 + FC_ABBRV_NUMWORDS + i / 2;
					/* NB: If the next character is the terminator then we're done. */
					if (!s[1])
					{
						return (o - os);
					}
					s += 2;		/* Quit looking for letters */
					break;
				}
			}
			/* NB: This next check is only safe because no letter pair starts with a NULL */
			if (!abbrv_lp[i])
				*o++ = *s++;
		}
	}

	/* Don't forget that terminator! */
	*o++ = 0;

	return o - os;
}

/*
 | As compress_string (above), but stores nothing and
 | only returns the length of the compressed string.
 */
static int compressed_length(char *s)
{
	char *f, *d;
	int i, l;

	l = 0;
	while (*s)
	{
		int fw, lw;
		if (*s == ' ')
		{
			fw = 0;
			lw = FC_ABBRV_NUMWORDS - FC_ABBRV_NONSPC;
		}
		else
		{
			fw = FC_ABBRV_NUMWORDS - FC_ABBRV_NONSPC;
			lw = FC_ABBRV_NUMWORDS;
		}
		for (i = fw; i < lw; i++)
		{
			d = abbrv_w[i];
			for (f = s; *f && *f == *d; f++, d++)
				;
			if (*d == 0)		/* Match? */
			{
				s = *f ? f : f - 1;	/* Update string pointer */
				l++;			/* increment output length */
				break;			/* Quit looking for words */
			}
		}

		/* Do we need to check the letter pairs? */
		if (i == lw)
		{
			for (i = 0; abbrv_lp[i]; i += 2)
			{
				if (s[0] == abbrv_lp[i] && s[1] == abbrv_lp[i + 1])
				{
					l++;		/* increment output length */
					/* NB: If the next character is the terminator then we're done. */
					if (!s[1])
					{
						return l;
					}
					s += 2;		/* Quit looking for letters */
					break;
				}
			}
			/* NB: This next check is only safe because no letter pair starts with a NULL */
			if (!abbrv_lp[i])
			{
				l++;
				s++;
			}
		}
	}
	/* Don't forget that terminator! */
	return l + 1;
}



/*
 | Decompress the given string 's' into the buffer at 'd'.
 | At most, max_len characters (incl. \0 terminator) will be
 | written into d.
 | Returns the length of 's'.
 */
static int decompress_string(char *d, char *s, int max_len)
{
	char *os = s;

	while (max_len > 1)
	{
		int nc = *s++;	/* Get next character */

		if (nc < 128)			/* Is it a plain character? */
		{
			if (0 == (*d++ = nc))
			{
				break;
			}
			max_len--;
		}
		else					/* Abbreviation to expand. */
		{
			if (nc >= FC_ABBRV_NUMWORDS + 128)	/* Letter pair? */
			{
				*d++ = abbrv_lp[(nc - (FC_ABBRV_NUMWORDS + 128)) * 2];
				if (0 ==
					(*d++ = abbrv_lp[(nc - (FC_ABBRV_NUMWORDS + 128)) * 2 + 1]))
					break;
				max_len -= 2;
			}
			else				/* It's a word */
			{
				char *ws = abbrv_w[nc - 128];
				while (*ws && max_len > 1)
				{
					*d++ = *ws++;
					max_len--;
				}
			}
		}
	}

	/* Skip over the rest of the abbreviated string if we ran out of space */
	if (max_len <= 1)			/* Out of space? */
	{
		int nc;
		*d = 0;					/* Terminate */
		do
		{
			nc = *s++;			/* Next char */
			if (nc >= 128 + FC_ABBRV_NUMWORDS)	/* Ignore words */
				nc = abbrv_lp[(nc - (FC_ABBRV_NUMWORDS + 128) * 2) + 1];	/* Only check 2nd letter of pair */
		}
		while (nc);
	}
	return s - os;				/* Length of abbreviated string */
}

#endif /* ABBR_FILECACHE */


/* Each entry in the cache looks like this: */
typedef struct fce_
{
	char *name;	/* canonical pathname of file */
	char *text;	/* text (be it compressed or not) */
	char *eof;	/* byte beyond the last byte of text */
	int used;	/* access counter when the file was last used */
	int compressed;	/* compression method, (ie. 0 for none or 1 for abbreviations) */
}
FileCacheEntry;

/*
 | The handles we chuck around are pointers to one of these structs.
 | Note that since we actually return (and take) |FILE*|s we just
 | compare the value of a |FILE*| with the limits of the array of
 | |CachedFileHandle|s to decide whether its 'ours' or a genuine
 | (ie. stdio) file handle.  This /is/ pretty lame, I know...
 */
typedef struct cfh_
{
	char *ptr;	/* sequential file pointer, as it were */
	FileCacheEntry *fce;	/* ->the file-cache entry data */
}
CachedFileHandle;

#define MAX_OPEN_CACHED_FILES	16	/* We allow up to 16 of these files open at once */
#define MAX_CACHE_ENTRIES		64	/* We allow up to 64 cache entries */

static FileCacheEntry *file_cache;	/* to be used as file_cache[MAX_CACHE_ENTRIES] */
static CachedFileHandle *cached_file_handle;	/* to be used as cached_file_handle[MAX_OPEN_CACHED_FILES] */
static int file_cache_initd = 0;	/* Is the cache initialised? */
static int file_cache_size = 0;	/* Total size of the cached files */
static int fc_access_counter = 1;	/* incremented on each cache access */
/*
 | Pre-calculate max. possible value of a FILE* (ie. address in memory)
 | that could be a valid |CachedFileHandle*|.
 */
static FILE *max_cfh_addr;	/* == (FILE*) (&(cached_file_handle[MAX_OPEN_CACHED_FILES-1])) */

/*
 | Initialise the file cache
 */
static void init_file_cache(void)
{
	int i;

	/* Allocate storage */
	file_cache = fc_malloc(MAX_CACHE_ENTRIES * sizeof(FileCacheEntry));
	cached_file_handle =
		fc_malloc(MAX_OPEN_CACHED_FILES * sizeof(CachedFileHandle));

	if (!file_cache || !cached_file_handle)
	{
		/* Disable file-caching */
		if (file_cache)
		{
			fc_free(file_cache);
		}
		if (cached_file_handle)
		{
			fc_free(cached_file_handle);
		}
		use_filecache = 0;
	}
	else
	{
		/* Initialise the cache */
		for (i = 0; i < MAX_CACHE_ENTRIES; i++)
			file_cache[i].name = NULL;
		for (i = 0; i < MAX_OPEN_CACHED_FILES; i++)
			cached_file_handle[i].fce = NULL;
		fc_access_counter = 1;
		file_cache_size = 0;
		max_cfh_addr =
			(FILE *)(&(cached_file_handle[MAX_OPEN_CACHED_FILES - 1]));
	}
	file_cache_initd = 1;
}


/*
 | Helper: take a copy of the string and return a pointer to it
 */
static char *string_cpy(char *s)
{
	char *d = fc_malloc(strlen(s) + 1L);
	if (d)
	{
		strcpy(d, s);
	}
	return d;
}


/*
 | Cache the specified file, returning either the cache entry
 | that it has been cached at, or NULL for failure.
 |
 | Note that for the abbreviated file cache a temporary file
 | is used to allow the compression to be applied just once.
 | (otherwise it has to be done twice - once to determine the
 | eventual compressed size and once to actually store and compress
 | it).
 */
static FileCacheEntry *cache_file(char *name)
{
	int i, size = 0;
	FILE *fp;
	char buffer[1024];
	char *d;

	FILE *tf = NULL;	/* Used if abbr_filecache and abbr_tmpfile are set */
	char cfn[1024];	/* Used if abbr_filecache and abbr_tmpfile are set */

	/* Find the first free slot in the cache */
	for (i = 0; i < MAX_CACHE_ENTRIES; i++)
		if (!file_cache[i].name)
			break;

	/* No more entries? */
	if (i >= MAX_CACHE_ENTRIES)
	{
		return NULL;
	}

	/* Set up the info on the file */
	if ((file_cache[i].name = string_cpy(name)) == NULL)
	{
		return NULL;
	}

	/* Open the file */
	fp = my_fopen(name, "r");
	if (!fp)
	{
		fc_free(file_cache[i].name);
		file_cache[i].name = 0;
		return NULL;
	}

	/* Open/create tempfile if need be: */
	if (abbr_filecache && abbr_tmpfile)
	{
		/* Hack: Form the pathname of the cached compressed file (in canonical form) */
		sprintf(cfn, "%s%s", scrap_path,
				riscosify_name(name + strlen(resource_path)));
		/* Ensure that that particular directory exists... */
		ensure_path(cfn);
		/* Check whether cache file is out of date */
		if (file_is_newer(riscosify_name(name), cfn))
		{
			tf = fopen(cfn, "wb");
			size = 0;
		}
		else
		{
			tf = fopen(cfn, "rb");
			if (tf)
			{
				size = myFile_Size(cfn);
			}
		}
	}

	/* If we don't have the cached file (but want it), compress the source text to it */
	if (tf)
	{
		if (!size)
		{
			int k;
			while (!my_fgets(fp, buffer, sizeof(buffer)))
			{
				if (smart_filecache && (!*buffer || *buffer == '#'))
					continue;
				k = compress_string(buffer, buffer);
				if (fwrite(buffer, 1, k, tf) != k)
				{
					fclose(tf);
					remove(cfn);
					core("error writing tempfile");
				}
				size += k;
			}
			fclose(tf);
			tf = fopen(cfn, "rb");
		}
	}
	else
	{
		/* Count the number of bytes */
		while (!my_fgets(fp, buffer, sizeof(buffer)))
		{
			if (smart_filecache && (!*buffer || *buffer == '#'))
				continue;
			if (abbr_filecache)
				size += compressed_length(buffer);
			else
				size += strlen(buffer) + 1;
		}
	}

	/* Close the (source) file */
	my_fclose(fp);

	/* Allocate enough storage for the text */
	file_cache[i].text = fc_malloc(size + 1L);
	if (!file_cache[i].text)
	{
		fc_free(file_cache[i].name);
		file_cache[i].name = 0;
		if (tf)
		{
			fclose(tf);
		}
		return NULL;
	}

	/* Do we have a tempfile to load? */
	if (tf)
	{
		if (fread(file_cache[i].text, 1, size, tf) != size)
			core("error reading tempfile");
		fclose(tf);
	}
	else
	{
		/* Re-open the file... */
		fp = my_fopen(name, "r");
		if (!fp)
		{
			fc_free(file_cache[i].name);
			fc_free(file_cache[i].text);
			file_cache[i].name = 0;
			return NULL;
		}

		/* And read it into the buffer... */
		d = file_cache[i].text;
		while (!my_fgets(fp, buffer, sizeof(buffer)))
		{
			if (smart_filecache && (!*buffer || *buffer == '#'))
				continue;
			if (abbr_filecache)
				d += compress_string(d, buffer);
			else
			{
				strcpy(d, buffer);
				d += strlen(buffer) + 1;
			}
		}

		if ((d - file_cache[i].text) != size)
		{
			debug("Calculated size is %d, pointer offset is %d", size,
				  (d - file_cache[i].text));
			core("Cached file is larger than calculated!");
		}

		/* Close the file */
		my_fclose(fp);
	}

	/* Set up the 'last accessed' value, etc. */
	file_cache[i].used = fc_access_counter++;
	file_cache[i].eof = file_cache[i].text + size;
	file_cache[i].compressed = abbr_filecache;
	file_cache_size += size;

	/* Return success */
	return &(file_cache[i]);
}


/*
 | Discard a file from the cache
 */
static void discard_cached_file(int i)
{
	if (!file_cache[i].name)
	{
		return;
	}							/* invalid request */
	fc_free(file_cache[i].text);
	fc_free(file_cache[i].name);
	file_cache_size -= (file_cache[i].eof) - (file_cache[i].text);
	file_cache[i].name = 0;
}


/*
 | Attempt to flush as much of the cache as required
 | to bring it within the size limit.
 | If protect != 0 then that entry in the cache won't be flushed.
 */
static void flush_file_cache(FileCacheEntry * protect)
{
	int i, j, done;
	int oldest_u, oldest_e;
	FileCacheEntry *fce;
	int needed = (4 << 10);	/* Hack: try to free at least 4K */

	done = (file_cache_size + needed) <= max_file_cache_size;

	while (!done)
	{
		oldest_u = fc_access_counter;
		oldest_e = -1;

		fce = file_cache;
		/* Find oldest entry that isn't in use */
		for (i = 0; i < MAX_CACHE_ENTRIES; fce++, i++)
		{
			if (fce == protect)
			{
				continue;
			}					/* Hack ;) */
			if (fce->name)		/* Is this cache slot full? */
			{
				for (j = 0; j < MAX_OPEN_CACHED_FILES; j++)
					if (cached_file_handle[j].fce == fce)
						break;
				if (j < MAX_OPEN_CACHED_FILES)
					continue;	/* Cached file is still open */
				if (fce->used < oldest_u)
				{
					oldest_e = i;
					oldest_u = file_cache[i].used;
				}
			}
		}

		if (oldest_e < 0)
			done = 1;			/* We can flush nothing more */
		else
		{
			discard_cached_file(oldest_e);
			done = (file_cache_size + needed) <= max_file_cache_size;
		}
	}
}


/*
 | Locate the specified file within the cache.
 | Returns NULL if the file is not cached
 */
static FileCacheEntry *find_cached_file(char *name)
{
	int i;
	FileCacheEntry *fce = file_cache;

	for (i = 0; i < MAX_CACHE_ENTRIES; i++, fce++)
	{
		if (fce->name)
			if (streq(fce->name, name))
				return fce;
	}
	return NULL;
}



/*--------------------------------------------------------------------------*/
/* Externally visible file cache stuff										*/
/*--------------------------------------------------------------------------*/

/*
 | Open a file...
 | Returns the file cache handle of the file, or NULL for failure.
 | Note that if mode is anything other than "r" the call defers to
 | my_fopen().
 |
 | NB: The returned handle is almost certainly *NOT* a |FILE*|
 | (although it may be if the cache cannot accomodate the file).
 |
 | Therefore, you *MUST* ensure that any file opened with cached_fopen()
 | is only ever accessed via cached_fgets() and cached_fclose().
 |
 | Failure to do so will result in, ahem, unpleasantness.  Extreme
 | unpleasantness.
 */
FILE *cached_fopen(char *name, char *mode)
{
	FileCacheEntry *fcs = NULL;
	int fch;

	if (strcmp(mode, "r") || !use_filecache)
		return my_fopen(name, mode);

	if (!file_cache_initd)
	{
		init_file_cache();
	}

	if (max_file_cache_size >= 0)
	{
		/* Find a free cache entry */
		for (fch = 0; fch < MAX_OPEN_CACHED_FILES; fch++)
			if (!cached_file_handle[fch].fce)
				break;

		/* Out of handles? */
		if (fch >= MAX_OPEN_CACHED_FILES)
			return my_fopen(name, mode);

		/* Is the file already cached? */
		fcs = find_cached_file(name);
		if (!fcs)
		{
			/* File wasn't cached, so cache it */
			flush_file_cache(NULL);	/* Clean stuff out of the cache if need be */
			fcs = cache_file(name);	/* Cache the new file */
			flush_file_cache(fcs);	/* Flush, but keep the latest file */
		}
	}

	/* Did we fail to cache the file? */
	if (!fcs)
	{
		return my_fopen(name, mode);
	}

	/* File was cached OK */
	cached_file_handle[fch].ptr = fcs->text;	/* Init sequential pointer */
	cached_file_handle[fch].fce = fcs;	/* Cache block pointer */
	fcs->used = fc_access_counter++;	/* Opening the file counts as an access */

	return (FILE *)(&cached_file_handle[fch]);
}


/*
 | Close a file
 */
errr cached_fclose(FILE *fch_)
{
	CachedFileHandle *fch;

	/* Is the FILE* genuine? */
	if ((fch_ < (FILE *)cached_file_handle) || (fch_ > max_cfh_addr))
		return my_fclose(fch_);

	fch = (CachedFileHandle *) fch_;

	/* Check for "Ooopses": */
	if (fch->fce == NULL)
		core("cached_fclose called on a non-open file handle");

	flush_file_cache(NULL);		/* Clean out the cache if need be */
	fch->fce = NULL;			/* Mark file handle as inactive */

	return 0;
}


/*
 | Do the my_fgets thing on a file
 */
errr cached_fgets(FILE *fch_, char *buffer, int max_len)
{
	CachedFileHandle *fch;
	char *eof;
	char *ptr;

	/* Is the FILE* genuine? */
	if ((fch_ < (FILE *)cached_file_handle) || (fch_ > max_cfh_addr))
		return my_fgets(fch_, buffer, max_len);

	fch = (CachedFileHandle *) fch_;

	/* Check for "Oopses": */
	if (!file_cache_initd)
		core("cached_fgets() on uninitialised file-cache");
	if (!fch->fce)
		core("cached_fgets called for a un-open file");

	eof = fch->fce->eof;
	ptr = fch->ptr;

	/* Out of bounds? */
	if (ptr >= eof)
	{
		return 1;
	}							/* Read failed */

	/*
	   | Read the next line, up to \0 (which would have v=been \n in the original file),
	   | or max_len-1 characters
	 */
	if (fch->fce->compressed)
		ptr += decompress_string(buffer, ptr, max_len);
	else
	{
		if (eof - ptr < max_len)
		{
			max_len = eof - ptr;
		}
		for (; max_len >= 1; max_len--)
			if ((*buffer++ = *ptr++) == 0)
				break;
		*buffer = 0;			/* terminate (paranoia) */
	}

	/* Update sequential pointer */
	fch->ptr = ptr;

	return 0;
}

#endif /* USE_FILECACHE */


/*
 | This section deals with checking that the .raw files are up to date
 | wrt to the .txt files.  Note that this function won't work for RISC OS 2
 | (due to the lack of OS_Args 7) so it simply returns 0 to indicate that
 | the file isn't OOD.
 |
 | For this to work, the equivalent function (in init2.c) needs to be
 | #if-d out (and this function should be declared).  You'll probably
 | also need to zap the UNIX #includes at the top of the file
 */

extern errr check_modification_date(int fd, cptr template_file)
{
	char raw_buf[1024];
	char txt_buf[1024];
	int i;
	os_error *e;

	if (os_version() < 300)
	{
		return 0;
	}

	/* Use OS_Args 7 to find out the pathname 'fd' refers to */
	e = SWI(6, 0, SWI_OS_Args,
			/* In: */
			7,					/* Get path from filehandle */
			fd,					/* file handle */
			raw_buf,			/* buffer */
			0, 0,				/* unused */
			1024				/* size of buffer */
			/* No output regs used */
		);
	if (e)
	{
		core(e->errmess);
	}

	/* Build the path to the template_file */
	path_build(txt_buf, sizeof(txt_buf), ANGBAND_DIR_EDIT, template_file);

	i = file_is_newer(riscosify_name(txt_buf), raw_buf);

	return i;
}


/*--------------------------------------------------------------------------*/

/*
 | This is the hideous IClear hack.  Basically what we do is to take a copy
 | of the module and kill it in the RMA.  Then, on return to the desktop
 | we reinstate the module.
 |
 | NB: This is truly, truly evil.
 */

static int *iclear_module = NULL;

/*
 | Start the IClear hack
 */
static void iclear_hack(void)
{
	os_error *e;
	int code = 0;
	int *i;

	/* Get base address of module */
	e = SWI(2, 4, SWI_OS_Module, 18, "IClear", 0, 0, 0, &code);
	if (e || !code)
		return;

	/* Module size is at code!-4 */
	i = (int *)code;
	iclear_module = malloc(i[-1] + 4);
	if (!iclear_module)
		return;

	/* Copy the module */
	*iclear_module = i[-1];
	memcpy(iclear_module + 1, (void *)code, i[-1]);

	/* Kill the current version */
	e = SWI(2, 0, SWI_OS_Module, 4, "IClear");
	if (e)
	{
		free(iclear_module);
		iclear_module = NULL;
	}
}


/*
 | Remove the IClear hack
 */
static void remove_iclear_hack(void)
{
	os_error *e;

	if (!iclear_module)
		return;

	e = SWI(3, 0, SWI_OS_Module, 11, iclear_module + 1, *iclear_module);
	if (e)
		debug("Failed to reinstall IClear: %s", e->errmess);

	free(iclear_module);
	iclear_module = NULL;
}



/*--------------------------------------------------------------------------*/

/* Alarm functions */
static int alarm_ackd = 0;	/* has the alarm been acknowledged? */
static window_handle aw = 0;	/* alarm window */

/*
 | Is the alarm due to go off, ie. is it enabled, and if so
 | does the current time match the alarm time?
 */
static void check_alarm()
{
	time_t t;
	struct tm *lt;

	alarm_lastcheck = Time_Monotonic();

	time(&t);
	lt = localtime(&t);
	if (lt->tm_hour == alarm_h && lt->tm_min == alarm_m)
	{
		if (!alarm_ackd) alarm_disp = 1;
	}
	else
	{
		alarm_ackd = 0;
	}

	/* Hack: if the alarm has already been acknowledged then don't re-trigger it */
	if (alarm_ackd)
	{
		alarm_disp = 0;
	}

	/* Hack: if the alarm should make a noise, then make one: */
	if (alarm_disp && alarm_beep == 1)
	{
		static unsigned int last_beep = 0;
		unsigned int t = Time_Monotonic();
		if (t >= last_beep + 100)
		{
			Sound_SysBeep();
			last_beep = t;
		}
	}

	/*
	   | If we're in the desktop then fire the alarm off if need be.
	   | If we aren't then do nothing - the fullscreen bored() function
	   | will take care of the alarm.
	 */
#ifndef FULLSCREEN_ONLY
	if (!fullscreen_font && alarm_disp)
		trigger_alarm_desktop();
#endif /* FULLSCREEN_ONLY */		
}


static void ack_alarm(void)
{
	if (aw)
	{
		Window_Delete(aw);
		aw = 0;
	}
	alarm_ackd = 1;
	alarm_disp = 0;

	if (alarm_type == 3)
	{
		/* One shot alarm */
		alarm_type = 0;
		write_alarm_choices();
	}

}

#ifndef FULLSCREEN_ONLY
/*
 | Click in the (desktop) alarm window
 */
static BOOL Hnd_AlarmClick(event_pollblock * pb, void *ref)
{
	ack_alarm();
	return TRUE;
}


/*
 | The alarm has gone off in the desktop
 */
static void trigger_alarm_desktop(void)
{
	char buffer[120];
	if (aw)
		return;

	aw = Window_Create("alarm", template_TITLEMIN);
	if (!aw)
	{
		core("failed to create Alarm window!");
	}
	sprintf(buffer, "Alarm from %s", VARIANT);
	Window_SetTitle(aw, buffer);
	Event_Claim(event_CLICK, aw, 0, Hnd_AlarmClick, NULL);
	Event_Claim(event_CLOSE, aw, event_ANY, Hnd_AlarmClick, NULL);

	Icon_printf(aw, 1, "An alarm was set for %02d:%02d", alarm_h, alarm_m);
	Icon_SetText(aw, 2, alarm_message);
	Window_Show(aw, open_CENTERED);
}

#endif /* FULLSCREEN_ONLY */


/*--------------------------------------------------------------------------*/

#ifndef FE_DEBUG_INFO
static void show_debug_info(void)
{
	core("main-acn internal logic error 004");
}
#else

static int debug_cx = 0;
static int debug_cy = 0;
static int debug_cl = TERM_WHITE;
static int debug_sl = 0;


static void debug_cls(void)
{
	Term_clear();
	debug_cx = debug_cy = debug_sl = 0;
}

static void debug_tcol(int c)
{
	debug_cl = c;
}


static void debug_scroll(void)
{
	char **c = ((term_data *)Term)->t.scr->c;	/* char array [24][80] */
	byte **a = ((term_data *)Term)->t.scr->a;	/* attr array [24][80] */
	int cc;
	char tmp[82];
	int y, x, p;

	cc = a[1][0];

	for (y = 1; y < 23; y++)
	{
		for (x = p = 0; x < 80; x++)
		{
			if (a[y][x] != cc)
			{
				tmp[p] = 0;
				Term_putstr(x - p, y - 1, p, cc, tmp);
				p = 0;
				cc = a[y][x];
			}
			tmp[p++] = c[y][x];
		}
		Term_putstr(x - p, y - 1, p, cc, tmp);
	}

	Term_erase(0, 22, 80);
}


static void debug_print_line(char *l)
{
	char *le;
	int cr = 0;

	/* Handle scrolling */
	if (debug_cy > 22)
	{
		debug_cy = 22;
		if (--debug_sl < 0)
		{
			int k;
			display_line(0, 23, TERM_YELLOW, "[RET one line, SPC one page]");
			do
			{
				k = inkey();
			}
			while (k != 32 && k != 13);
			Term_erase(0, 23, 79);
			debug_sl = k == 32 ? 21 : 0;
		}
		debug_scroll();
	}

	/* Hack: check for NL */
	for (le = l; *le; le++)
		if (*le == '\n')
		{
			cr = 1;
			break;
		}

	/* display text */
	Term_putstr(debug_cx, debug_cy, le - l, debug_cl, l);

	/* move cursor */
	if (!cr)
	{
		debug_cx += (le - l);
		if (debug_cx >= 80)
		{
			cr = 1;
		}
	}
	if (cr)
	{
		debug_cx = 0;
		debug_cy += 1;
	}

	Term_gotoxy(debug_cx, debug_cy);

}


static int debug_next_line(char *lb, char **t, int cx)
{
	int i = 0;
	char *lt = *t;

	if (!*lt)
	{
		return -1;
	}							/* Out of text */

	while (*lt && cx < 80)
	{
		lb[i] = *lt++;

		if (lb[i] == '\n')		/* New line */
		{
			cx = 0;				/* Cursor x will be 0 after displaying */
			i++;				/* Keep the \n in the output */
			break;				/* All done */
		}
		else if (lb[i] == '\t')	/* Tab */
		{
			while (cx < 80)
			{
				lb[i++] = ' ';
				cx++;
				if ((cx & 7) == 0)
				{
					break;
				}
			}
		}
		else					/* Anything else */
		{
			cx++;
			i++;
		}
	}

	lb[i] = 0;					/* terminate line buffer */
	*t = lt;					/* update text pointer */
	return cx;					/* return cursor x after printing */
}

static void debug_printf(char *fmt, ...)
{
	char buffer[1024];
	char line[82];
	va_list ap;
	char *p = buffer;

	va_start(ap, fmt);
	vsprintf(buffer, fmt, ap);
	va_end(ap);

	/* Now split the string into display lines */
	while (debug_next_line(line, &p, debug_cx) >= 0)
		debug_print_line(line);
}


static void debug_version_info(void)
{
	debug_tcol(TERM_YELLOW);

	debug_printf("\n\nMisc. Info:\n");
	debug_tcol(TERM_WHITE);
	debug_printf("\tVariant name = \"%s\"\n", VARIANT);
	debug_printf("\tFront-end version: %s\n", PORTVERSION);
	debug_printf("\tFront-end compiled: %s %s\n", __TIME__, __DATE__);
	debug_printf("\tCompile time flags:\n");

#ifdef USE_FILECACHE
	debug_printf("\t\tUSE_FILECACHE\n");
#endif

#ifdef ABBR_FILECACHE
	debug_printf("\t\tABBR_FILECACHE\n");
#endif

#ifdef SMART_FILECACHE
	debug_printf("\t\tSMART_FILECACHE\n");
#endif

	debug_tcol(TERM_YELLOW);
	debug_printf("\nResource path:\n");
	debug_tcol(TERM_WHITE);
	debug_printf("\t\"%s\"\n", resource_path);

	debug_tcol(TERM_YELLOW);
	debug_printf("\nTempfile path:\n");
	debug_tcol(TERM_WHITE);
	debug_printf("\t\"%s\"\n", scrap_path);
	debug_printf("\tScrapfiles are %s deleted at exit.\n",
				 (flush_scrap ? "" : "NOT"));

	debug_tcol(TERM_YELLOW);
	debug_printf("\nChoices files:\n");
	debug_tcol(TERM_L_BLUE);
	debug_printf("\tDesired files:\n");
	debug_tcol(TERM_WHITE);
	debug_printf("\tPrimary (r/w): \"%s\"\n", choices_file[CHFILE_WRITE]);
	debug_printf("\t Fallback (r): \"%s\"\n", choices_file[CHFILE_READ]);
	debug_printf("\t Mirror (r/w): \"%s\"\n", choices_file[CHFILE_MIRROR]);
	debug_tcol(TERM_L_BLUE);
	debug_printf("\tActual files:\n");
	debug_tcol(TERM_WHITE);
	debug_printf("\t		Write: \"%s\"\n", find_choices(TRUE));
	debug_printf("\t		 Read: \"%s\"\n", find_choices(FALSE));

	debug_tcol(TERM_YELLOW);
	debug_printf("\nAlarm files:\n");
	debug_tcol(TERM_L_BLUE);
	debug_printf("\tDesired files:\n");
	debug_tcol(TERM_WHITE);
	debug_printf("\tPrimary (r/w): \"%s\"\n", alarm_file[CHFILE_WRITE]);
	debug_printf("\t Fallback (r): \"%s\"\n", alarm_file[CHFILE_READ]);
	debug_tcol(TERM_L_BLUE);
	debug_printf("\tActual files:\n");
	debug_tcol(TERM_WHITE);
	debug_printf("\t		Write: \"%s\"\n", find_alarmfile(TRUE));
	debug_printf("\t		 Read: \"%s\"\n", find_alarmfile(FALSE));
#ifdef USE_DA
	debug_tcol(TERM_YELLOW);
	debug_printf("\nDynamic areas:\n");
	debug_tcol(TERM_WHITE);
	debug_printf("\tFontcache DA = %d\t", font_area);
	debug_printf("size = %d\theap size = %d\n", font_area_size, font_heap_size);
	debug_printf("\t   ralloc DA = %d\t", game_area);
	debug_printf("size = %d\theap size = %d\n", game_area_size, game_heap_size);
#endif
}


static void debug_filecache_info(void)
{
#ifndef USE_FILECACHE
	debug_tcol(TERM_L_DARK);
	debug_printf("File cache disabled at compile time.\n");
#else
	int j, k;
	int t, cs, ucs, cf;

	cf = cs = ucs = j = k = 0;	/* To stop an usused warning if USE_FILECACHE is undefined */
	t = strlen(resource_path);

	if (!file_cache_initd)
	{
		init_file_cache();
	}							/* Paranoia */
	debug_tcol(TERM_YELLOW);
	debug_printf("\nFilecache contents:\n");
	debug_tcol(TERM_L_BLUE);
	debug_printf("Flags: Smart=%d;  Abbrv=%d;  Slave=%d;   Enable=%d\n",
				 smart_filecache, abbr_filecache, abbr_tmpfile, use_filecache);
	debug_tcol(TERM_SLATE);
	if (smart_filecache || abbr_filecache)
		debug_printf("\t\t%3s  %6s/%-6s  %6s  %6s  Path (relative to lib/)\n",
					 "Hnd", "Cache", "Disc", "Time", "Status");
	else
		debug_printf("\t\t%3s  %6s  %6s  %6s  Path (relative to lib/)\n", "Hnd",
					 "Size", "Time", "Status");

	for (j = 0; j < MAX_CACHE_ENTRIES; j++)
	{
		FileCacheEntry *fce = &(file_cache[j]);
		if (fce->name)
		{
			cf++;
			debug_tcol(TERM_L_GREEN);
			debug_printf("\t\t%3d  ", j);
			debug_tcol(TERM_L_UMBER);
			if (!smart_filecache && !abbr_filecache)
				debug_printf("%6d  ", fce->eof - fce->text);
			else
			{
				debug_printf("%6d/", fce->eof - fce->text);
				k = myFile_Size(riscosify_name(fce->name));
				debug_printf("%-6d  ", k);
				if (k > 0)
				{
					ucs += k;
				}
			}
			cs += fce->eof - fce->text;
			debug_printf("%6d  ", fce->used);
			for (k = 0; k < MAX_OPEN_CACHED_FILES; k++)
				if (cached_file_handle[k].fce == fce)
					break;
			debug_tcol(TERM_RED);
			debug_printf("%-6s  ", k < MAX_OPEN_CACHED_FILES ? "Open" : "");
			debug_tcol(TERM_L_UMBER);
			debug_printf("%s\n", fce->name + t);
		}
	}

	debug_tcol(TERM_L_BLUE);
	debug_printf("\tTotal:\t%3d  ", cf);
	if (ucs)
		debug_printf("%6d/%-6d\n", cs, ucs);
	else
		debug_printf("%6d\n", cs);
	debug_tcol(TERM_BLUE);
#endif /* USE_FILECACHE */
}


static void show_debug_info(void)
{
	int k;
	/* blank the term */
	debug_cls();

	/* Repeatedly prompt for a command */
	do
	{
		debug_tcol(TERM_VIOLET);
		debug_printf("\nInfo: (V)ersion, (F)ilecache, ESC=exit ");
		do
		{
			k = inkey();
			switch (k)
			{
				case 'v':  case 'V': debug_version_info();
					break;
				case 'f':  case 'F': debug_filecache_info
						();
					break;
				case 27: break;
				default: k = 0;
			}
		}
		while (!k);
	}
	while (k != 27);

	Term_clear();
}

#endif /* FE_DEBUG_INFO */

#endif /* __riscos */


