/*
 * File: main-acn.c
 * StrongED$Mode=C
 */

#ifdef __riscos
#define PORTVERSION	"1.26 (02 Sep 1999)"	/* for the info box */

/*
 * Copyright (c) 1997/8/9 Musus Umbra, Ben Harrison, and others
 *
 * This software may be copied and distributed for educational, research,
 * and not for profit purposes provided that this copyright and statement
 * are included in all such copies.
 *
 */

/*
 | Purpose: Support for Acorn RISC OS Angband (and variants based on 2.8.x)
 |
 | Author: Musus Umbra <musus@argonet.co.uk>
 |
 | 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)
 |
 |     A suitable "!!Variant.h" file for the variant being compiled;
 |     this should simply #define the appropriate symbols.
 |
 | NB: The following symbols are *required* and *must* be defined properly:
 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 |
 | VARIANT		Name of this game eg. "Angband", "Zangband", etc.
 |				Note that this *must* match the entry in the !Variant Obey file
 |				and also that it must only contain characters that are valid as
 |				part of a RISC OS path variable, eg. "Yin-Yangband" is NOT okay.
 |
 | VERSION		Version num (and optionally the date) eg. "2.8.3" or "2.8.3 (06 Feb 1998)"
 | AUTHORS		For the info box eg. "Ben Harrison"
 | PORTERS		for the info box eg. "Musus Umbra"
 |
 | VFILETYPE	filetype of saved games eg. 0x118
 | 				As of rel 1.18 this may be overridden by supplying
 | 				a 3 digit hex number on the command line, eg.
 | 				"... -T118 ..."
 |
 | RESPATH		Resource path variable eg. "Angband$Path"
 |				This should be defined as VARIANT"$Path" unless you've
 |				altered my standard !Run file for some reason.
 |
 | RESDIR		Where templates are, eg. "Angband" ($Dir is assumed)
 |				Again, this should be defined as VARIANT unless !Run
 |				has been modified accordingly.
 |
 | ICONNAME		iconbar icon sprite name eg. "!angband"
 |
 | SOUNDPATH	Path to sound samples, eg. "AngbandSound:"
 |				Yet again, this should be defined as VARIANT unless !Run
 |				and/or !Angsound has been modified accordingly.
 |
 |
 | The following symbols may be required, depending on the variant itself:
 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 |
 | OLD_RFREE	define if rfree() should return an errr instead of a vptr
 |
 | PDEADCHK				should expand to an expression that is true if the
 |						player is dead (eg. (p_ptr->is_dead) for Angband
 |						or (!alive || dead) for Zang220).
 |						If this isn't defined then (p_ptr->is_dead) will be
 |						used.

 |
 | 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 probably also be used for files in lib/edit/
 |						although whether that's worth doing is debatable.
 |						(ISTR that an ealry Kamband repeatedly read a file from
 |						lib/edit when rolling up certain character classes).
 |
 | SMART_FILECACHE		SMARRT_FILECACHE 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, this symbol causes the smart file cache
 |						option to be on by default.
 |
 | 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.
 |
 |						(Irony: it's only really useful on old (ie.slow)
 |						machines on which the compression will suck speedwise).


 |
 | The following symbols control the debugging options:
 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 |
 | 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.
 */


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

#include "!!Variant.h"

/* Make FE_DEBUG_INFO into something we can use in boolean expressions */
#ifdef FE_DEBUG_INFO
#  define ALLOW_DEBUG_INFO 1
#else
#  define ALLOW_DEBUG_INFO 0
#endif

/* Deal with any un-defined (or odd) file-caching symbols */
#ifdef USE_FILECACHE
#  define ALLOW_USE_FILECACHE 1
#else
#  define ALLOW_USE_FILECACHE 0
#  undef ABBR_FILECACHE
#  undef SMART_FILECACHE
#endif

#ifdef ABBR_FILECACHE
#  define ALLOW_ABBR_FILECACHE 1
#else
#  define ALLOW_ABBR_FILECACHE 0
#endif
#ifdef SMART_FILECACHE
#  define DEFAULT_SMART_FILECACHE 1
#else
#  define DEFAULT_SMART_FILECACHE 0
#endif

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

/* 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

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



#include "angband.h"
#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 <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 char 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 = 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 char *alarm_types[] = { "Off", "On (one-shot)", "On (repeating)", "On (one-shot)" };


/* 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 */
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 */

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 char *fs_quit_key_text = "Press f12 to return to the desktop";
static char *alarm_cancel_text = "(Press ^Escape to cancel the alarm)";

#ifndef PDEADCHK
#define PDEADCHK \
	(p_ptr->is_dead)
#endif

/* Debugging flags, etc. */
static int log_g_malloc = 0;							/* Log calls to ralloc, etc */
static int show_sound_alloc = 0;						/* Log sound mappings, etc */
static int abbr_filecache = ALLOW_ABBR_FILECACHE;		/* compress text in the file-cache? */
static int abbr_tmpfile = 1;							/* Cache compressed files on disc? */
static int smart_filecache = DEFAULT_SMART_FILECACHE;	/* skip non-useful lines? */
static int use_filecache = ALLOW_USE_FILECACHE;			/* Activate file caching? */
static int minimise_memory = 0;							/* Cripple some things to save memory */

/* 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 errr Term_curs_acn( int x, int y );
static errr Term_curs_acnFS(int x, int y);
static void Term_init_acn( term *t );
static errr Term_text_acn( int x, int y, int n, byte a, cptr s );
static errr Term_text_acnFS(int x, int y, int n, byte a, cptr s);
static errr Term_user_acn( int n );
static errr Term_wipe_acn( int x, int y, int n );
static errr Term_wipe_acnFS(int x, int y, int n);
static errr Term_xtra_acn(int n, int v);
static errr Term_xtra_acnFS(int n, int v);
static errr Term_xtra_acn_check( void );
static errr Term_xtra_acn_checkFS(void);
static errr Term_xtra_acn_clearFS( void );
static errr Term_xtra_acn_event( void );
static errr Term_xtra_acn_eventFS(void);
static errr Term_xtra_acn_react( void );
static errr Term_xtra_acn_reactFS(int force);


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


/* Forward declarations of the alarm stuff */
static void check_alarm( BOOL desktop );
static void trigger_alarm_desktop( void );
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.
 */
static vptr g_malloc( huge size );

#ifdef OLD_RFREE
static errr g_free( vptr blk, huge size );
#else
static vptr g_free( vptr blk, huge size );
#endif

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


/*
 | 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( 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, 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, 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" */
	}

	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 seperate 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;
}


static 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 */
}



int access( const char *path, int mode )
{
	char *real_path = riscosify_name(path);
	int handle;

	/* Try to open the file */
	handle = myFile_Open( real_path, 0x4f );

	/* Failed? */
	if ( !handle ) { return -1; }

	/* Close the file */
	myFile_Close(handle);

	/* Sucess */
	return 0;
}


/* 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, (void*) buf, (int) nbytes );

	return unwritten ? 1 : 0;
}


/* Seek in a file */
errr fd_seek( int handle, long 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 = "<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 */
}


/*
 | 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 <VARIANT>$ */
	t = path + sprintf(path,"%s$",VARIANT);

	/* Copy the path specifier and move 'name' past it */
	for ( ; *name!='.' ; *t++=*name++ )
		;
	name++;			/* name now points to the font name-proper */

	/* 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;
	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;
}





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

/*
 | 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) );
}





/*
 | 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 );

}

/*
 | 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( char *a, char *b, int n )
{
	int i;

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


/*
 | 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 seperated) 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,buffer[260];
	char menu_buffer[260];
	int paths;
	int i,max_width;
	char *path[64];		/* pointers to path names */
	menu_item *mi;

	font_menu = NULL;

	/* Get the path (ie. dir) to look under */
	t = getenv( 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",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 */
		}
	}
}



/*
 | Create and set up the Info box
 */
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);
}


/*
 | 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, 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;
}





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;
}



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

	/*
	 | Only poll the wimp if there's something in the keyboard buffer
	 | or every 10cs
	 */

	/* 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 ) )
	{
		event_pollmask old_mask = event_mask;
		last_poll = curr_time;
		event_mask.data.null = 0;
		Stop_Hourglass;
		Event_Poll();
		Start_Hourglass;
		event_mask = old_mask;
	}

	/*
	 | 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 )
{
	event_pollmask old_mask = event_mask;
	event_mask.data.null = 0;

	Stop_Hourglass;

	while ( !key_pressed && !fullscreen_font )
	{
		int curr_time = Time_Monotonic();
		Wimp_PollIdle( event_mask, &event_lastevent, curr_time+100 );
		Event_Process( &event_lastevent );
		check_alarm(TRUE);
	}
	Start_Hourglass;

	event_mask = old_mask;
	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 */
			check_alarm(TRUE);
			if ( v )
				return Term_xtra_acn_event();
			else
				return Term_xtra_acn_check();

		case TERM_XTRA_BORED :		/* Bored */
			check_alarm(TRUE);
			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 )
			{
				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;

		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;
}



/* 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) */

	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;

	t->data       = (vptr) td;

	Term_activate(t);
}




/* 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 )
{
	const static 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;
				}
			default :
				/* 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 )
{
	Icon_SetDouble( gamma_win, 0, gamma, 2 );
}


static void init_gamma_window( void )
{
	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 )
{
	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( void *siv, void *ref )
{
	slider_info *si = (slider_info*) siv;
	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 );
}







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 )
	{
		char *t_, *o_;

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

		/* Load choices */
		while ( fgets(buffer,260,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( *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 */
						attach_font_to_term( &(data[t]), f_ );
					}
				}
			}
		}
		fclose(fp);
	}

	/*
	 | 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;
	}


}




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 )
	{
		char *t_,*o_;
		/* Load choices */
		while ( fgets(buffer,260,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);
	}
}



/*
 | 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;
}





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) */
}





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

	/* Create a window for each term.  Term 0 is special (no scroll bars) */
	data[0].w = Window_Create( "angband", template_TITLEMIN );
	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]) );
	}

	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- */
}

/*
 | 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);
}




static unsigned int htoi( char *s )
{
	const static char hex[]="0123456789ABCDEF";
	unsigned int v = 0;
	while ( *s )
	{
		char *m;
		int d = toupper( *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;
	int  da_font=1, da_game=1;


	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(argv[i][1]) )
			{
				case 'm' :		/* -minimise_memory */
					minimise_memory = 1;
					break;
				case 'c' :		/* -c[a][s][f][<n>] */
					for ( j=2; argv[i][j] ; j++ )
					{
						int on = isupper(argv[i][j]);
						switch ( tolower(argv[i][j]) )
						{
							case 'a' : abbr_filecache = on && ALLOW_ABBR_FILECACHE; break;
							case 's' : smart_filecache = on; break;
							case 'f' : abbr_tmpfile = on && ALLOW_ABBR_FILECACHE; break;
							case 'p' : flush_scrap = !on; break;
							default :
								if ( isdigit(argv[i][j]) )
								{
									max_file_cache_size = atoi(argv[i]+j)<<10;
									while ( isdigit(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;
				case 'd' :		/* -df, -dg, -dc or -d : disable DAs */
					switch ( tolower(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;
				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);
			}
		}
	}

	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;

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

	/* Expand the (Angband) resource path */
	t = getenv( RESPATH );
	if ( !t || !*t ) { Msgs_ReportFatal( 0,"%s is not defined",RESPATH ); }
	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( RESDIR );
	Msgs_LoadFile("Messages");

	/* This is a hack to only call Event_Initialise3 under RO3 */
	if ( os_version() < 300 )
		Event_Initialise( VARIANT );
	else
		Event_Initialise3( 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( "<" RESDIR "$Dir>.Sprites22" );
	else
		resource_sprites = Sprite_LoadFile( "<" RESDIR "$Dir>.Sprites" );

	Screen_CacheModeInfo();

	/* Initialise some ZapRedraw stuff */
	initialise_palette();
	initialise_fonts();			/* Set up the fonts */
	initialise_r_data();		/* Set up the r_data buffer */

	/* 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();

	/* 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";

	if ( start_fullscreen )
	{
		enter_fullscreen_mode();
	}
	else
	{
		Start_Hourglass;			/* Paranoia */
		Hnd_ModeChange( NULL, NULL );/* Caches the various fonts/palettes */
		show_windows();
		grab_caret();
		/* Wait for a null poll so that the windows can appear */
		do
		{
			event_pollmask old_mask = event_mask;
			event_mask.data.null = 0;
			Event_Poll();
			event_mask = old_mask;
		} while ( event_lastevent.type != 0 );
	}

	/* Initialise Angband */
	Start_Hourglass;			/* Paranoia */
	strcpy(savefile, unixify_name(arg_savefile) );
	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 )
{
	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;
	Pointer_RestrictToRect( r );
}




/*
 | 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,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;
	mouse_block mb;

	/* 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();

	/* Remember where the mouse is */
	Wimp_GetPointerInfo( &mb );
	old_mouse_posn = mb.pos;

	/* 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;
	unsigned char blk[8];

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

	/* Restore the Term interface */
	for ( i=0; i<MAX_TERM_DATA; i++ )
	{
		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;
		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 position */
	blk[0] = 3;				/* Set mouse position sub-reason code */
	blk[1] = (old_mouse_posn.x & 0xff);
	blk[2] = (old_mouse_posn.x & 0xff00)>>8;
	blk[3] = (old_mouse_posn.y & 0xff);
	blk[4] = (old_mouse_posn.y & 0xff00)>>8;
	OS_Word( osword_DEFINEPOINTERANDMOUSE, blk );

	Start_Hourglass;

	/*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();

	/* Refresh the windows - this probably isn't necessary anyway */
	refresh_windows();
}





static void fs_writechars( int x, int y, int n, 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[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[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)
{
	const static char hex[] = "0123456789ABCDEF";

	if ( code==KEYPRESS_QUIT )
	{
		/*Sound_SysBeep();*/
		leave_fullscreen_mode();
		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)
{
	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;

		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,(char*)s,(char)a);
	return 0;
}



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

	check_alarm( FALSE );

	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;
}







/*--------------------------------------------------------------------------*/
/* (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
 |
 | The 'huge' is to be type-compatible with z-virt.c
 */
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",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.
 */
#ifdef OLD_RFREE
static errr g_free( vptr blk, huge size )
#else
static vptr g_free( vptr blk, huge size )
#endif
{
	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;
}





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

/*
 | 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,"%s%s",SOUNDPATH,"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,2048,f ) )
	{
		char *sample_name;
		int  event_number;

		/* Skip comments and lines that begin with whitespace */
		if ( *buffer=='#' || isspace(*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(*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(*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(*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(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 %s%s",SOUNDPATH,leafname);
		SWI(1,0,SWI_OS_CLI,buffer);
	}
}



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, 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 i;
	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( &i );
	Term_set_cursor( 1 );

	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));

			if ( ALLOW_DEBUG_INFO )
			{
				display_line(2,23,tum_col(optn==23),"Show debug info");
				max_opt = 12;
			}

			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 );
							set_gamma_window_state();
							/* 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;
							set_sound_window_state();
							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;
							set_sound_window_state();
							break;
						case 4 :	/* Start fullscreen */
							start_fullscreen = !start_fullscreen;
							if ( start_fullscreen )
								enter_fullscreen_mode();
							else
								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( i );

	/* 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,1024) )
			{
				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,1024) )
		{
			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,1024) )
		{
			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, 1024, 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( BOOL desktop )
{
	time_t t;
	struct tm *lt;

	if ( !alarm_type ) { return; }

	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 int last_beep = 0;
		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.
	 */
	if ( desktop && alarm_disp )
		trigger_alarm_desktop();
}


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();
	}

}


/*
 | 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 );
}


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

#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");
	if ( ALLOW_USE_FILECACHE ) { debug_printf("\t\tUSE_FILECACHE\n"); }
	if ( ALLOW_ABBR_FILECACHE ) { debug_printf("\t\tABBR_FILECACHE\n"); }
	if ( DEFAULT_SMART_FILECACHE ) { debug_printf("\t\tSMART_FILECACHE\n"); }

	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));

	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);
}


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 */



