/*
 * amiga.c
 *
 * IO interface for the Amiga and DICE C
 *
 * Changes to distributed source are indicated by comments containing
 * the keyword AMIGA.
 *
 */

#include <proto/amigaguide.h>
#include <proto/asl.h>
#include <proto/diskfont.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/gadtools.h>
#include <proto/graphics.h>
#include <proto/intuition.h>
#include <proto/keymap.h>
#include <devices/audio.h>
#include <devices/timer.h>
#include <exec/exec.h>
#include <graphics/gfxbase.h>
#include <graphics/gfxmacros.h>
#include <graphics/videocontrol.h>
#include <intuition/imageclass.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "frotz.h"
#include "amiga.h"

#define INFORMATION "\
\n\
FROTZ V2.32 - interpreter for all Infocom games. Complies with standard\n\
1.0 of Graham Nelson's specification. Written by Stefan Jokisch in 1995-7\n\
Amiga version by David Kinder\n\
\n\
Syntax: frotz [options] story-file\n\
\n\
  -a   watch attribute setting  \t -p   alter piracy opcode\n\
  -A   watch attribute testing  \t -r # right margin\n\
  -c # context lines            \t -s # random number seed value\n\
  -i   ignore runtime errors    \t -S # transscript width\n\
  -l # left margin              \t -t   set Tandy bit\n\
  -o   watch object movement    \t -u # slots for multiple undo\n\
  -O   watch object locating    \t -x   expand abbreviations g/x/z"

static unsigned char graphic_font[] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x30, 0x70, 0xFF, 0x70, 0x30, 0x00, 0x00,
  0x00, 0x0C, 0x0E, 0xFF, 0x0E, 0x0C, 0x00, 0x00,
  0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
  0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00,
  0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
  0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
  0x08, 0x08, 0x08, 0xFF, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0xFF, 0x08, 0x08, 0x08,
  0x08, 0x08, 0x08, 0x08, 0x0F, 0x08, 0x08, 0x08,
  0x10, 0x10, 0x10, 0x10, 0xF0, 0x10, 0x10, 0x10,
  0x10, 0x10, 0x10, 0x10, 0x1F, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x1F, 0x10, 0x10, 0x10, 0x10,
  0x00, 0x00, 0x00, 0xF8, 0x08, 0x08, 0x08, 0x08,
  0x08, 0x08, 0x08, 0x08, 0xF8, 0x00, 0x00, 0x00,
  0x10, 0x10, 0x10, 0x10, 0x1F, 0x20, 0x40, 0x80,
  0x80, 0x40, 0x20, 0x1F, 0x10, 0x10, 0x10, 0x10,
  0x01, 0x02, 0x04, 0xF8, 0x08, 0x08, 0x08, 0x08,
  0x08, 0x08, 0x08, 0x08, 0xF8, 0x04, 0x02, 0x01,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8,
  0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F,
  0x08, 0x08, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x08, 0x08, 0x08,
  0xF8, 0xF8, 0xF8, 0xF8, 0xFF, 0xF8, 0xF8, 0xF8,
  0x1F, 0x1F, 0x1F, 0x1F, 0xFF, 0x1F, 0x1F, 0x1F,
  0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F,
  0x00, 0x00, 0x00, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8,
  0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0x00, 0x00, 0x00,
  0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x20, 0x40, 0x80,
  0x80, 0x40, 0x20, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F,
  0x01, 0x02, 0x04, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8,
  0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0x04, 0x02, 0x01,
  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
  0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
  0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00,
  0x00, 0xFF, 0x80, 0x80, 0x80, 0x80, 0xFF, 0x00,
  0x00, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 0xFF, 0x00,
  0x00, 0xFF, 0xE0, 0xE0, 0xE0, 0xE0, 0xFF, 0x00,
  0x00, 0xFF, 0xF0, 0xF0, 0xF0, 0xF0, 0xFF, 0x00,
  0x00, 0xFF, 0xF8, 0xF8, 0xF8, 0xF8, 0xFF, 0x00,
  0x00, 0xFF, 0xFC, 0xFC, 0xFC, 0xFC, 0xFF, 0x00,
  0x00, 0xFF, 0xFE, 0xFE, 0xFE, 0xFE, 0xFF, 0x00,
  0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
  0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,
  0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00,
  0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81,
  0x08, 0x08, 0x08, 0x08, 0xFF, 0x08, 0x08, 0x08,
  0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x00,
  0x00, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x3C, 0x18,
  0x18, 0x3C, 0x7E, 0x18, 0x18, 0x7E, 0x3C, 0x18,
  0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xFF,
  0x00, 0x3C, 0x66, 0x06, 0x18, 0x00, 0x18, 0x00,
  0x63, 0x76, 0x5C, 0x60, 0x78, 0x66, 0x60, 0x00,
  0x78, 0x6E, 0x63, 0x7C, 0x63, 0x6E, 0x78, 0x00,
  0x00, 0x1E, 0x1B, 0x18, 0x18, 0xD8, 0x78, 0x00,
  0x63, 0x63, 0x77, 0x49, 0x77, 0x63, 0x63, 0x00,
  0x63, 0x77, 0x6B, 0x6B, 0x63, 0x63, 0x63, 0x00,
  0x49, 0x52, 0x64, 0x68, 0x70, 0x60, 0x60, 0x00,
  0x00, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00,
  0x63, 0x53, 0x4F, 0x63, 0x79, 0x65, 0x63, 0x00,
  0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00,
  0x18, 0x18, 0x7E, 0x99, 0x7E, 0x18, 0x18, 0x00,
  0x60, 0x60, 0x78, 0x6C, 0x67, 0x63, 0x63, 0x00,
  0x38, 0x3C, 0x36, 0x30, 0x30, 0x30, 0x30, 0x00,
  0x77, 0x6B, 0x77, 0x63, 0x63, 0x63, 0x63, 0x00,
  0x18, 0xD8, 0x78, 0x3C, 0x1E, 0x1B, 0x18, 0x00,
  0x71, 0x6A, 0x64, 0x71, 0x6A, 0x64, 0x60, 0x00,
  0x60, 0x60, 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x00,
  0x18, 0x18, 0x18, 0x18, 0x3C, 0x5A, 0xDB, 0x00,
  0x7C, 0x63, 0x63, 0x7C, 0x66, 0x63, 0x63, 0x00,
  0x60, 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x03, 0x00,
  0x18, 0x3C, 0x5A, 0x99, 0x18, 0x18, 0x18, 0x00,
  0x70, 0x78, 0x6C, 0x66, 0x63, 0x63, 0x63, 0x00,
  0x7E, 0x03, 0x7E, 0x03, 0x03, 0x03, 0x7C, 0x00,
  0x70, 0x6C, 0x63, 0x6C, 0x78, 0x60, 0x60, 0x00,
  0xDB, 0x5A, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x00,
  0x78, 0x64, 0x52, 0x49, 0x4F, 0x49, 0x49, 0x00,
  0x60, 0x63, 0x66, 0x6C, 0x78, 0x60, 0x60, 0x00,
  0xE7, 0xC3, 0x81, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF,
  0xFF, 0xE7, 0xE7, 0xE7, 0xE7, 0x81, 0xC3, 0xE7,
  0xE7, 0xC3, 0x81, 0xE7, 0xE7, 0x81, 0xC3, 0xE7,
  0xFF, 0xC3, 0x99, 0xF9, 0xE7, 0xFF, 0xE7, 0xFF
};

USHORT ZColours[11] =
{
  0x0000,0x0E00,0x00C0,0x0EE0,0x005A,0x0F0F,0x00EE,0x0FFF,
  0x0AAA,0x0777,0x0444
};
LONG ZMachinePens[16] =
{ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 };
int UsePenTable;

USHORT CustomColours[16];
USHORT SystemColours[16];

static int current_style = 0;
static int current_font = 0;
static int saved_style = 0;
static int saved_font = 0;
static int user_tandy_bit = -1;
static int user_random_seed = -1;
static int cursor_state = 0;

int last_text_out = 0;
int current_game = 0;
int justify_pending = 0;

int getopt (int, char *[], const char *);

extern const char *optarg;
extern int optind;

#define HISTORY_LINES 20
unsigned char *History[HISTORY_LINES];
int HistoryPosition = HISTORY_LINES;

#define TEXTBUFFER_SIZE 256
#define QUALIFIER_SHIFT (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT)
#define QUALIFIER_ALT (IEQUALIFIER_LALT|IEQUALIFIER_RALT)

struct NewMenu NewMenus[] =
 {{ NM_TITLE,"Project",0,0,0,0 },
  { NM_ITEM,"Prefs...","F",0,0,0 },
  { NM_ITEM,"Help...","H",0,0,0 },
  { NM_ITEM,"About...","?",0,0,0 },
  { NM_ITEM,NM_BARLABEL,0,0,0,0 },
  { NM_ITEM,"Quit","Q",0,0,0 },
  { NM_TITLE,"Game",0,0,0,0 },
  { NM_ITEM,"Restore...","O",0,0,"restore" },
  { NM_ITEM,"Save...","S",0,0,"save" },
  { NM_ITEM,"Restart","R",0,0,"restart" },
  { NM_ITEM,NM_BARLABEL,0,0,0,0 },
  { NM_ITEM,"Script","P",0,0,"script" },
  { NM_ITEM,"Unscript","U",0,0,"unscript" },
  { NM_ITEM,NM_BARLABEL,0,0,0,0 },
  { NM_ITEM,"Superbrief","<",0,0,"superbrief" },
  { NM_ITEM,"Brief",".",0,0,"brief" },
  { NM_ITEM,"Verbose",">",0,0,"verbose" },
  { NM_ITEM,NM_BARLABEL,0,0,0,0 },
  { NM_ITEM,"Tandy Flag",0,CHECKIT|MENUTOGGLE,0,0 },
  { NM_TITLE,"Commands",0,0,0,0 },
  { NM_ITEM,"Look","L",0,0,"look" },
  { NM_ITEM,"Inventory","I",0,0,"inventory" },
  { NM_ITEM,NM_BARLABEL,0,0,0,0 },
  { NM_ITEM,"Diagnose","D",0,0,"diagnose" },
  { NM_ITEM,"Score","C",0,0,"score" },
  { NM_ITEM,"Time","T",0,0,"time" },
  { NM_ITEM,NM_BARLABEL,0,0,0,0 },
  { NM_ITEM,"Wait","Z",0,0,"wait" },
  { NM_ITEM,"Again","G",0,0,"again" },
  { NM_ITEM,NM_BARLABEL,0,0,0,0 },
  { NM_ITEM,"Yes","Y",0,0,"yes" },
  { NM_ITEM,"No","N",0,0,"no" },
  { NM_END,0,0,0,0,0 }};

WORD ScreenPens[] = { -1 };
char Version[] = "$VER:Frotz 2.32 (6.11.97)";
char TitleBar[] = "Frotz 2.32 Amiga Release 10";
unsigned char TextBuffer[TEXTBUFFER_SIZE];
unsigned char LengthBuffer[TEXTBUFFER_SIZE];
unsigned long TextBufferPtr;

extern struct Library *IntuitionBase;
extern struct GfxBase *GfxBase;
struct Library *KeymapBase;
struct Library *AmigaGuideBase;
struct Screen *Screen;
struct Screen *DefaultPubScreen;
struct Window *Window;
struct Window *OldWindowPtr;
struct RastPort *RastPort;
struct Menu *Menus;
struct FileRequester *FileRequester;
struct Process *ThisProcess;
struct WBStartup *WBMessage;
struct DiskObject *SaveIcon;
struct List *SearchDirs;
struct List *ExcludeDirs;
struct List *GamesList;
struct Preferences FrotzPrefs;
struct MorePreferences MoreFrotzPrefs;
APTR Visual;

ULONG ForeColour = 1;
ULONG BackColour = 0;
ULONG RevForeColour = 2;
ULONG RevBackColour = 3;
ULONG InputColour = 2;
int UsingColour = 0;

struct TextFont *ScreenFont;
struct TextFont *TextFont, *OpenedTextFont;
struct TextFont *FixedFont, *OpenedFixedFont;
int EmphasisStyle;

struct timerequest *TimerRequest;
struct MsgPort *TimerMsgPort;
int TimerOpenCode = -1;
int TimerActive;

int ScreenWidth, ScreenHeight;
int ScaleX, ScaleY;
int TextWidth, TextHeight;
int PreviousRows;

struct SampleHeader
{
  unsigned short Length;
  unsigned char	 Times;
  unsigned char  BaseNote;
  unsigned short Frequency;
  unsigned char	 Blank[2];
  unsigned short SampleLength;
};

struct MacintoshMid
{
  unsigned short Length;
  unsigned char	 Commands[11];
  unsigned char  SoundFileName[19];
};

#define MAC_OFFSET 68

struct IOAudio *SoundRequest, *SoundLeftRequest, *SoundRightRequest;
struct MsgPort *SoundMsgPort;
struct SampleHeader *SampleHeader;
int SoundOpenCode = -1;
int CurrentSample;
int SampleUnsigned;
int SamplePlaying;
char *SampleMemory;
char SoundDirectory[MAX_FILE_NAME+1];

struct GraphicsHeader
{
  unsigned char  Part;
  unsigned char  Flags;
  unsigned short Unknown1;
  unsigned short Images;
  unsigned short Link;
  unsigned char  InfoSize;
  unsigned char  Unused;
  unsigned short Checksum;
  unsigned short Unknown2;
  unsigned short Version;
};

#define PIC_NUMBER 0
#define PIC_WIDTH  2
#define PIC_HEIGHT 4
#define PIC_FLAGS  6
#define PIC_DATA   8
#define PIC_COLOUR 11

#define GFX_IBM_MCGA 1
#define GFX_IBM_CGA  2
#define GFX_AMIGA    3

char *GfxMemory;
struct GraphicsHeader *GfxHeader;
int GfxType;
unsigned short TableSeq[3840];
unsigned char TableVal[3840];

int InputMax1,InputMax2;

/*
 * os_beep
 *
 * Play a beep sound. Ideally, the sound should be high- (number == 1)
 * or low-pitched (number == 2).
 *
 */

void os_beep (int number)
{
  DisplayBeep(Window->WScreen);
}

/*
 * os_display_char
 *
 * Display a character of the current font using the current colours and
 * text style. The cursor moves to the next position. Printable codes are
 * all ASCII values from 32 to 126, ISO Latin-1 characters from 160 to
 * 255, ZC_GAP (gap between two sentences) and ZC_INDENT (paragraph
 * indentation). The screen should not be scrolled after printing to the
 * bottom right corner.
 *
 */

void os_display_char (zchar c)
{
unsigned long bcol;

  if (h_version != V6) last_text_out = 1;

  if (current_font == GRAPHICS_FONT)
  {
    if (RastPort->DrawMode&INVERSVID)
    {
      bcol = RastPort->BgPen;
      SetBPen(RastPort,RastPort->FgPen);
    }
    SafeRectFill(RastPort,
      RastPort->cp_x,
      RastPort->cp_y,
      RastPort->cp_x+TextWidth-1,
      RastPort->cp_y+TextHeight-1,
      RastPort->BgPen);
    if (RastPort->DrawMode&INVERSVID) SetBPen(RastPort,bcol);

    if ((TextWidth == 8) && (TextHeight == 8))
    {
    int i;
    __chip static unsigned short graphics_char[8];

      for (i = 0; i < 8; i++)
	graphics_char[i] = graphic_font[((c-32)<<3)+i]<<8;
      BltPattern(RastPort,graphics_char,RastPort->cp_x,RastPort->cp_y,
	RastPort->cp_x+7,RastPort->cp_y+7,2);
    }
    else
    {
    int i,j;
    unsigned char unscaled;

      for (i = 0; i < TextHeight; i++)
      {
	unscaled = graphic_font[((c-32)<<3)+((i*7)/(TextHeight-1))];
	for (j = 0; j < TextWidth; j++)
	{
	  if (unscaled & (128>>((j*7)/(TextWidth-1))))
	    WritePixel(RastPort,RastPort->cp_x+j,RastPort->cp_y+i);
	}
      }
    }
    Move(RastPort,RastPort->cp_x+TextWidth,RastPort->cp_y);
  }
  else
  {
    if (c == ZC_INDENT)
    {
      os_display_char(' ');
      os_display_char(' ');
      os_display_char(' ');
      return;
    }
    if (c == ZC_GAP)
    {
      os_display_char(' ');
      os_display_char(' ');
      return;
    }

    if ((c >= ZC_ASCII_MIN && c <= ZC_ASCII_MAX) || (c >= ZC_LATIN1_MIN && c <= ZC_LATIN1_MAX))
    {
      if (TextBufferPtr == TEXTBUFFER_SIZE) FlushText();
      *(TextBuffer+(TextBufferPtr++)) = c;
    }
  }
}

/*
 * os_display_string
 *
 * Pass a string of characters to os_display_char.
 *
 */

void os_display_string (const zchar *s)
{
zchar c;

  while ((c = *s++) != 0)
  {
    if (c == ZC_NEW_FONT || c == ZC_NEW_STYLE)
    {
      int arg = (zchar)*s++;

      if (c == ZC_NEW_FONT) os_set_font(arg);
      if (c == ZC_NEW_STYLE) os_set_text_style(arg);
    }
    else os_display_char(c);
  }
}

/*
 * os_erase_area
 *
 * Fill a rectangular area of the screen with the current background
 * colour. Top left coordinates are (1,1). The cursor does not move.
 *
 */

void os_erase_area (int top, int left, int bottom, int right)
{
  FlushText();
  SafeRectFill(RastPort,
    Window->BorderLeft+left-1,
    Window->BorderTop+top-1,
    Window->BorderLeft+right-1,
    Window->BorderTop+bottom-1,
    UsingColour != 0 ? RastPort->BgPen : BackColour);
}

/*
 * os_fatal
 *
 * Display error message and stop interpreter.
 *
 */

void os_fatal (const char *s)
{
  os_stop_sample();

  if (WBMessage)
  {
    Requester(0,"Fatal error: %s","Cancel",s);
  }
  else
  {
    fputs("\nFatal error: ",stderr);
    fputs(s,stderr);
    fputs("\n",stderr);
  }

  Quit(1);
}

/*
 * os_finish_with_sample
 *
 * Remove the current sample from memory (if any).
 *
 */

void os_finish_with_sample (void)
{
  os_stop_sample();

  if (CurrentSample != 0)
  {
    FreeVec(SampleMemory);
    CurrentSample = 0;
  }
}

/*
 * os_font_data
 *
 * Return true if the given font is available. The font can be
 *
 *    TEXT_FONT
 *    PICTURE_FONT
 *    GRAPHICS_FONT
 *    FIXED_WIDTH_FONT
 *
 * The font size should be stored in "height" and "width". If
 * the given font is unavailable then these values must _not_
 * be changed.
 *
 */

int os_font_data (int font, int *height, int *width)
{
  *height = h_font_height;
  *width = h_font_width;

  if (font == TEXT_FONT) return 1;
  if (font == GRAPHICS_FONT) return 1;
  if (font == FIXED_WIDTH_FONT)	return 1;
  return 0;
}

/*
 * os_read_file_name
 *
 * Return the name of a file. Flag can be one of:
 *
 *    FILE_SAVE     - Save game file
 *    FILE_RESTORE  - Restore game file
 *    FILE_SCRIPT   - Transscript file
 *    FILE_RECORD   - Command file for recording
 *    FILE_PLAYBACK - Command file for playback
 *    FILE_SAVE_AUX - Save auxilary ("preferred settings") file
 *    FILE_LOAD_AUX - Load auxilary ("preferred settings") file
 *
 * The length of the file name is limited by MAX_FILE_NAME. Ideally
 * an interpreter should open a file requester to ask for the file
 * name. If it is unable to do that then this function should call
 * print_string and read_string to ask for a file name.
 *
 */

int os_read_file_name (char *file_name, const char *default_name, int flag)
{
FILE *fp;
char dir_buffer[MAX_FILE_NAME+1];
char file_buffer[MAX_FILE_NAME+1];
int saved_replay = istream_replay;
int saved_record = ostream_record;
int result = 0;

  istream_replay = 0;
  ostream_record = 0;

  if (flag != FILE_SCRIPT || MoreFrotzPrefs.Flags & PREFS_ALLFREQ)
  {
    if (FileRequester)
    {
    char *title;
    int save = 0;

      switch (flag)
      {
	case FILE_SAVE:
	  title = "Save Game";
	  save = 1;
	  break;
	case FILE_RESTORE:
	  title = "Restore Game";
	  break;
	case FILE_RECORD:
	  title = "Save Record";
	  save = 1;
	  break;
	case FILE_PLAYBACK:
	  title = "Play Record";
	  break;
	case FILE_SAVE_AUX:
	  title = "Save Auxiliary";
	  save = 1;
	  break;
	case FILE_LOAD_AUX:
	  title = "Load Auxiliary";
	  break;
	case FILE_SCRIPT:
	  title = "Script File";
	  save = 1;
	  break;
      }
      strcpy(dir_buffer,default_name);
      *(FilePart(dir_buffer)) = 0;
      strcpy(file_buffer,FilePart(default_name));

    int asl_return;

      SetColourScheme(0);
      asl_return = AslRequestTags(FileRequester,
	ASLFR_TitleText,title,
	ASLFR_InitialDrawer,dir_buffer,
	ASLFR_InitialFile,file_buffer,
	ASLFR_DoSaveMode,save,TAG_DONE);
      SetColourScheme(1);
      if (asl_return)
      {
	strcpy(file_name,FileRequester->fr_Drawer);
	AddPart(file_name,FileRequester->fr_File,MAX_FILE_NAME);

	if ((flag == FILE_SAVE) && (SaveIcon))
	  PutDiskObject(file_name,SaveIcon);
	result = 1;
      }
      goto finished;
    }
  }

  print_string("Enter a file name.\n");
  print_string("Default is \"");
  print_string(default_name);
  print_string("\": ");

  read_string(MAX_FILE_NAME-4,file_name);
  if (file_name[0] == 0) strcpy(file_name,default_name);

  result = 1;
  if (flag == FILE_SAVE || flag == FILE_SAVE_AUX || flag == FILE_RECORD)
  {
    if ((fp = fopen(file_name,"rb")) == NULL) goto finished;
    fclose(fp);
    result = read_yes_or_no("Overwrite existing file");
  }

finished:

  istream_replay = saved_replay;
  ostream_record = saved_record;
  return result;
}

/*
 * os_init_screen
 *
 * Initialise the IO interface. Prepare screen and other devices
 * (mouse, sound card). Set various OS depending story file header
 * entries:
 *
 *     h_config (aka flags 1)
 *     h_flags (aka flags 2)
 *     h_screen_cols (aka screen width in characters)
 *     h_screen_rows (aka screen height in lines)
 *     h_screen_width
 *     h_screen_height
 *     h_font_height (defaults to 1)
 *     h_font_width (defaults to 1)
 *     h_default_foreground
 *     h_default_background
 *     h_interpreter_number
 *     h_interpreter_version
 *     h_user_name (optional; not used by any game)
 *
 * Finally, set reserve_mem to the amount of memory (in bytes) that
 * should not be used for multiple undo and reserved for later use.
 *
 */

void os_init_screen (void)
{
static struct TextAttr new_font;
unsigned long screen;
int show_title = 1;

  ReadGamesFile();
  current_game = ScanForGame(h_release,h_serial,h_checksum);
  UsingColour = 0;

  if ((KeymapBase = OpenLibrary("keymap.library",37)) == 0) Quit(1);

  new_font.ta_Name = FrotzPrefs.TextFontName;
  new_font.ta_YSize = FrotzPrefs.TextFontSize;
  OpenedTextFont = OpenCorrectFont(&new_font);
  TextFont = OpenedTextFont;
  new_font.ta_Name = FrotzPrefs.FixedFontName;
  new_font.ta_YSize = FrotzPrefs.FixedFontSize;
  OpenedFixedFont = OpenCorrectFont(&new_font);
  FixedFont = OpenedFixedFont;

  if (h_version >= V5)
  {
    switch (FrotzPrefs.Colour)
    {
      case COLOUR_BZ_ONLY:
	if (story_id == BEYOND_ZORK) UsingColour = 2;
	break;
      case COLOUR_BZ_AND_V6:
	if (story_id == BEYOND_ZORK) UsingColour = 2;
	if (h_version == V6) UsingColour = 3;
	break;
      case COLOUR_ALL_GAMES:
	UsingColour = 1;
	if (story_id == BEYOND_ZORK) UsingColour = 2;
	if (h_version == V6) UsingColour = 3;
	break;
    }
  }

  if ((DefaultPubScreen = LockPubScreen(0)) == 0) Quit(1);
  screen = FrotzPrefs.CustomScreen;
  if (screen)
  {
  static struct TextAttr screen_font;
  static char screen_font_name[MAXFONTNAME];

    strcpy(screen_font_name,MoreFrotzPrefs.ScreenFontName);
    screen_font.ta_Name = screen_font_name;
    screen_font.ta_YSize = MoreFrotzPrefs.ScreenFontSize;
    ScreenFont = OpenDiskFont(&screen_font);

    if (h_version == V6 && MoreFrotzPrefs.Flags & PREFS_NO_V6_TITLE)
      show_title = 0;

    Screen = OpenScreenTags(0,
      SA_Pens,ScreenPens,
      SA_DisplayID,FrotzPrefs.ScreenMode,
      SA_Overscan,OSCAN_TEXT,
      SA_Depth,UsingColour ? 4 : 2,
      SA_Interleaved,1,
      SA_Type,CUSTOMSCREEN|AUTOSCROLL,
      SA_Font,&screen_font,
      SA_Title,TitleBar,
      SA_ShowTitle,show_title,
      SA_Behind,1,TAG_DONE);
    if (Screen == 0) Quit(1);
    GetScreenRatio(Screen);

    if (MoreFrotzPrefs.Colours[0] != 0xFFFF)
    {
      CustomColours[0] = MoreFrotzPrefs.Colours[0];
      CustomColours[1] = MoreFrotzPrefs.Colours[1];
      CustomColours[2] = MoreFrotzPrefs.Colours[2];
      CustomColours[3] = MoreFrotzPrefs.Colours[3];
    }
    else
    {
      CustomColours[0] = GetRGB4(DefaultPubScreen->ViewPort.ColorMap,0);
      CustomColours[1] = GetRGB4(DefaultPubScreen->ViewPort.ColorMap,1);
      CustomColours[2] = GetRGB4(DefaultPubScreen->ViewPort.ColorMap,2);
      CustomColours[3] = GetRGB4(DefaultPubScreen->ViewPort.ColorMap,3);
    }

    if (UsingColour)
    {
      CopyMem(ZColours,CustomColours+4,8*sizeof(USHORT));
      CopyMem(CustomColours,CustomColours+12,4*sizeof(USHORT));
      CopyMem(CustomColours,SystemColours,16*sizeof(USHORT));
      LoadRGB4(&Screen->ViewPort,CustomColours,16);
    }
    else
    {
      if (MoreFrotzPrefs.Colours[0] != 0xFFFF)
	LoadRGB4(&Screen->ViewPort,CustomColours,4);
    }

  }
  else
  {
  int i;

    GetScreenRatio(DefaultPubScreen);
    if (IntuitionBase->lib_Version >= 39)
    {
      if (UsingColour)
      {
	for (i = 0; i < 16; i++)
	{
	  ZMachinePens[i] = ObtainPen(
	    DefaultPubScreen->ViewPort.ColorMap,-1,0,0,0,PEN_EXCLUSIVE);
	}
	UsePenTable = 1;
	for (i = 0; i < 16; i++)
	{
	  if (ZMachinePens[i] == -1) FreePenTable();
	}
	if (UsePenTable == 0) UsingColour = 0;
      }
    }
    else UsingColour = 0;
  }

short left,top,width,height;

  if (MoreFrotzPrefs.Window[2] > 0)
  {
    left = MoreFrotzPrefs.Window[0];
    top = MoreFrotzPrefs.Window[1];
    width = MoreFrotzPrefs.Window[2];
    height = MoreFrotzPrefs.Window[3];
  }
  else
  {
    left = 0;
    top = DefaultPubScreen->BarHeight+1;
    width = ScreenWidth;
    height = ScreenHeight-DefaultPubScreen->BarHeight-1;
  }

int window_left,window_top,window_width,window_height;

  window_left = screen ? 0 : left;
  window_width = screen ? Screen->Width : width;
  if (screen)
  {
    window_top = show_title ? 2 : 0;
    window_height = show_title ? Screen->Height-2 : Screen->Height;
  }
  else
  {
    window_top = top;
    window_height = height;
  }

  if (h_version == V6 && MoreFrotzPrefs.V6Style == V6_640200)
  {
  int min_width = 640;
  int min_height = 200;

    if (screen)
    {
      if (show_title) min_height += Screen->Font->ta_YSize;
    }
    else
    {
      min_width += DefaultPubScreen->WBorLeft+DefaultPubScreen->WBorRight;
      min_height += DefaultPubScreen->WBorTop+1+
	DefaultPubScreen->Font->ta_YSize+SizeGadgetHeight(DefaultPubScreen);
    }
    window_width =
      MIN(min_width,screen ? Screen->Width : DefaultPubScreen->Width);
    window_height =
      MIN(min_height,screen ? Screen->Height : DefaultPubScreen->Height);
  }    

  if (MoreFrotzPrefs.Flags & PREFS_CENTRE_WINDOW)
  {
    if (screen)
    {
      window_left = (Screen->Width-window_width)/2;
    }
    else
    {
      window_left = (DefaultPubScreen->Width-window_width)/2;
      window_top = (DefaultPubScreen->Height-window_height)/2;
    }
  }
    
  if ((Window = OpenWindowTags(0,
    WA_Left,window_left,
    WA_Top,window_top,
    WA_Width,window_width,
    WA_Height,window_height,
    WA_SmartRefresh,1,
    WA_NewLookMenus,1,
    WA_AutoAdjust,1-screen,
    WA_Borderless,screen,
    WA_Backdrop,screen,
    WA_Activate,1-screen,
    WA_CloseGadget,1-screen,
    WA_DragBar,1-screen,
    WA_DepthGadget,1-screen,
    WA_SizeGadget,1-screen,
    WA_SizeBBottom,1-screen,
    show_title ? WA_Title : TAG_IGNORE,TitleBar,
    WA_ScreenTitle,TitleBar,
    WA_IDCMP,IDCMP_RAWKEY|IDCMP_MENUPICK|IDCMP_MOUSEBUTTONS|IDCMP_CLOSEWINDOW|IDCMP_CHANGEWINDOW,
    screen ? WA_CustomScreen : WA_PubScreen, screen ? Screen : DefaultPubScreen,
    TAG_DONE)) == 0) Quit(1);

  SetZColours();
  ThisProcess = (struct Process *)FindTask(0);
  OldWindowPtr = ThisProcess->pr_WindowPtr;
  ThisProcess->pr_WindowPtr = Window;
  ScreenToFront(Window->WScreen);
  ActivateWindow(Window);

  if ((Visual = GetVisualInfo(Window->WScreen,TAG_DONE)) == 0) Quit(1);
  if ((Menus = CreateMenus(NewMenus,GTMN_NewLookMenus,TRUE,TAG_DONE)) == 0) Quit(1);
  LayoutMenus(Menus,Visual,GTMN_NewLookMenus,TRUE,TAG_DONE);
  SetMenuStrip(Window,Menus);

  if (h_version == V3)
  {
    if (user_tandy_bit != -1)
      ItemAddress(Menus,FULLMENUNUM(1,11,0))->Flags |= CHECKED;
  }
  else OffMenu(Window,FULLMENUNUM(1,11,0));

  RastPort = Window->RPort;
  SetDrMd(RastPort,JAM2);
  SetFont(RastPort,FixedFont);
  TextWidth = RastPort->TxWidth;
  TextHeight = RastPort->TxHeight;

  if ((TimerMsgPort = CreateMsgPort()) == 0) Quit(1);
  if ((TimerRequest = CreateIORequest(TimerMsgPort,sizeof(struct timerequest))) == 0) Quit(1);
  if ((TimerOpenCode = OpenDevice("timer.device",UNIT_VBLANK,(struct IORequest *)TimerRequest,0)) != 0) Quit(1);

  if ((SoundMsgPort = CreateMsgPort()) == 0) Quit(1);

  if ((FileRequester = AllocAslRequestTags(ASL_FileRequest,
    ASLFR_Window,Window,
    ASLFR_SleepWindow,TRUE,
    ASLFR_RejectIcons,TRUE,TAG_DONE)) == 0) Quit(1);

  if (RastPort->BitMap->Depth == 1)
  {
    RevForeColour = 0;
    RevBackColour = 1;
    InputColour = 1;
  }

  EmphasisStyle = MoreFrotzPrefs.Flags & PREFS_ITALIC ?
    FSF_ITALIC : FSF_UNDERLINED;

  option_left_margin = MoreFrotzPrefs.LeftMargin;
  option_right_margin = MoreFrotzPrefs.RightMargin;

  if (h_version == V3)
  {
    h_config |= CONFIG_SPLITSCREEN;
    h_config |= CONFIG_PROPORTIONAL;
    if (user_tandy_bit != -1) h_config |= CONFIG_TANDY;
    if (h_flags & OLD_SOUND_FLAG)
    {
      if (InitializeSound() == 0)
      {
	ResetSound();
	h_flags &= ~OLD_SOUND_FLAG;
      }
    }
  }
  if (h_version >= V4)
  {
    h_config |= CONFIG_BOLDFACE;
    h_config |= CONFIG_EMPHASIS;
    h_config |= CONFIG_FIXED;
    h_config |= CONFIG_TIMEDINPUT;
    if (h_flags & SOUND_FLAG)
    {
      if (InitializeSound() == 0)
      {
	ResetSound();
	h_flags &= ~SOUND_FLAG;
      }
    }
  }
  if (h_version >= V5)
  {
    if (UsingColour)
      h_config |= CONFIG_COLOUR;
    if (h_flags & UNDO_FLAG)
    {
      if (option_undo_slots == 0) h_flags &= ~UNDO_FLAG;
    }
    if (h_flags & COLOUR_FLAG)
    {
      if (UsingColour == 0) h_flags &= ~COLOUR_FLAG;
    }
    LoadGraphicsFile();
  }
  if (h_version == V6)
  {
    h_config |= CONFIG_PICTURES;
    h_flags &= ~MENU_FLAG;
  }

  if (h_version == V6)
  {
    h_default_foreground = WHITE_COLOUR;
    h_default_background = BLACK_COLOUR;
  }
  else
  {
    h_default_foreground = 1;
    h_default_background = 1;
  }
  SetScreenDimensions();
  h_interpreter_number = INTERP_AMIGA;
  h_interpreter_version = h_version != V6 ? 'C' : 3;

  PreviousRows = h_screen_rows;
}

/*
 * os_more_prompt
 *
 * Display a MORE prompt, wait for a keypress and remove the MORE
 * prompt from the screen.
 *
 */

void os_more_prompt (void)
{
int saved_font;
int saved_style;
int saved_x,saved_y;
int new_x,new_y;

  FlushText();

  saved_font = current_font;
  saved_style = current_style;

  os_set_font(TEXT_FONT);
  os_set_text_style(0);

  GetCursorPos(&saved_y,&saved_x);
  os_display_string("[MORE]");
  os_read_key(0,1);

  GetCursorPos(&new_y,&new_x);
  os_erase_area(new_y,saved_x,new_y+h_font_height,new_x);
  os_set_cursor(saved_y,saved_x);
  os_set_font(saved_font);
  if (saved_style != 0) os_set_text_style(saved_style);
}

/*
 * os_prepare_sample
 *
 * Load the given sample from the disk.
 *
 */

void os_prepare_sample (int number)
{
  if (CurrentSample == number) return;
  os_finish_with_sample();

BPTR sound_data_lock;
BPTR sound_data_file;
unsigned long sample_length;
struct FileInfoBlock *sound_data_fib;
int i;

  sound_data_lock = LockSoundData(number);
  if (sound_data_lock)
  {
    if (sound_data_fib = AllocDosObject(DOS_FIB,0))
    {
      Examine(sound_data_lock,sound_data_fib);
      sample_length = sound_data_fib->fib_Size;
      FreeDosObject(DOS_FIB,sound_data_fib);

      if (SampleMemory = AllocVec(sample_length,MEMF_CHIP))
      {
	if (sound_data_file = OpenFromLock(sound_data_lock))
	{
	  Read(sound_data_file,SampleMemory,sample_length);
	  Close(sound_data_file);

	  SampleHeader = (struct SampleHeader *)SampleMemory;
	  if (SampleUnsigned)
	  {
	    for (i = 0; i < SampleHeader->SampleLength; i++)
	      *(SampleMemory+sizeof(struct SampleHeader)+i) ^= 0x80;
	  }
	}
      }
    }
    UnLock(sound_data_lock);
  }

  if (SampleMemory == 0) return;
  CurrentSample = number;
}

/*
 * os_process_arguments
 *
 * Handle command line switches. Some variables may be set to activate
 * special features of Frotz:
 *
 *     option_attribute_assignment
 *     option_attribute_testing
 *     option_context_lines
 *     option_object_locating
 *     option_object_movement
 *     option_left_margin
 *     option_right_margin
 *     option_ignore_errors
 *     option_piracy
 *     option_undo_slots
 *     option_expand_abbreviations
 *     option_script_cols
 *
 * The global pointer "story_name" is set to the story file name.
 *
 */

void os_process_arguments (int argc, char *argv[])
{
  if (IntuitionBase->lib_Version < 37) exit(0);
  LoadPreferences(1);

int num;
int c;
extern char GameFilename[];

  do
  {
    optarg = 0;
    c = getopt(argc,argv,"aAc:il:oOpr:s:S:tu:Wx");

    if (optarg != 0) num = atoi(optarg);

    switch (c)
    {
      case 'W': GetGameFilename();
		story_name = GameFilename;
		break;
      case 'a': option_attribute_assignment = 1;
		break;
      case 'A': option_attribute_testing = 1;
		break;
      case 'c': option_context_lines = num;
		break;
      case 'i': option_ignore_errors = 1;
		break;
      case 'l': option_left_margin = num;
		if (option_left_margin > 0)
		  MoreFrotzPrefs.LeftMargin = option_left_margin;
		else
		  option_left_margin = 0;
		break;
      case 'o': option_object_movement = 1;
		break;
      case 'O': option_object_locating = 1;
		break;
      case 'p': option_piracy = 1;
		break;
      case 'r': option_right_margin = num;
		if (option_right_margin > 0)
		  MoreFrotzPrefs.RightMargin = option_right_margin;
		else
		  option_right_margin = 0;
		break;
      case 's': user_random_seed = num;
		break;
      case 'S': option_script_cols = num;
		break;
      case 't': user_tandy_bit = 1;
		break;
      case 'u': option_undo_slots = num;
		break;
      case 'x': option_expand_abbreviations = 1;
		break;
    }
  }
  while (c != EOF);

  if (optind != argc-1 && story_name == 0)
  {
    puts(INFORMATION);
    DeleteList(&SearchDirs,1,0);
    DeleteList(&ExcludeDirs,1,0);
    DeleteList(&GamesList,1,0);
    exit(1);
  }

  if (story_name == 0) story_name = argv[optind];
  LoadPreferences(0);

BPTR lock;
extern char save_name[];
extern char script_name[];
extern char command_name[];
extern char auxilary_name[];
char *file_name;
char *extension;

  if (lock = Lock(story_name,ACCESS_READ))
  {
    NameFromLock(lock,save_name,MAX_FILE_NAME);
    UnLock(lock);
  }
  else strcpy(save_name,story_name);
  file_name = FilePart(save_name);
  extension = strrchr(save_name,'.');
  if (extension > file_name) *extension = 0;

  strcpy(script_name,FilePart(save_name));
  strcpy(command_name,save_name);
  strcpy(auxilary_name,save_name);

  strcat(save_name,".Save");
  strcat(script_name,".Script");
  strcat(command_name,".Record");
  strcat(auxilary_name,".Aux");
}

/*
 * os_read_line
 *
 * Read a line of input from the keyboard into a buffer. The buffer
 * may already be primed with some text. In this case, the "initial"
 * text is already displayed on the screen. After the input action
 * is complete, the function returns with the terminating key value.
 * The length of the input should not exceed "max" characters plus
 * an extra 0 terminator.
 *
 * Terminating keys are the return key (13) and all function keys
 * (see the Specification of the Z-machine) which are accepted by
 * the is_terminator function. Mouse clicks behave like function
 * keys except that the mouse position is stored in global variables
 * "mouse_x" and "mouse_y" (top left coordinates are (1,1)).
 *
 * Furthermore, Frotz introduces some special terminating keys:
 *
 *     ZC_HKEY_PLAYBACK (Alt-P)
 *     ZC_HKEY_RECORD (Alt-R)
 *     ZC_HKEY_SEED (Alt-S)
 *     ZC_HKEY_UNDO (Alt-U)
 *     ZC_HKEY_RESTART (Alt-N, "new game")
 *     ZC_HKEY_QUIT (Alt-X, "exit game")
 *     ZC_HKEY_DEBUG (Alt-D)
 *     ZC_HKEY_HELP (Alt-H)
 *
 * If the timeout argument is not zero, the input gets interrupted
 * after timeout/10 seconds (and the return value is 0).
 *
 * The complete input line including the cursor must fit in "width"
 * screen units.
 *
 * The function may be called once again to continue after timeouts,
 * misplaced mouse clicks or hot keys. In this case the "continued"
 * flag will be set. This information can be useful if the interface
 * implements input line history.
 *
 * The screen is not scrolled after the return key was pressed. The
 * cursor is at the end of the input line when the function returns.
 *
 * Since Frotz 2.2 the helper function "completion" can be called
 * to implement word completion (similar to tcsh under Unix).
 *
 */

zchar os_read_line (int max, zchar *buf, int timeout, int width, int continued)
{
int c, pos;
int inputting = 1;
unsigned long saved_pen;
zchar extension[10];
UWORD qualifier;

  cursor_state = 1;
  pos = strlen(buf);
  DrawCursor(*(buf+pos));
  InputMax1 = RastPort->cp_x+width;
  InputMax2 = MIN(InputMax1,Window->Width-Window->BorderRight);
  saved_pen = RastPort->FgPen;
  if (cwin == 0)
  {
    if (UsingColour < 2) SetAPen(RastPort,InputColour);
  }

  StartTimer(timeout);
  while (inputting)
  {
    LimitWindow(1,TextLength(RastPort,buf+pos,strlen(buf+pos)));
    c = GetKey(&qualifier);
    LimitWindow(0,0);

    switch (c)
    {
      case 27:		/* Escape */
	break;

      case 264:		/* Window Sizing */
	RedrawLine(buf,&pos,max);
	break;

      case 261:		/* Tab */
	if (pos == strlen(buf))
	{
	  if (completion(buf,extension) == 0)
	  {
	  zchar *tbuf;

	    if (tbuf = malloc(strlen(buf)+strlen(extension)+1))
	    {
	      strcpy(tbuf,buf);
	      strcat(tbuf,extension);
	      PutIntoBuffer(buf,tbuf,&pos,max);
	      free(tbuf);
	    }
	  }
	  else os_beep(1);
	}
	break;
      case ZC_BACKSPACE:	/* Backspace */
	if (pos > 0)
	{
	int deleted;

	  DrawCursor(*(buf+pos));
	  deleted = *(buf+pos-1);
	  memmove(buf+pos-1,buf+pos,strlen(buf)-pos+1);
	  MoveTextRow(-CharLength(deleted),InputMax2);
	  Move(RastPort,RastPort->cp_x-CharLength(deleted),RastPort->cp_y);
	  pos--;
	  DrawCursor(*(buf+pos));
	}
        break;

      case 260:			/* Delete */
	if (pos < strlen(buf))
	{
	int deleted;

	  DrawCursor(*(buf+pos));
	  deleted = *(buf+pos);
	  memmove(buf+pos,buf+pos+1,strlen(buf)-pos);
	  Move(RastPort,RastPort->cp_x+CharLength(deleted),RastPort->cp_y);
	  MoveTextRow(-CharLength(deleted),InputMax2);
	  Move(RastPort,RastPort->cp_x-CharLength(deleted),RastPort->cp_y);
	  DrawCursor(*(buf+pos));
	}
        break;

      case ZC_ARROW_LEFT:	/* Cursor Left */
	if (pos > 0)
	{
	  if (qualifier & QUALIFIER_SHIFT)
	  {
	    DrawCursor(*(buf+pos));
	    CursorLeftEnd(buf,&pos);
	    DrawCursor(*(buf+pos));
	  }
	  else
	  {
	    DrawCursor(*(buf+pos--));
	    Move(RastPort,RastPort->cp_x-CharLength(*(buf+pos)),RastPort->cp_y);
	    DrawCursor(*(buf+pos));
	  }
	}
	break;

      case ZC_ARROW_RIGHT:	/* Cursor Right */
	if (pos < strlen(buf))
	{
	  if (qualifier & QUALIFIER_SHIFT)
	  {
	    DrawCursor(*(buf+pos));
	    CursorRightEnd(buf,&pos);
	    DrawCursor(*(buf+pos));
	  }
	  else
	  {
	    DrawCursor(*(buf+pos));
	    Move(RastPort,RastPort->cp_x+CharLength(*(buf+pos)),RastPort->cp_y);
	    pos++;
	    DrawCursor(*(buf+pos));
	  }
	}
	break;

      case ZC_ARROW_UP:		/* Cursor Up */
	if ((cwin != 0) || (qualifier & QUALIFIER_ALT))
	{
	  if (is_terminator(c) != 0)
	  {
	    StopTimer();
	    return Terminate(buf,pos,c,saved_pen);
	  }
	}
	else
	{
	  if (qualifier & QUALIFIER_SHIFT)
	  {
	    HistoryPosition = 0;
	    while ((*(History+HistoryPosition) == 0) && (HistoryPosition < HISTORY_LINES))
	      HistoryPosition++;
	    PutIntoBuffer(buf,HistoryPosition == HISTORY_LINES ?
		(unsigned char *)"" : *(History+HistoryPosition),&pos,max);
	  }
	  else
	  {
	    if ((HistoryPosition > 0) && (*(History+HistoryPosition-1) != 0))
	    {
	      HistoryPosition--;
	      PutIntoBuffer(buf,*(History+HistoryPosition),&pos,max);
	    }
	  }
	}
	break;

      case ZC_ARROW_DOWN:	/* Cursor Down */
	if ((cwin != 0) || (qualifier & QUALIFIER_ALT))
	{
	  if (is_terminator(c) != 0)
	  {
	    StopTimer();
	    return Terminate(buf,pos,c,saved_pen);
	  }
	}
	else
	{
	  if (qualifier & QUALIFIER_SHIFT)
	  {
	    HistoryPosition = HISTORY_LINES;
	    PutIntoBuffer(buf,"",&pos,max);
	  }
	  else
	  {
	    if ((HistoryPosition < HISTORY_LINES-1) && (*(History+HistoryPosition+1) != 0))
	    {
	      HistoryPosition++;
	      PutIntoBuffer(buf,
		HistoryPosition == HISTORY_LINES ?
		  (unsigned char *)"" : *(History+HistoryPosition),&pos,max);
	    }
	    else
	    {
	      HistoryPosition = HISTORY_LINES;
	      PutIntoBuffer(buf,"",&pos,max);
	    }
	  }
	}
	break;

      default:
	if (c >= 2000)		/* Menus */
	{
	  PutIntoBuffer(buf,GTMENUITEM_USERDATA(ItemAddress(Menus,c-2000)),&pos,max);
	  c = ZC_RETURN;
	}

	if ((c >= ZC_ASCII_MIN && c <= ZC_ASCII_MAX) || (c >= ZC_LATIN1_MIN && c <= ZC_LATIN1_MAX))
	{
	  if ((strlen(buf) < max) && (FitTextLine(buf+pos,c)))
	  {
	    memmove(buf+pos+1,buf+pos,strlen(buf)-pos+1);
	    *(buf+pos) = c;

	    pos++;
	    DrawCursor(*(buf+pos));
	    MoveTextRow(CharLength(c),InputMax2);
	    os_display_char(c);
	    DrawCursor(*(buf+pos));
	  }
	}
	else
	{
	  if (is_terminator(c) != 0)
	  {
	    StopTimer();
	    return Terminate(buf,pos,c,saved_pen);
	  }
	}
	break;
    }
  }
}

/*
 * os_read_key
 *
 * Read a single character from the keyboard (or a mouse click) and
 * return it. Input aborts after timeout/10 seconds.
 *
 */

zchar os_read_key (int timeout, bool cursor)
{
int key = 0;

  cursor_state = (cursor) ? 1 : 0;
  DrawCursor(0);

  do
  {
    StartTimer(timeout);
    LimitWindow(1,0);
    if (key == 264) DrawCursor(0);		/* Window Sizing */
    key = GetKey(0);
    LimitWindow(0,0);
    StopTimer();
  }
  while (key >= 256);

  DrawCursor(0);
  last_text_out = 0;
  return (key);
}

/*
 * os_reset_screen
 *
 * Reset the screen before the program ends.
 *
 */

void os_reset_screen (void)
{
  FlushText();

  if (last_text_out != 0)
  {
    os_set_font(TEXT_FONT);
    os_set_text_style(0);
    screen_new_line();
    os_display_string("[Hit any key to exit.]");
    os_read_key(0,1);
  }

  CloseDisplay();
  DeleteList(&SearchDirs,1,0);
  DeleteList(&ExcludeDirs,1,0);
  DeleteList(&GamesList,1,0);
}

/*
 * os_scroll_area
 *
 * Scroll a rectangular area of the screen up (units > 0) or down
 * (units < 0) and fill the empty space with the current background
 * colour. Top left coordinates are (1,1). The cursor stays put.
 *
 */

void os_scroll_area (int top, int left, int bottom, int right, int units)
{
  FlushText();
  if (units > 0)
  {
    ClipBlit(RastPort,
      Window->BorderLeft+left-1,
      Window->BorderTop+top-1+units,
      RastPort,
      Window->BorderLeft+left-1,
      Window->BorderTop+top-1,
      right-left+1,bottom-top+1-units,0xC0);
    SafeRectFill(RastPort,
      Window->BorderLeft+left-1,
      Window->BorderTop+bottom-units,
      Window->BorderLeft+right-1,
      Window->BorderTop+bottom-1,
      UsingColour != 0 ? RastPort->BgPen : BackColour);
  }
}

/*
 * os_set_colour
 *
 * Set the foreground and background colours which can be:
 *
 *     1
 *     BLACK_COLOUR
 *     RED_COLOUR
 *     GREEN_COLOUR
 *     YELLOW_COLOUR
 *     BLUE_COLOUR
 *     MAGENTA_COLOUR
 *     CYAN_COLOUR
 *     WHITE_COLOUR
 *
 *     Amiga only:
 *
 *     LIGHTGREY_COLOUR
 *     MEDIUMGREY_COLOUR
 *     DARKGREY_COLOUR
 *
 * There may be more colours in the range from 16 to 255; see the
 * remarks about os_peek_colour.
 *
 */

void os_set_colour (int new_foreground, int new_background)
{
  FlushText();

  switch (UsingColour)
  {
    case 0:
      SetAPen(RastPort,ForeColour);
      SetBPen(RastPort,BackColour);
      if (GfxType == GFX_IBM_CGA)
      {
	if (new_foreground == BLACK_COLOUR) SetAPen(RastPort,1);
	if (new_foreground == WHITE_COLOUR) SetAPen(RastPort,2);
	if (new_background == BLACK_COLOUR) SetBPen(RastPort,1);
	if (new_background == WHITE_COLOUR) SetBPen(RastPort,2);
      }
      break;
    case 1:
      if (new_foreground != 1)
      {
	if (UsePenTable)
	  SetAPen(RastPort,ZMachinePens[new_foreground-BLACK_COLOUR]);
	else
	  SetAPen(RastPort,new_foreground-BLACK_COLOUR+4);
      }
      else SetAPen(RastPort,ForeColour);

      if (new_background != 1)
      {
	if (UsePenTable)
	  SetBPen(RastPort,ZMachinePens[new_background-BLACK_COLOUR]);
	else
	  SetBPen(RastPort,new_background-BLACK_COLOUR+4);
      }
      else SetBPen(RastPort,BackColour);
      break;
    case 2:
      if (new_foreground == 1)
	new_foreground = WHITE_COLOUR;
      if (new_background == 1)
	new_background = BLACK_COLOUR;

      if (UsePenTable)
      {
	SetAPen(RastPort,ZMachinePens[new_foreground-BLACK_COLOUR]);
	SetBPen(RastPort,ZMachinePens[new_background-BLACK_COLOUR]);
      }
      else
      {
	SetAPen(RastPort,new_foreground-BLACK_COLOUR+4);
	SetBPen(RastPort,new_background-BLACK_COLOUR+4);
      }

      if (cwin == 0)
      {
	CustomColours[0] = CustomColours[RastPort->BgPen];
	SetColourScheme(1);
      }
      break;
    case 3:
      if (new_foreground == 1)
	new_foreground = h_default_foreground;
      if (new_background == 1)
	new_background = h_default_background;
      SetAPen(RastPort,UsePenTable ? ZMachinePens[1] : 1);
      SetBPen(RastPort,UsePenTable ? ZMachinePens[0] : 0);
      if (cwin == 0)
      {
	if (new_foreground < 16)
	  CustomColours[1] = ZColours[new_foreground-BLACK_COLOUR];
	if (new_background < 16)
	  CustomColours[0] = ZColours[new_background-BLACK_COLOUR];
	SetColourScheme(1);
	if (UsePenTable)
	{
	  SetRGB4(&(Window->WScreen->ViewPort),ZMachinePens[0],
	    (CustomColours[0]&0x0F00)>>8,
	    (CustomColours[0]&0x00F0)>>4,
	    (CustomColours[0]&0x000F));
	  SetRGB4(&(Window->WScreen->ViewPort),ZMachinePens[1],
	    (CustomColours[1]&0x0F00)>>8,
	    (CustomColours[1]&0x00F0)>>4,
	    (CustomColours[1]&0x000F));
	}
      }
      else
      {
	if (new_foreground < 16)
	{
	  if (ZColours[new_foreground-BLACK_COLOUR] == CustomColours[0])
	    SetAPen(RastPort,UsePenTable ? ZMachinePens[0] : 0);
	  if (ZColours[new_foreground-BLACK_COLOUR] == CustomColours[1])
	    SetAPen(RastPort,UsePenTable ? ZMachinePens[1] : 1);
	}
	else SetAPen(RastPort,new_foreground-16);
	if (new_background < 16)
	{
	  if (ZColours[new_background-BLACK_COLOUR] == CustomColours[0])
	    SetBPen(RastPort,UsePenTable ? ZMachinePens[0] : 0);
	  if (ZColours[new_background-BLACK_COLOUR] == CustomColours[1])
	    SetBPen(RastPort,UsePenTable ? ZMachinePens[1] : 1);
	}
	else SetBPen(RastPort,new_background-16);
      }
      break;
  }
}

/*
 * os_set_cursor
 *
 * Place the text cursor at the given coordinates. Top left is (1,1).
 *
 */

void os_set_cursor (int row, int col)
{
  FlushText();
  if (row > h_screen_height-h_font_height+1)
    row = h_screen_height-h_font_height+1;
  if (col > h_screen_width-h_font_width+1)
    col = h_screen_width-h_font_width+1;
  Move(RastPort,Window->BorderLeft+col-1,Window->BorderTop+row-1);
}

/*
 * os_set_font
 *
 * Set the font for text output. The interpreter takes care not to
 * choose fonts which aren't supported by the interface.
 *
 */

void os_set_font (int new_font)
{
  FlushText();
  current_font = new_font;
  os_set_text_style(current_style);
}

/*
 * os_set_text_style
 *
 * Set the current text style. Following flags can be set:
 *
 *     REVERSE_STYLE
 *     BOLDFACE_STYLE
 *     EMPHASIS_STYLE (aka underline aka italics)
 *     FIXED_WIDTH_STYLE
 *
 */

void os_set_text_style (int new_style)
{
  FlushText();
  current_style = new_style;

  if (current_font == FIXED_WIDTH_FONT || new_style & FIXED_WIDTH_STYLE)
    SetFont(RastPort,FixedFont);
  else
    SetFont(RastPort,TextFont);

  if ((story_id != BEYOND_ZORK) && (UsingColour == 0))
  {
    SetAPen(RastPort,ForeColour);
    SetBPen(RastPort,BackColour);
  }
  else SetDrMd(RastPort,JAM2);
  SetSoftStyle(RastPort,FS_NORMAL,AskSoftStyle(RastPort));

  if (new_style & REVERSE_STYLE)
  {
    if ((story_id != BEYOND_ZORK) && (UsingColour == 0))
    {
      SetAPen(RastPort,RevForeColour);
      SetBPen(RastPort,RevBackColour);
    }
    else SetDrMd(RastPort,JAM2|INVERSVID);
  }

  if (new_style & BOLDFACE_STYLE)
  {
    while ((RastPort->AlgoStyle & FSF_BOLD) == 0)
      SetSoftStyle(RastPort,RastPort->AlgoStyle|FSF_BOLD,AskSoftStyle(RastPort));
  }

  if (new_style & EMPHASIS_STYLE)
  {
    while ((RastPort->AlgoStyle & EmphasisStyle) == 0)
      SetSoftStyle(RastPort,RastPort->AlgoStyle|EmphasisStyle,
      AskSoftStyle(RastPort));
  }
}

/*
 * os_start_sample
 *
 * Play the given sample at the given volume (ranging from 1 to 8 and
 * 255 meaning a default volume). The sound is played once or several
 * times in the background (255 meaning forever). The end_of_sound
 * function is called as soon as the sound finishes.
 *
 */

void os_start_sample (int number, int volume, int repeats)
{
  os_stop_sample();
  os_prepare_sample(number);

  if (CurrentSample == 0) return;

unsigned long rate;

  rate = (GfxBase->DisplayFlags&PAL ? 3546895 : 3579545) / SampleHeader->Frequency;

  SoundRequest->ioa_Request.io_Command = CMD_STOP;
  BeginIO(SoundRequest);
  WaitForMsg(SoundMsgPort);

  SoundLeftRequest->ioa_Request.io_Command = CMD_WRITE;
  SoundLeftRequest->ioa_Request.io_Flags = ADIOF_PERVOL;
  SoundLeftRequest->ioa_Period = rate;
  SoundLeftRequest->ioa_Volume = volume * 8;
  SoundLeftRequest->ioa_Cycles = repeats;
  SoundLeftRequest->ioa_Data = SampleMemory+sizeof(struct SampleHeader);
  SoundLeftRequest->ioa_Length = SampleHeader->SampleLength;
  if (SampleUnsigned)
  {
    SoundLeftRequest->ioa_Data += MAC_OFFSET;
    SoundLeftRequest->ioa_Length -= (MAC_OFFSET*2);
  }
  BeginIO(SoundLeftRequest);

  SoundRightRequest->ioa_Request.io_Command = CMD_WRITE;
  SoundRightRequest->ioa_Request.io_Flags = ADIOF_PERVOL;
  SoundRightRequest->ioa_Period = rate;
  SoundRightRequest->ioa_Volume = volume * 8;
  SoundRightRequest->ioa_Cycles = repeats;
  SoundRightRequest->ioa_Data = SampleMemory+sizeof(struct SampleHeader);
  SoundRightRequest->ioa_Length = SampleHeader->SampleLength;
  if (SampleUnsigned)
  {
    SoundRightRequest->ioa_Data += MAC_OFFSET;
    SoundRightRequest->ioa_Length -= (MAC_OFFSET*2);
  }
  BeginIO(SoundRightRequest);

  SoundRequest->ioa_Request.io_Command = CMD_START;
  BeginIO(SoundRequest);
  WaitForMsg(SoundMsgPort);

  SamplePlaying = 1;
}

/*
 * os_stop_sample
 *
 * Turn off the current sample.
 *
 */

void os_stop_sample (void)
{
  if (SoundOpenCode != 0) return;

  SoundLeftRequest->ioa_Request.io_Command = CMD_FLUSH;
  BeginIO(SoundLeftRequest);
  WaitForMsg(SoundMsgPort);

  SoundRightRequest->ioa_Request.io_Command = CMD_FLUSH;
  BeginIO(SoundRightRequest);
  WaitForMsg(SoundMsgPort);

  SamplePlaying = 0;
}

/*
 * os_string_width
 *
 * Calculate the length of a word in screen units. Apart from letters,
 * the word may contain special codes:
 *
 *    ZC_NEW_STYLE - next character is a new text style
 *    ZC_NEW_FONT  - next character is a new font
 *
 */

int os_string_width (const zchar *s)
{
int length,i,c;

  if (((TextFont->tf_Flags & FPF_PROPORTIONAL) == 0) && (TextFont->tf_XSize == FixedFont->tf_XSize))
  {
    length = 0;
    for (i = 0; s[i] != 0; i++)
    {
      c = (unsigned char) s[i];
      if ((c == ZC_NEW_STYLE) || (c == ZC_NEW_FONT)) i++;
      if (c == ZC_INDENT) length += 3;
      if (c == ZC_GAP) length += 2;
      if ((c >= ZC_ASCII_MIN && c <= ZC_ASCII_MAX) || (c >= ZC_LATIN1_MIN && c <= ZC_LATIN1_MAX))
	length++;
    }
    return length*TextWidth;
  }
  else
  {
    length = 0;
    for (i = 0; s[i] != 0; i++)
    {
      c = (unsigned char) s[i];

      if (c == ZC_INDENT)
      {
	*(LengthBuffer+length++) = ' ';
	*(LengthBuffer+length++) = ' ';
	c = ' ';
      }
      if (c == ZC_GAP)
      {
	*(LengthBuffer+length++) = ' ';
	c = ' ';
      }

      if ((c == ZC_NEW_STYLE) || (c == ZC_NEW_FONT))
      {
	i++;
      }
      else
      {
	if (c >= 32)
	{
	  if (length < TEXTBUFFER_SIZE) *(LengthBuffer+length++) = c;
	}
      }
    }
    return TextLength(RastPort,LengthBuffer,length);
  }
}

/*
 * os_char_width
 *
 * Return the length of the character in screen units.
 *
 */

int os_char_width (zchar c)
{
char s[2];

  s[0] = c;
  s[1] = 0;
  return os_string_width(s);
}

/*
 * os_peek_colour
 *
 * Return the colour of the screen unit below the cursor. (If the
 * interface uses a text mode, it may return the background colour
 * of the character at the cursor position instead.) This is used
 * when text is printed on top of pictures. Note that this coulor
 * need not be in the standard set of Z-machine colours. To handle
 * this situation, Frotz entends the colour scheme: Colours above
 * 15 (and below 256) may be used by the interface to refer to non
 * standard colours. Of course, os_set_colour must be able to deal
 * with these colours.
 *
 */

int os_peek_colour (void)
{
ULONG pen,colour;
int i;

  pen = ReadPixel(RastPort,RastPort->cp_x,
    RastPort->cp_y+RastPort->TxBaseline);
  if (UsingColour == 3)
  {
    colour = GetRGB4(Window->WScreen->ViewPort.ColorMap,pen);
    if (pen < 2)
    {
      for (i = 0; i < 11; i++)
	if (colour == ZColours[i]) return i+BLACK_COLOUR;
    }
    return pen+16;
  }
  else
  {
    switch (pen)
    {
      case 0:
	return h_default_background;
      case 1:
	return BLACK_COLOUR;
      case 2:
	return WHITE_COLOUR;
      default:
	return h_default_foreground;
    }
  }
}

/*
 * os_picture_data
 *
 * Return true if the given picture is available. If so, store the
 * picture width and height in the appropriate variables. Picture
 * number 0 is a special case: Write the highest legal picture number
 * and the picture file release number into the height and width
 * variables respectively when this picture number is asked for.
 *
 */

int os_picture_data (int picture, int *height, int *width)
{
char *info;

  if (GfxMemory)
  {
    if (picture == 0)
    {
      *height = ByteSwap(GfxHeader->Images);
      *width = ByteSwap(GfxHeader->Version);
      return 1;
    }
    if (info = FindPicture(picture))
    {
      *height = ByteSwap(*(short *)(info+PIC_HEIGHT));
      *width = ByteSwap(*(short *)(info+PIC_WIDTH));

      if (GfxType == GFX_IBM_MCGA || GfxType == GFX_AMIGA)
	*width *= 2;

      return 1;
    }
  }
  *height = 0;
  *width = 0;
  return 0;
}

/*
 * os_draw_picture
 *
 * Display a picture at the given coordinates.
 *
 */

void os_draw_picture (int picture, int y, int x)
{
char *info;

  if (UsingColour == 0 && GfxType != GFX_IBM_CGA) return;
  if (GfxMemory == 0) return;
  if ((info = FindPicture(picture)) == 0) return;
  x += Window->BorderLeft-1;
  y += Window->BorderTop-1;

unsigned char buf[512];
long data, colour;
unsigned short code, prev_code, val;
short width, height, flags;
short count, bits, bits_per_code, bits_left, pos, iwidth;
int i, j, ypos = -1, alloc = 0, depth;
struct BitMap bitmap;
PLANEPTR mask;

  data = (long)((*(unsigned char *)(info+PIC_DATA))<<16)+
	 (long)((*(unsigned char *)(info+PIC_DATA+1))<<8)+
	 (long)(*(unsigned char *)(info+PIC_DATA+2));
  width = ByteSwap(*(short *)(info+PIC_WIDTH));
  height = ByteSwap(*(short *)(info+PIC_HEIGHT));
  flags = ByteSwap(*(short *)(info+PIC_FLAGS));

  if (GfxHeader->InfoSize >= 14)
  {
    colour = (long)((*(unsigned char *)(info+PIC_COLOUR))<<16)+
	     (long)((*(unsigned char *)(info+PIC_COLOUR+1))<<8)+
	     (long)(*(unsigned char *)(info+PIC_COLOUR+2));
    if (colour != 0)
    {
      count = *((unsigned char *)(GfxMemory+colour++));
      if (Screen)
      {
	for (i = 0; i < 14; i++)
	{
	  CustomColours[2+i] =
	    (((int)*((unsigned char *)(GfxMemory+colour))>>4)<<8)+
	    (((int)*((unsigned char *)(GfxMemory+colour+1))>>4)<<4)+
	    ((int)*((unsigned char *)(GfxMemory+colour+2))>>4);
	  colour+=3;
	}
	LoadRGB4(&Screen->ViewPort,CustomColours,16);
      }
      else
      {
      ULONG r,g,b;

	for (i = 0; i < 14; i++)
	{
	  r = (int)*((unsigned char *)(GfxMemory+colour))>>4;
	  g = (int)*((unsigned char *)(GfxMemory+colour+1))>>4;
	  b = (int)*((unsigned char *)(GfxMemory+colour+2))>>4;
	  SetRGB4(&(Window->WScreen->ViewPort),ZMachinePens[2+i],r,g,b);
	  colour+=3;
	}
      }
    }
  }

  if (GfxType == GFX_AMIGA) return;
  iwidth = width;
  if (GfxType == GFX_IBM_MCGA) iwidth *= 2;

  depth = UsePenTable ? 8 : 4;
  InitBitMap(&bitmap,depth,iwidth,height);
  for (i = 0; i < depth; i++)
  {
    if (bitmap.Planes[i] = AllocRaster(iwidth,height))
    {
      BltClear(bitmap.Planes[i],RASSIZE(iwidth,height),0);
      alloc++;
    }
  }
  if (mask = AllocRaster(iwidth,height))
  {
    BltClear(mask,RASSIZE(iwidth,height),0);
    alloc++;
  }

  if (alloc == depth+1)
  {
    bits_per_code = 9;
    bits_left = 0;
    pos = 9999;

    for (;;)
    {
      code = 0;
      i = bits_per_code;
      do
      {
	if (bits_left <= 0)
	{
	  bits = *((unsigned char *)(GfxMemory+(data++)));
	  bits_left = 8;
	}
	code |= (bits>>(8-bits_left))<<(bits_per_code-i);
	i -= bits_left;
	bits_left = -i;
      }
      while (i > 0);

      code &= 0xFFF>>(12-bits_per_code);
      if (code == 256)
      {
	bits_per_code = 9;
	count = 257;
	continue;
      }
      if (code == 257) break;
      val = code;
      i = 0;
      if (code == count)
      {
 	val = prev_code;
	i = 1;
      }
      while (val > 255)
      {
	buf[i++] = TableVal[val-256];
	val = TableSeq[val-256];
      }
      buf[i] = val;
      if (code == count) buf[0] = val;
      TableVal[count-256] = val;
      TableSeq[count-256] = prev_code;
      if (++count == (1<<bits_per_code) && bits_per_code < 12)
	bits_per_code++;

      do
      {
      int bitpos;

	if (pos >= width)
	{
	  ypos++;
	  bitpos = ypos*bitmap.BytesPerRow;
	  pos = 0;
	}
	val = buf[i--];

	if (GfxType == GFX_IBM_CGA)
	{
	int black,white;

	  black = (UsingColour && Screen) ? 4 : 1;
	  white = (UsingColour && Screen) ? WHITE_COLOUR-BLACK_COLOUR+4 : 2;

	  if (flags & 1)
	  {
	    if (val)
	    {
	    int bitposx;

	      switch (val)
	      {
		case 2:
		  val = white;
		  break;
		case 3:
		  val = black;
		  break;
	      }

	      bitposx = bitpos+(pos>>3);
	      for (j = 0; j < depth; j++)
	      {
		if (val & (1<<j))
		  *(bitmap.Planes[j]+bitposx) |= 128>>(pos&7);
	      }
	    }
	    pos++;
	  }
	  else
	  {
	  int bitposx;

	    bitposx = bitpos+(pos>>3);
	    for (j = 0; j < depth; j++)
	    {
	      if (black&(1<<j)) *(bitmap.Planes[j]+bitposx) |= ~val;
	      if (white&(1<<j)) *(bitmap.Planes[j]+bitposx) |= val;
	    }
	    pos+=8;
	  }

	}
	else
	{
	  if (val)
	  {
	  int bitposx,xbit = pos*2;

	    if (UsePenTable) val = ZMachinePens[val];
	    bitposx = bitpos+(xbit>>3);
	    for (j = 0; j < depth; j++)
	    {
	      if (val & (1<<j))
		*(bitmap.Planes[j]+bitposx) |= (128+64)>>(xbit&7);
	    }
	  }
	  pos++;
	}

      }
      while (i >= 0);
      prev_code = code;
    }
    for (i = 0; i < RASSIZE(iwidth,height); i++)
    {
      for (j = 0; j < depth; j++) *(mask+i) |= *(bitmap.Planes[j]+i);
    }
    BltMaskBitMapRastPort(&bitmap,0,0,RastPort,x,y,iwidth,height,0xE0,mask);
    if ((Window->Flags & WFLG_BORDERLESS) == 0)
    {
      if ((x+iwidth > Window->Width-Window->BorderRight) || (y+height > Window->Height-Window->BorderBottom))
	RefreshWindowFrame(Window);
    }
  }

  for (i = 0; i < depth; i++)
  {
    if (bitmap.Planes[i])
      FreeRaster(bitmap.Planes[i],iwidth,height);
  }
  if (mask) FreeRaster(mask,iwidth,height);
}

/*
 * os_random_seed
 *
 * Return an appropriate random seed value in the range from 0 to
 * 32767, possibly by using the current system time.
 *
 */

int os_random_seed (void)
{
  if (user_random_seed == -1) return clock()&32767;
  return user_random_seed;
}

/*
 * os_restart_game
 *
 * This routine allows the interface to interfere with the process of
 * restarting a game at various stages:
 *
 *     RESTART_BEGIN - restart has just begun
 *     RESTART_WPROP_SET - window properties have been initialised
 *     RESTART_END - restart is complete
 *
 */

void os_restart_game (int stage)
{
  if (stage == RESTART_BEGIN)
  {
    USHORT cols[16];
    int x,y;

    CheckReset();

    if (UsingColour && (story_id == BEYOND_ZORK))
    {
      if (os_picture_data(1,&x,&y))
      {
	CopyMem(CustomColours,cols,16*sizeof(USHORT));
	os_set_colour(WHITE_COLOUR,BLACK_COLOUR);
	SafeRectFill(RastPort,
	  Window->BorderLeft,Window->BorderTop,
	  Window->BorderLeft+h_screen_width-1,
	  Window->BorderTop+h_screen_height-1,0);
	os_draw_picture(1,1,1);
	os_read_key(0,0);
	CopyMem(cols,CustomColours,16*sizeof(USHORT));
	SetZColours();
      }
    }
  }
}

void SafeRectFill(struct RastPort *rp,long xMin,long yMin,long xMax,long yMax,unsigned long pen)
{
unsigned long saved_pen;

  saved_pen = rp->FgPen;
  SetAPen(rp,pen);
  if (xMax > Window->Width-Window->BorderRight-1) xMax = Window->Width-Window->BorderRight-1;
  if (yMax > Window->Height-Window->BorderBottom-1) yMax = Window->Height-Window->BorderBottom-1;
  RectFill(rp,xMin,yMin,xMax,yMax);
  SetAPen(rp,saved_pen);
}

void FlushText(void)
{
static int semaphore;

  if (TextBufferPtr < 1) return;
  if (semaphore) return;
  semaphore = 1;

  Move(RastPort,RastPort->cp_x,RastPort->cp_y+RastPort->TxBaseline);

  if (justify_pending)
  {
  int i,j,len,spaces;

    for (i = 0, spaces = 0; i < TextBufferPtr; i++)
      if (*(TextBuffer+i) == ' ') spaces++;

    len = h_screen_width-option_right_margin-option_left_margin+
      Window->BorderLeft-RastPort->cp_x-
      TextLength(RastPort,TextBuffer,TextBufferPtr)+
      (spaces*TextLength(RastPort," ",1));
    if (RastPort->AlgoStyle & FSF_BOLD)
      len -= RastPort->Font->tf_BoldSmear;
    if (*(TextBuffer+TextBufferPtr-1) == ' ') spaces--;

    for (i = 0, j = 0; i < TextBufferPtr; i++)
    {
      if (*(TextBuffer+i) == ' ')
      {
	if (i-j > 0) Text(RastPort,TextBuffer+j,i-j);
	j = i+1;
	Move(RastPort,RastPort->cp_x+(len/spaces),RastPort->cp_y);
	len -= len/spaces;
	spaces--;
      }
    }
    if (i-j > 0) Text(RastPort,TextBuffer+j,i-j);

    justify_pending = 0;
  }
  else Text(RastPort,TextBuffer,TextBufferPtr);

  Move(RastPort,RastPort->cp_x,RastPort->cp_y-RastPort->TxBaseline);
  TextBufferPtr = 0;

  if ((Window->Flags & WFLG_BORDERLESS) == 0)
  {
    if (RastPort->cp_x >= Window->Width-Window->BorderRight)
      RefreshWindowFrame(Window);
  }

  semaphore = 0;
}

int GetKey(UWORD *qualifier_addr)
{
struct IntuiMessage *imsg;
ULONG class, port_signals;
UWORD code, qualifier;
int i,old_height;

static unsigned long old_sec = 0,old_mic = 0;
static unsigned long new_sec = 0,new_mic = 0;

  FlushText();
  port_signals = PORTSIG(Window->UserPort)|
		 PORTSIG(TimerMsgPort)|
		 PORTSIG(SoundMsgPort);

  while(1)
  {
    while(imsg = (struct IntuiMessage *)GetMsg(Window->UserPort))
    {
      class = imsg->Class;
      code = imsg->Code;
      qualifier = imsg->Qualifier;
      mouse_x = imsg->MouseX-Window->BorderLeft+1;
      mouse_y = imsg->MouseY-Window->BorderTop+1;
      new_sec = imsg->Seconds;
      new_mic = imsg->Micros;
      if (class == IDCMP_MENUVERIFY) SetColourScheme(0);
      ReplyMsg((struct Message *)imsg);
      if (qualifier_addr) *qualifier_addr = qualifier;
      switch(class)
      {
	case IDCMP_RAWKEY:
	  switch (code)
	  {
	    case 0x4C:					/* Cursor Up */
	      return ZC_ARROW_UP;
	    case 0x4D:					/* Cursor Down */
	      return ZC_ARROW_DOWN;
	    case 0x4F:					/* Cursor Left */
	      return ZC_ARROW_LEFT;
	    case 0x4E:					/* Cursor Right */
	      return ZC_ARROW_RIGHT;
	    case 0x5F:					/* Help */
	      HelpGuide();
	      break;
	    default:
	      if (code >= 0x50 && code <= 0x59)		/* Function Keys */
		return code - 0x50 + ZC_FKEY_MIN;

	    static struct InputEvent ie;
	    unsigned char ascii_buffer[1];

	      if (code & IECODE_UP_PREFIX) break;
	      ie.ie_Class = IECLASS_RAWKEY;
	      ie.ie_Prev2DownCode = ie.ie_Prev1DownCode;
	      ie.ie_Prev2DownQual = ie.ie_Prev1DownQual;
	      ie.ie_Prev1DownCode = ie.ie_Code;
	      ie.ie_Prev1DownQual = ie.ie_Qualifier;
	      ie.ie_Code = code;
	      ie.ie_Qualifier = 0;
	      if (MapRawKey(&ie,ascii_buffer,1,0))
	      {
		switch (*ascii_buffer)
		{
		  case 'd':
		    if (qualifier & QUALIFIER_ALT) return ZC_HKEY_DEBUG;
		    break;
		  case 'h':
		    if (qualifier & QUALIFIER_ALT) return ZC_HKEY_HELP;
		    break;
		  case 'n':
		    if (qualifier & QUALIFIER_ALT) return ZC_HKEY_RESTART;
		    break;
		  case 'p':
		    if (qualifier & QUALIFIER_ALT) return ZC_HKEY_PLAYBACK;
		    break;
		  case 'r':
		    if (qualifier & QUALIFIER_ALT) return ZC_HKEY_RECORD;
		    break;
		  case 's':
		    if (qualifier & QUALIFIER_ALT) return ZC_HKEY_SEED;
		    break;
		  case 'u':
		    if (qualifier & QUALIFIER_ALT) return ZC_HKEY_UNDO;
		    break;
		  case 'x':
		    if (qualifier & QUALIFIER_ALT) return ZC_HKEY_QUIT;
		    break;
		}
	      }

	      ie.ie_Qualifier = qualifier;
	      if (MapRawKey(&ie,ascii_buffer,1,0))
	      {
		code = (int)(*ascii_buffer);
		switch (code)
		{
		  case 8:
		    return ZC_BACKSPACE;
		  case 9:
		    return 261;
		  case 13:
		    return ZC_RETURN;
		  case 27:
		    return ZC_ESCAPE;
		  case 127:
		    return 260;
		  default:
		    if (qualifier & IEQUALIFIER_NUMERICPAD)
		    {					/* Numeric Keypad */
		      if (code >= '0' && code <= '9') return code - '0' + ZC_NUMPAD_MIN;
		    }
		    if ((code >= ZC_ASCII_MIN && code <= ZC_ASCII_MAX))
		      return code;
		    if ((code >= ZC_LATIN1_MIN && code <= ZC_LATIN1_MAX))
		      return code;
		}
	      }
	      break;
	  }
	  break;
	case IDCMP_MENUPICK:
	  SetColourScheme(1);
	  i = ProcessMenus(code);
	  if (i > 0) return i;
	  break;
	case IDCMP_MOUSEBUTTONS:
	  if (code == SELECTDOWN)
	  {
	    BOOL double = DoubleClick(old_sec,old_mic,new_sec,new_mic);
	    old_sec = new_sec;
	    old_mic = new_mic;
	    return double ? ZC_DOUBLE_CLICK : ZC_SINGLE_CLICK;
	  }
	  break;
	case IDCMP_CLOSEWINDOW:
	  Quit(0);
	  break;
	case IDCMP_CHANGEWINDOW:
	  old_height = h_screen_height;
	  SetScreenDimensions();
	  resize_screen();
	  InputMax2 = MIN(InputMax1,Window->Width-Window->BorderRight);

	  if (PreviousRows > h_screen_rows)
	  {
	    SafeRectFill(RastPort,
	      Window->BorderLeft,
	      Window->BorderTop+(h_screen_rows*TextHeight),
	      Window->Width-Window->BorderRight-1,
	      Window->Height-Window->BorderBottom-1,BackColour);

	    if (RastPort->cp_y+TextHeight > Window->Height-Window->BorderBottom)
	    {
	      Move(RastPort,RastPort->cp_x,Window->BorderTop+((h_screen_rows-1)*TextHeight));
	      SafeRectFill(RastPort,
		Window->BorderLeft,
		RastPort->cp_y,
		Window->Width-Window->BorderRight-1,
		RastPort->cp_y+TextHeight-1,BackColour);
	      PreviousRows = h_screen_rows;
	      return 264;				/* Window Sizing */
	    }
	  }
	  PreviousRows = h_screen_rows;
	  break;
      }
    }
    if (GetMsg(TimerMsgPort))
    {
      TimerActive = 0;
      return ZC_TIME_OUT;
    }
    if (GetMsg(SoundMsgPort))
    {
      if (GetMsg(SoundMsgPort)) end_of_sound();
      SamplePlaying = 0;
    }
    ModifyIDCMP(Window,Window->IDCMPFlags | IDCMP_MENUVERIFY);
    Wait(port_signals);
    ModifyIDCMP(Window,Window->IDCMPFlags & ~IDCMP_MENUVERIFY);
  }
}

void DrawCursor(int under)
{
int size;
BYTE saved_draw_mode;

  FlushText();
  if (cursor_state == 0) return;
  size = (under == 0) ? RastPort->TxWidth : CharLength(under);

  saved_draw_mode = RastPort->DrawMode;
  SetDrMd(RastPort,COMPLEMENT);
  if (UsingColour == 3) SetWrMsk(RastPort,0x01);

  SafeRectFill(RastPort,
    RastPort->cp_x,
    RastPort->cp_y,
    RastPort->cp_x+size-1,
    RastPort->cp_y+TextHeight-1,0);

  SetDrMd(RastPort,saved_draw_mode);
  if (UsingColour == 3) SetWrMsk(RastPort,0xFF);
}

void ShowAbout(void)
{
char *title, *after_title;
char *authors, *before_authors, *after_authors;
char *level;

  title = GetGameTitle(current_game);
  authors = GetGameAuthors(current_game);
  level = GetGameLevel(current_game);

  after_title = (strcmp(title,"") == 0) ? "" : "\n";
  if (strcmp(authors,"") == 0)
  {
    before_authors = "";
    after_authors = (strcmp(title,"") == 0) ? "" : "\n";
  }
  else
  {
    before_authors = "Written by ";
    after_authors = (strcmp(level,"") == 0) ? "\n\n" : "\n";
  }

  Requester(Window,
    "%s%s%s%s%s%s"
    "Frotz 2.32 Standard 1.0 Infocom Interpreter\n"
    "Copyright 1995-97 by Stefan Jokisch\n\n"
    "Amiga Release 10\n"
    "Amiga Version by David Kinder",
    "Continue",
    title,after_title,before_authors,authors,after_authors,level);
}

int CharLength(int c)
{
unsigned char buffer;

  if (c < 0) c += 256;
  buffer = (unsigned char)c;
  return TextLength(RastPort,&buffer,1);
}

void MoveTextRow(int offset,int max)
{
int xSource,xDest;

  xSource = RastPort->cp_x;
  xDest = RastPort->cp_x+offset;
  ClipBlit(RastPort,
    xSource,
    RastPort->cp_y,
    RastPort,
    xDest,
    RastPort->cp_y,
    max-MAX(xSource,xDest),
    TextHeight,0xC0);
}

void CursorLeftEnd(unsigned char *buffer,int *pos)
{
  while (*pos > 0)
  {
    (*pos)--;
    Move(RastPort,RastPort->cp_x-CharLength(*(buffer+*pos)),RastPort->cp_y);
  }
}

void CursorRightEnd(unsigned char *buffer,int *pos)
{
  while (*pos < strlen(buffer))
  {
    Move(RastPort,RastPort->cp_x+CharLength(*(buffer+*pos)),RastPort->cp_y);
    (*pos)++;
  }
}

int FitTextLine(unsigned char *pointer,int new)
{
int a,b;

  a = TextLength(RastPort,pointer,strlen(pointer))+CharLength(new)+
    RastPort->cp_x+RastPort->TxWidth;
  b = Window->BorderLeft+h_screen_width-
    option_right_margin-option_left_margin;
  return (a > b) ? 0 : 1;
}

void Quit(int code)
{
  last_text_out = 0;
  os_reset_screen();
  exit(code);
}

void StartTimer(int timeout)
{
  if ((TimerActive == 0) && (timeout > 0))
  {
    TimerRequest->tr_node.io_Command = TR_ADDREQUEST;
    TimerRequest->tr_node.io_Message.mn_ReplyPort = TimerMsgPort;
    TimerRequest->tr_time.tv_secs = timeout/10;
    TimerRequest->tr_time.tv_micro = (timeout-(TimerRequest->tr_time.tv_secs*10))*100000;
    SendIO((struct IORequest *)TimerRequest);
    TimerActive = 1;
  }
}

void StopTimer(void)
{
  if (TimerActive != 0)
  {
    if (GetMsg(TimerMsgPort) == 0)
    {
      AbortIO((struct IORequest *)TimerRequest);
      WaitIO((struct IORequest *)TimerRequest);
    }
    TimerActive = 0;
  }
}

void StoreInHistory(unsigned char *line)
{
int i;

  if ((*line != 0) && (SafeCmp(*(History+HISTORY_LINES-1),line) != 0))
  {
    if (*History) FreeVec(*History);
    for (i = 0; i < HISTORY_LINES-1; i++) *(History+i) = *(History+i+1);
    if (*(History+HISTORY_LINES-1) = AllocVec(strlen(line)+1,MEMF_CLEAR))
      strcpy(*(History+HISTORY_LINES-1),line);
  }
  HistoryPosition = HISTORY_LINES;
}

int SafeCmp(const char *a,const char *b)
{
  if ((a == 0) || (b == 0)) return 1;
  return strcmp(a,b);
}

void PutIntoBuffer(unsigned char *buffer,unsigned char *new,int *pos,int max_size)
{
  if (new == 0) return;

  DrawCursor(*(buffer+*pos));

int saved_x_position;
int saved_colour;
int i;

  CursorLeftEnd(buffer,pos);
  saved_colour = RastPort->FgPen;
  saved_x_position = RastPort->cp_x;
  SetAPen(RastPort,RastPort->BgPen);
  for (i = 0; i < strlen(buffer); i++) os_display_char(*(buffer+i));
  FlushText();
  SetAPen(RastPort,saved_colour);
  Move(RastPort,saved_x_position,RastPort->cp_y);

  *buffer = 0;

  while(1)
  {
    if ((FitTextLine(buffer,*(new+*pos)) == 0) || (*(new+*pos) == 0) || (*pos == max_size))
    {
      for (i = 0; i < strlen(buffer); i++) os_display_char(*(buffer+i));
      FlushText();
      DrawCursor(*(buffer+*pos));
      return;
    }
    else
    {
      *(buffer+*pos) = *(new+*pos);
      (*pos)++;
      *(buffer+*pos) = 0;
    }
  }
}

void RedrawLine(unsigned char *buffer,int *pos,int max_size)
{
char *current_buffer;

  DrawCursor(*(buffer+*pos));
  if ((current_buffer = AllocVec(max_size,MEMF_CLEAR)) == 0) return;
  strcpy(current_buffer,buffer);
  PutIntoBuffer(buffer,current_buffer,pos,max_size);
  FreeVec(current_buffer);
}

int ProcessMenus(UWORD menu_number)
{
struct MenuItem *menu_item;

  while (menu_number != MENUNULL)
  {
    switch (MENUNUM(menu_number))
    {
      case 0:
	switch (ITEMNUM(menu_number))
	{
	  case 0:
	    if (OpenPreferences()) return ZC_HKEY_RESTART;
	    break;
	  case 1:
	    HelpGuide();
	    break;
	  case 2:
	    ShowAbout();
	    break;
	  case 4:
	    Quit(0);
	    break;
	}
	break;
      case 1:
	if (ITEMNUM(menu_number) == 11)
	{
	  if (h_version == V3)
	  {
	    if (ItemAddress(Menus,menu_number)->Flags & CHECKED)
	    {
	      h_config |= CONFIG_TANDY;
	      SET_BYTE(H_CONFIG,h_config)
	      user_tandy_bit = 1;
	    }
	    else
	    {
	      h_config &= ~CONFIG_TANDY;
	      SET_BYTE(H_CONFIG,h_config)
	      user_tandy_bit = -1;
	    }
	  }
	}
	else return (menu_number+2000);
	break;
      case 2:
	return (menu_number+2000);
	break;
    }
    menu_item = ItemAddress(Menus,menu_number);
    menu_number = menu_item->NextSelect;
  }
  return 0;
}

int Terminate(unsigned char *buffer,int pos,int c,int pen)
{
  DrawCursor(*(buffer+pos));
  CursorRightEnd(buffer,&pos);
  SetAPen(RastPort,pen);
  if (c == ZC_RETURN) StoreInHistory(buffer);
  last_text_out = 0;
  return c;
}

void HelpGuide(void)
{
struct NewAmigaGuide frotz_guide =
  { 0,"Frotz.guide",0,0,0,0,0,0,0,0,0,0,0 };

  frotz_guide.nag_Screen = Window->WScreen;
  if (AmigaGuideBase == 0)
    AmigaGuideBase = OpenLibrary("amigaguide.library",34);
  if (AmigaGuideBase != 0)
  {
    SetColourScheme(0);
    CloseAmigaGuide(OpenAmigaGuide(&frotz_guide,TAG_DONE));
    SetColourScheme(1);
  }
}

int InitializeSound(void)
{
  SetupSoundDirectory();

  if ((SoundRequest = (struct IOAudio *)
    CreateIORequest(SoundMsgPort,sizeof(struct IOAudio))) == 0) return 0;
  if ((SoundLeftRequest = (struct IOAudio *)
    CreateIORequest(SoundMsgPort,sizeof(struct IOAudio))) == 0) return 0;
  if ((SoundRightRequest = (struct IOAudio *)
    CreateIORequest(SoundMsgPort,sizeof(struct IOAudio))) == 0) return 0;

static unsigned char allocation_map[] = { 3,5,10,12 };

  SoundRequest->ioa_Request.io_Message.mn_Node.ln_Pri = 127;
  SoundRequest->ioa_Request.io_Command = ADCMD_ALLOCATE;
  SoundRequest->ioa_Request.io_Flags = ADIOF_NOWAIT;
  SoundRequest->ioa_Data = allocation_map;
  SoundRequest->ioa_Length = sizeof(allocation_map);

  if ((SoundOpenCode =
    OpenDevice("audio.device",0,(struct IORequest *)SoundRequest,0)) != 0) return 0;

unsigned long channels;

  channels = (unsigned long)SoundRequest->ioa_Request.io_Unit;
  CopyMem(SoundRequest,SoundLeftRequest,sizeof(struct IOAudio));
  SoundLeftRequest->ioa_Request.io_Unit = (void *)(channels & 9);
  CopyMem(SoundRequest,SoundRightRequest,sizeof(struct IOAudio));
  SoundRightRequest->ioa_Request.io_Unit = (void *)(channels & 6);

  return 1;
}

void ResetSound(void)
{
  os_finish_with_sample();
  if (SoundOpenCode == 0) CloseDevice((struct IORequest *)SoundRequest);
  if (SoundRequest) DeleteIORequest(SoundRequest);
  if (SoundLeftRequest) DeleteIORequest(SoundLeftRequest);
  if (SoundRightRequest) DeleteIORequest(SoundRightRequest);
}

void SetupSoundDirectory(void)
{
char base_dir[MAX_FILE_NAME+1];
BPTR lock;

  if (lock = Lock(story_name,ACCESS_READ))
  {
    NameFromLock(lock,base_dir,MAX_FILE_NAME);
    UnLock(lock);
  }
  else strcpy(base_dir,story_name);
  *FilePart(base_dir) = 0;

  switch (current_game)
  {
    case 14:				/* The Lurking Horror */
      strcpy(SoundDirectory,base_dir);
      AddPart(SoundDirectory,"LurkingHorrorSound",MAX_FILE_NAME);
      if (lock = Lock(SoundDirectory,ACCESS_READ))
      {
	UnLock(lock);
	return;
      }
      break;
    case 20:				/* Sherlock */
      strcpy(SoundDirectory,base_dir);
      AddPart(SoundDirectory,"SherlockSound",MAX_FILE_NAME);
      if (lock = Lock(SoundDirectory,ACCESS_READ))
      {
	UnLock(lock);
	return;
      }
      break;
  }
  strcpy(SoundDirectory,base_dir);
  AddPart(SoundDirectory,"Sound",MAX_FILE_NAME);
}

BPTR LockSoundData(int number)
{
BPTR sound_name_file;
BPTR sound_mac_file;
BPTR lock;
char sound_name[MAX_FILE_NAME+1];
char sound_data[MAX_FILE_NAME+1];
char new_sound_name[MAX_FILE_NAME+1];
char *new_sound_ptr;
struct MacintoshMid mac_mid_data;

  SampleUnsigned = 0;
  sprintf(sound_name,"%s/s%d.nam",SoundDirectory,number);
  sprintf(sound_data,"%s/s%d.dat",SoundDirectory,number);

  if (sound_name_file = Open(sound_name,MODE_OLDFILE))
  {
    Seek(sound_name_file,2,OFFSET_BEGINNING);
    new_sound_ptr = new_sound_name;
    do
    {
      *new_sound_ptr = FGetC(sound_name_file);
    }
    while (*new_sound_ptr++ != 0);
    sprintf(sound_data,"%s/%s",SoundDirectory,new_sound_name);
    Close(sound_name_file);
  }
  if ((lock = Lock(sound_data,ACCESS_READ)) != 0) return lock;

  sprintf(sound_data,"%s/s%d",SoundDirectory,number);
  if ((lock = Lock(sound_data,ACCESS_READ)) != 0)
  {
    SampleUnsigned = 1;
    return lock;
  }

  sprintf(sound_data,"%s/m%d",SoundDirectory,number);
  if ((sound_mac_file = Open(sound_data,MODE_OLDFILE)) != 0)
  {
    SampleUnsigned = 1;
    Read(sound_mac_file,&mac_mid_data,sizeof(struct MacintoshMid));
    Close(sound_mac_file);
    sprintf(sound_data,"%s/%s",SoundDirectory,mac_mid_data.SoundFileName);
    return Lock(sound_data,ACCESS_READ);
  }

  switch (current_game)
  {
    case 14:				/* The Lurking Horror */
      sprintf(sound_data,"%s/lurkin%.2d.snd",SoundDirectory,number);
      break;
    case 20:				/* Sherlock */
      sprintf(sound_data,"%s/sherlo%.2d.snd",SoundDirectory,number);
      break;
    default:
      return 0;
      break;
  }
  if ((lock = Lock(sound_data,ACCESS_READ)) != 0)
  {
    SampleUnsigned = 1;
    return lock;
  }

  return 0;
}

void WaitForMsg(struct MsgPort *port)
{
  WaitPort(port);
  GetMsg(port);
}

void BusyPointer(struct Window *window,int busy)
{
  if ((IntuitionBase->lib_Version >= 39) && (window))
    SetWindowPointer(window,WA_BusyPointer,busy,TAG_DONE);
}

LONG Requester(struct Window *window,UBYTE *text,UBYTE *gadgets,...)
{
va_list arguments;
LONG return_value;
static struct EasyStruct requester =
  { sizeof(struct EasyStruct),0,"Frotz",0,0 };

  requester.es_TextFormat = text;
  requester.es_GadgetFormat = gadgets;
  va_start(arguments,gadgets);
  SetColourScheme(0);
  BusyPointer(window,1);
  return_value = EasyRequestArgs(window,&requester,0,arguments);
  BusyPointer(window,0);
  SetColourScheme(1);
  va_end(arguments);
  return return_value;
}

void CloseDisplay(void)
{
int i;

  FlushText();
  last_text_out = 0;

  if (h_version == V3)
  {
    if (h_flags & OLD_SOUND_FLAG) ResetSound();
  }
  if (h_version >= V4)
  {
    if (h_flags & SOUND_FLAG) ResetSound();
  }

  for (i = 0; i < HISTORY_LINES; i++)
  {
    if (*(History+i)) FreeVec(*(History+i));
    *(History+i) = 0;
  }

  StopTimer();
  if (TimerOpenCode == 0) CloseDevice((struct IORequest *)TimerRequest);
  if (TimerRequest) DeleteIORequest(TimerRequest);
  if (TimerMsgPort) DeleteMsgPort(TimerMsgPort);
  if (SoundMsgPort) DeleteMsgPort(SoundMsgPort);

  if (GfxMemory) FreeVec(GfxMemory);

  CloseBGUI(2);
  if (SaveIcon) FreeDiskObject(SaveIcon);
  if (ScreenFont) CloseFont(ScreenFont);
  if (OpenedTextFont) CloseFont(OpenedTextFont);
  if (OpenedFixedFont) CloseFont(OpenedFixedFont);
  if (Menus) FreeMenus(Menus);
  if (Visual) FreeVisualInfo(Visual);
  if (ThisProcess) ThisProcess->pr_WindowPtr = OldWindowPtr;
  if (Window) CloseWindow(Window);
  if (Screen) CloseScreen(Screen);
  FreePenTable();
  if (DefaultPubScreen) UnlockPubScreen(0,DefaultPubScreen);
  if (FileRequester) FreeAslRequest(FileRequester);
  if (AmigaGuideBase) CloseLibrary(AmigaGuideBase);
  if (KeymapBase) CloseLibrary(KeymapBase);

  TimerRequest = 0;
  TimerMsgPort = 0;

  SoundOpenCode = -1;
  SoundRequest = 0;
  SoundLeftRequest = 0;
  SoundRightRequest = 0;
  SoundMsgPort = 0;

  GfxMemory = 0;

  SaveIcon = 0;
  OpenedTextFont = 0;
  OpenedFixedFont = 0;
  Menus = 0;
  Visual = 0;
  ThisProcess = 0;
  Window = 0;
  Screen = 0;
  DefaultPubScreen = 0;
  FileRequester = 0;
  AmigaGuideBase = 0;
  KeymapBase = 0;
}

void GetScreenRatio(struct Screen *screen)
{
struct TagItem vti[] =
  { VTAG_VIEWPORTEXTRA_GET,0,
    VTAG_END_CM,0 };
struct ViewPortExtra *vpe;

  ScaleX = 1000;
  ScaleY = 1000;
  ScreenWidth = screen->Width;
  ScreenHeight = screen->Height;
  if (screen->ViewPort.ColorMap)
  {
    if (VideoControl(screen->ViewPort.ColorMap,vti) == 0)
    {
      vpe = (struct ViewPortExtra *)vti[0].ti_Data;
      ScreenWidth = vpe->DisplayClip.MaxX - vpe->DisplayClip.MinX + 1;
      ScreenHeight = vpe->DisplayClip.MaxY - vpe->DisplayClip.MinY + 1;
      ScaleX = MAX((ScreenWidth*1000)/640,1000);
      ScaleY = MAX((ScreenHeight*1000)/256,1000);
    }
  }
}

void SetScreenDimensions(void)
{
  h_screen_width = Window->Width-Window->BorderLeft-Window->BorderRight;
  h_screen_height = Window->Height-Window->BorderTop-Window->BorderBottom;
  h_font_width = TextWidth;
  h_font_height = TextHeight;
  h_screen_cols = h_screen_width/h_font_width;
  h_screen_rows = h_screen_height/h_font_height;
}

void SetColourScheme(int custom)
{
static USHORT previous;

  if ((UsingColour > 1) && (Screen))
    LoadRGB4(&Screen->ViewPort,custom ? CustomColours : SystemColours,16);
}

void Justifiable(void)
{
  if (h_version == V6) return;
  if ((MoreFrotzPrefs.Flags & PREFS_JUSTIFICATION) == 0) return;
  if ((TextFont->tf_Flags & FPF_PROPORTIONAL) == 0) return;
  if (cwin != 0) return;
  justify_pending = 1;
}

void LimitWindow(int limit,int x)
{
  if (limit)
  {
    WindowLimits(Window,
      RastPort->cp_x+RastPort->TxWidth+Window->BorderRight+x,
      Window->BorderTop+Window->BorderBottom+(TextHeight*3),
      ~0,~0);
  }
  else
  {
    WindowLimits(Window,
      Window->Width,Window->Height,
      Window->Width,Window->Height);
  }
}

void GetCursorPos(int *row, int *col)
{
  *row = RastPort->cp_y-Window->BorderTop+1;
  *col = RastPort->cp_x-Window->BorderLeft+1;
}

int SafeTryOpen(char *path,char *ext,BPTR *handle)
{
char *file,*extpos;

  if (*handle != 0) return 0;

  file = FilePart(path);
  if ((extpos = strrchr(file,'.')) == 0) extpos = file+strlen(file);
  strcpy(extpos,ext);

  *handle = Open(path,MODE_OLDFILE);
  return *handle;
}

void LoadGraphicsFile(void)
{
char gfx_path[MAX_FILE_NAME+1];
char *file_part;
BPTR fh = 0;
struct FileInfoBlock *fib;

  strcpy(gfx_path,story_name);
  if (SafeTryOpen(gfx_path,".mg1",&fh)) GfxType = GFX_IBM_MCGA;
  if (SafeTryOpen(gfx_path,".cg1",&fh)) GfxType = GFX_IBM_CGA;
  if (SafeTryOpen(gfx_path,".gfx",&fh)) GfxType = GFX_AMIGA;
  if (file_part = FilePart(gfx_path))
  {
    *file_part = '\0';
    strcat(gfx_path,"Pic");
    if (SafeTryOpen(gfx_path,".data",&fh)) GfxType = GFX_AMIGA;
  }
  if (fh == 0) return;

  if (fib = AllocDosObjectTags(DOS_FIB,TAG_DONE))
  {
    ExamineFH(fh,fib);
    if (GfxMemory = AllocVec(fib->fib_Size,0))
    {
      Read(fh,GfxMemory,fib->fib_Size);
      GfxHeader = (struct GraphicsHeader *)GfxMemory;
    }
    FreeDosObject(DOS_FIB,fib);
  }
  Close(fh);
}

int ByteSwap(unsigned short n)
{
  if (GfxType != GFX_AMIGA) return ((n>>8)|((n&255)<<8));
  return n;
}

char *FindPicture(int pic)
{
char *info;
int i;

  info = GfxMemory+sizeof(struct GraphicsHeader);
  for (i = 0; i < ByteSwap(GfxHeader->Images); i++)
  {
    if (pic == ByteSwap(*(short *)(info+PIC_NUMBER))) return info;
    info += GfxHeader->InfoSize;
  }
  return 0;
}

ULONG GetColour32(ULONG col)
{
  col |= (col<<4);
  col |= (col<<8);
  col |= (col<<16);
  col |= (col<<32);
  return col;
}

void FreePenTable(void)
{
  if (DefaultPubScreen && UsePenTable)
  {
    if (IntuitionBase->lib_Version >= 39)
    {
    int i;

      for (i = 0; i < 16; i++)
      {
	ReleasePen(DefaultPubScreen->ViewPort.ColorMap,ZMachinePens[i]);
	ZMachinePens[i] = -1;
      }
    }
  }
  UsePenTable = 0;
}

struct TextFont *OpenCorrectFont(struct TextAttr *textAttr)
{
struct TextAttr topaz8 = {"topaz.font",8,FS_NORMAL,0};

  if (h_version == V6 && MoreFrotzPrefs.V6Style == V6_640200)
    return OpenFont(&topaz8);
  return OpenDiskFont(textAttr);
}

int SizeGadgetHeight(struct Screen *screen)
{
struct DrawInfo *dri;
struct Image *size;
int height = 0;

  if (dri = GetScreenDrawInfo(screen))
  {
    if (size = NewObject(NULL,SYSICLASS,
      SYSIA_DrawInfo,dri,SYSIA_Which,SIZEIMAGE,TAG_DONE))
    {
      height = size->Height;
      DisposeObject(size);
    }
    FreeScreenDrawInfo(screen,dri);
  }
  return height;
}

void SetZColours(void)
{
  if ((Screen == NULL) && UsingColour)
  {
    int i;

    for (i = 0; i < 8; i++)
    {
      SetRGB4(&(Window->WScreen->ViewPort),ZMachinePens[i],
	(ZColours[i]&0x0F00)>>8,
	(ZColours[i]&0x00F0)>>4,
	(ZColours[i]&0x000F));
    }
  }
}
