From 1a8bcf473b5f7030ab71cab1d1a355d3f3d87c63 Mon Sep 17 00:00:00 2001 From: David Kinder Date: Wed, 7 Aug 2002 18:00:00 +0100 Subject: [PATCH] Level9 3.0 --- Amiga/amiga.c | 1076 +++++++++++ Amiga/dmakefile | 13 + COPYING | 340 ++++ DOS/dos.c | 409 ++++ DOS32/Level9.gdt | Bin 0 -> 9110 bytes DOS32/Level9.gpr | Bin 0 -> 14767 bytes DOS32/allegro.c | 1667 ++++++++++++++++ DOS32/allegro.cfg | 536 ++++++ DOS32/default.fnt | Bin 0 -> 3168 bytes DOS32/font.h | 98 + DOS32/keyboard.dat | Bin 0 -> 10927 bytes Glk/Makefile.glk | 115 ++ Glk/glk.c | 3219 +++++++++++++++++++++++++++++++ Glk/glk_readme.txt | 310 +++ Glk/level9.hdr | 53 + MakeDist.bat | 10 + Unix/unix-curses.c | 1073 +++++++++++ Win/Classlib/Classlib.sln | 21 + Win/Classlib/Classlib.vcproj | 210 ++ Win/Classlib/app.cpp | 150 ++ Win/Classlib/app.h | 35 + Win/Classlib/array.cpp | 49 + Win/Classlib/array.h | 58 + Win/Classlib/bitmapc.cpp | 152 ++ Win/Classlib/bitmapc.h | 22 + Win/Classlib/button.cpp | 22 + Win/Classlib/button.h | 7 + Win/Classlib/cacheini.cpp | 263 +++ Win/Classlib/cacheini.h | 102 + Win/Classlib/colorbtn.cpp | 53 + Win/Classlib/colorbtn.h | 11 + Win/Classlib/ddewnd.cpp | 140 ++ Win/Classlib/ddewnd.h | 24 + Win/Classlib/dialog.cpp | 189 ++ Win/Classlib/dialog.h | 23 + Win/Classlib/dlgwnd.cpp | 78 + Win/Classlib/dlgwnd.h | 17 + Win/Classlib/dll.h | 40 + Win/Classlib/edit.cpp | 90 + Win/Classlib/edit.h | 19 + Win/Classlib/eval.cpp | 158 ++ Win/Classlib/eval.h | 43 + Win/Classlib/event.cpp | 66 + Win/Classlib/event.h | 35 + Win/Classlib/filedlg.cpp | 147 ++ Win/Classlib/filedlg.h | 31 + Win/Classlib/findcol.cpp | 28 + Win/Classlib/findcol.h | 3 + Win/Classlib/fname.cpp | 181 ++ Win/Classlib/fname.h | 65 + Win/Classlib/font.cpp | 58 + Win/Classlib/font.h | 5 + Win/Classlib/hashwnd.cpp | 67 + Win/Classlib/hashwnd.h | 14 + Win/Classlib/inistuff.cpp | 282 +++ Win/Classlib/inistuff.h | 23 + Win/Classlib/keypress.cpp | 138 ++ Win/Classlib/keypress.h | 23 + Win/Classlib/listc.h | 327 ++++ Win/Classlib/main.cpp | 84 + Win/Classlib/main.h | 6 + Win/Classlib/matrix.cpp | 161 ++ Win/Classlib/matrix.h | 60 + Win/Classlib/mdi.cpp | 158 ++ Win/Classlib/mdi.h | 51 + Win/Classlib/misc.cpp | 224 +++ Win/Classlib/misc.h | 23 + Win/Classlib/mywin.h | 140 ++ Win/Classlib/numedit.cpp | 85 + Win/Classlib/numedit.h | 24 + Win/Classlib/object.cpp | 105 + Win/Classlib/object.h | 22 + Win/Classlib/owndrwb.cpp | 78 + Win/Classlib/owndrwb.h | 11 + Win/Classlib/parse.cpp | 293 +++ Win/Classlib/parse.h | 85 + Win/Classlib/pointer.h | 75 + Win/Classlib/printer.cpp | 98 + Win/Classlib/printer.h | 15 + Win/Classlib/radgrp.cpp | 29 + Win/Classlib/radgrp.h | 11 + Win/Classlib/realedit.cpp | 61 + Win/Classlib/realedit.h | 11 + Win/Classlib/statbar.cpp | 398 ++++ Win/Classlib/statbar.h | 55 + Win/Classlib/stringc.cpp | 120 ++ Win/Classlib/stringc.h | 133 ++ Win/Classlib/textwnd.cpp | 186 ++ Win/Classlib/textwnd.h | 38 + Win/Classlib/unitedit.cpp | 220 +++ Win/Classlib/unitedit.h | 27 + Win/Classlib/vectorc.h | 43 + Win/Classlib/windinfo.cpp | 48 + Win/Classlib/windinfo.h | 15 + Win/Classlib/window.cpp | 654 +++++++ Win/Classlib/window.h | 68 + Win/Compile.txt | 17 + Win/Lev9win.cpp | 1183 ++++++++++++ Win/Level9.hpj | 15 + Win/Level9.ico | Bin 0 -> 766 bytes Win/Level9.manifest | 20 + Win/Level9.rc | 149 ++ Win/Level9.rtf | 196 ++ Win/Level9.sln | 21 + Win/Level9.vcproj | 169 ++ Win/Resource.h | 27 + generic.c | 234 +++ level9.c | 3532 ++++++++++++++++++++++++++++++++++ level9.h | 105 + level9.txt | 225 +++ porting.txt | 322 ++++ 111 files changed, 22518 insertions(+) create mode 100755 Amiga/amiga.c create mode 100755 Amiga/dmakefile create mode 100755 COPYING create mode 100755 DOS/dos.c create mode 100755 DOS32/Level9.gdt create mode 100755 DOS32/Level9.gpr create mode 100755 DOS32/allegro.c create mode 100755 DOS32/allegro.cfg create mode 100755 DOS32/default.fnt create mode 100755 DOS32/font.h create mode 100755 DOS32/keyboard.dat create mode 100755 Glk/Makefile.glk create mode 100755 Glk/glk.c create mode 100755 Glk/glk_readme.txt create mode 100755 Glk/level9.hdr create mode 100755 MakeDist.bat create mode 100755 Unix/unix-curses.c create mode 100755 Win/Classlib/Classlib.sln create mode 100755 Win/Classlib/Classlib.vcproj create mode 100755 Win/Classlib/app.cpp create mode 100755 Win/Classlib/app.h create mode 100755 Win/Classlib/array.cpp create mode 100755 Win/Classlib/array.h create mode 100755 Win/Classlib/bitmapc.cpp create mode 100755 Win/Classlib/bitmapc.h create mode 100755 Win/Classlib/button.cpp create mode 100755 Win/Classlib/button.h create mode 100755 Win/Classlib/cacheini.cpp create mode 100755 Win/Classlib/cacheini.h create mode 100755 Win/Classlib/colorbtn.cpp create mode 100755 Win/Classlib/colorbtn.h create mode 100755 Win/Classlib/ddewnd.cpp create mode 100755 Win/Classlib/ddewnd.h create mode 100755 Win/Classlib/dialog.cpp create mode 100755 Win/Classlib/dialog.h create mode 100755 Win/Classlib/dlgwnd.cpp create mode 100755 Win/Classlib/dlgwnd.h create mode 100755 Win/Classlib/dll.h create mode 100755 Win/Classlib/edit.cpp create mode 100755 Win/Classlib/edit.h create mode 100755 Win/Classlib/eval.cpp create mode 100755 Win/Classlib/eval.h create mode 100755 Win/Classlib/event.cpp create mode 100755 Win/Classlib/event.h create mode 100755 Win/Classlib/filedlg.cpp create mode 100755 Win/Classlib/filedlg.h create mode 100755 Win/Classlib/findcol.cpp create mode 100755 Win/Classlib/findcol.h create mode 100755 Win/Classlib/fname.cpp create mode 100755 Win/Classlib/fname.h create mode 100755 Win/Classlib/font.cpp create mode 100755 Win/Classlib/font.h create mode 100755 Win/Classlib/hashwnd.cpp create mode 100755 Win/Classlib/hashwnd.h create mode 100755 Win/Classlib/inistuff.cpp create mode 100755 Win/Classlib/inistuff.h create mode 100755 Win/Classlib/keypress.cpp create mode 100755 Win/Classlib/keypress.h create mode 100755 Win/Classlib/listc.h create mode 100755 Win/Classlib/main.cpp create mode 100755 Win/Classlib/main.h create mode 100755 Win/Classlib/matrix.cpp create mode 100755 Win/Classlib/matrix.h create mode 100755 Win/Classlib/mdi.cpp create mode 100755 Win/Classlib/mdi.h create mode 100755 Win/Classlib/misc.cpp create mode 100755 Win/Classlib/misc.h create mode 100755 Win/Classlib/mywin.h create mode 100755 Win/Classlib/numedit.cpp create mode 100755 Win/Classlib/numedit.h create mode 100755 Win/Classlib/object.cpp create mode 100755 Win/Classlib/object.h create mode 100755 Win/Classlib/owndrwb.cpp create mode 100755 Win/Classlib/owndrwb.h create mode 100755 Win/Classlib/parse.cpp create mode 100755 Win/Classlib/parse.h create mode 100755 Win/Classlib/pointer.h create mode 100755 Win/Classlib/printer.cpp create mode 100755 Win/Classlib/printer.h create mode 100755 Win/Classlib/radgrp.cpp create mode 100755 Win/Classlib/radgrp.h create mode 100755 Win/Classlib/realedit.cpp create mode 100755 Win/Classlib/realedit.h create mode 100755 Win/Classlib/statbar.cpp create mode 100755 Win/Classlib/statbar.h create mode 100755 Win/Classlib/stringc.cpp create mode 100755 Win/Classlib/stringc.h create mode 100755 Win/Classlib/textwnd.cpp create mode 100755 Win/Classlib/textwnd.h create mode 100755 Win/Classlib/unitedit.cpp create mode 100755 Win/Classlib/unitedit.h create mode 100755 Win/Classlib/vectorc.h create mode 100755 Win/Classlib/windinfo.cpp create mode 100755 Win/Classlib/windinfo.h create mode 100755 Win/Classlib/window.cpp create mode 100755 Win/Classlib/window.h create mode 100755 Win/Compile.txt create mode 100755 Win/Lev9win.cpp create mode 100755 Win/Level9.hpj create mode 100755 Win/Level9.ico create mode 100755 Win/Level9.manifest create mode 100755 Win/Level9.rc create mode 100755 Win/Level9.rtf create mode 100755 Win/Level9.sln create mode 100755 Win/Level9.vcproj create mode 100755 Win/Resource.h create mode 100755 generic.c create mode 100755 level9.c create mode 100755 level9.h create mode 100755 level9.txt create mode 100755 porting.txt diff --git a/Amiga/amiga.c b/Amiga/amiga.c new file mode 100755 index 0000000..247b005 --- /dev/null +++ b/Amiga/amiga.c @@ -0,0 +1,1076 @@ +/***********************************************************************\ +* +* Level 9 interpreter +* Version 3.0 +* Copyright (c) 1996 Glen Summers +* Copyright (c) 2002 Glen Summers and David Kinder +* +* Level9 Amiga version by David Kinder. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. +* +\***********************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "level9.h" + +#define QUALIFIER_SHIFT (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT) +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) + +struct NewMenu NewMenus[] = +{ + {NM_TITLE, "Project", 0, 0, 0, 0}, + {NM_ITEM, "New Game...", "N", 0, 0, 0}, + {NM_ITEM, NM_BARLABEL, 0, 0, 0, 0}, + {NM_ITEM, "Save...", "S", 0, 0, 0}, + {NM_ITEM, "Restore...", "R", 0, 0, 0}, + {NM_ITEM, NM_BARLABEL, 0, 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_END, 0, 0, 0, 0, 0}}; + +char Version[] = "$VER:Level9 3.0 (7.4.2002)"; +char TitleBar[] = "Level 9"; + +#define TEXTBUFFER_SIZE 1024 +char TextBuffer[TEXTBUFFER_SIZE+1]; +int TextBufferPtr; + +#define HISTORY_LINES 20 +unsigned char *History[HISTORY_LINES]; +int HistoryPosition = HISTORY_LINES; + +extern struct Library *IntuitionBase; +struct Library *AmigaGuideBase; +struct Screen *Screen, *DefaultPubScreen; +struct Window *Window, *OldWindowPtr; +struct RastPort *RastPort; +struct Menu *Menus; +struct DiskObject *Icon; +struct TextFont *Font; +struct FileRequester *GameReq, *SaveReq; +struct Process *ThisProcess; +APTR Visual; + +int ScreenWidth, ScreenHeight; +int DisplayHeight, PreviousHeight; +int MoreCount; +int Playing; + +void amiga_init (char *dir); +void screen_ratio (struct Screen *screen); +void reset_cursor (void); +void rect (long xMin, long yMin, long xMax, long yMax, unsigned long pen); +void text (char *s, int n); +int check_len (char *s, int n); +void cursor (int under); +void set_window (int limit, int x); +int get_key (UWORD * qualifier_addr, int c, int wait); +void redraw_line (unsigned char *buffer, int *pos, int max_size); +void move_text (int offset, int max); +void cursor_left (unsigned char *buffer, int *pos); +void cursor_right (unsigned char *buffer, int *pos); +int fit_text (unsigned char *pointer, int new); +void into_buffer (unsigned char *buffer, unsigned char *new, int *pos, int max_size); +void store_hist (unsigned char *line); +int cmp (const char *a, const char *b); +void help (void); +void about (void); +LONG req (UBYTE * text, UBYTE * gadgets,...); +void busy (int busy); +struct FileRequester *alloc_freq (char *initdir); +void filereq (struct FileRequester *freq, char *buffer, char *title, ULONG save); +int newgame (char *fname); + +void os_printchar (char c) +{ + if (Window == 0) + { + if (c == '\r') + c = '\n'; + fputc (c, stdout); + return; + } + + if (c == '\r') + { + os_flush (); + ClipBlit (RastPort, + Window->BorderLeft, + Window->BorderTop + RastPort->TxHeight, + RastPort, + Window->BorderLeft, + Window->BorderTop, + Window->Width - Window->BorderLeft - Window->BorderRight, + DisplayHeight * RastPort->TxHeight, 0xC0); + rect (Window->BorderLeft, + Window->BorderTop + DisplayHeight * RastPort->TxHeight, + Window->Width - Window->BorderRight - 1, + Window->BorderTop + (DisplayHeight + 1) * RastPort->TxHeight - 1, 0); + reset_cursor (); + + if (++MoreCount >= DisplayHeight) + { + MoreCount = 0; + text ("[More]", 6); + cursor (0); + get_key (0, -1, 1); + cursor (0); + rect (Window->BorderLeft, + Window->BorderTop + DisplayHeight * RastPort->TxHeight, + Window->Width - Window->BorderRight - 1, + Window->BorderTop + (DisplayHeight + 1) * RastPort->TxHeight - 1, 0); + reset_cursor (); + } + + return; + } + + if (isprint(c) == 0) return; + if (TextBufferPtr >= TEXTBUFFER_SIZE) + os_flush (); + *(TextBuffer + (TextBufferPtr++)) = c; +} + +L9BOOL os_input (char *ibuff, int size) +{ + int c, pos = 0; + int inputting = 1; + unsigned long saved_pen; + UWORD qualifier; + + *ibuff = '\0'; + cursor (*ibuff); + saved_pen = RastPort->FgPen; + SetAPen (RastPort, 2); + + while (inputting) + { + set_window (1, TextLength (RastPort, ibuff + pos, strlen (ibuff + pos))); + c = get_key (&qualifier, *(ibuff + pos), 1); + set_window (0, 0); + + switch (c) + { + case -1: + cursor (*(ibuff + pos)); + SetAPen (RastPort, 1); + MoreCount = 0; + return 0; + + case -2: + cursor (*(ibuff + pos)); + cursor_right (ibuff, &pos); + strcpy(ibuff,"save"); + SetAPen (RastPort, 1); + MoreCount = 0; + return 1; + + case -3: + cursor (*(ibuff + pos)); + cursor_right (ibuff, &pos); + strcpy(ibuff,"#restore"); + SetAPen (RastPort, 1); + MoreCount = 0; + return 1; + + case 13: + if (strlen (ibuff) > 0) + { + cursor (*(ibuff + pos)); + cursor_right (ibuff, &pos); + SetAPen (RastPort, 1); + store_hist (ibuff); + MoreCount = 0; + return 1; + } + break; + + case 264: + redraw_line (ibuff, &pos, size); + break; + + case 127: + if (pos > 0) + { + int deleted; + + cursor (*(ibuff + pos)); + deleted = *(ibuff + pos - 1); + memmove (ibuff + pos - 1, ibuff + pos, strlen (ibuff) - pos + 1); + move_text (-char_len (deleted), Window->Width - Window->BorderRight); + Move (RastPort, RastPort->cp_x - char_len (deleted), RastPort->cp_y); + pos--; + cursor (*(ibuff + pos)); + } + break; + + case 260: + if (pos < strlen (ibuff)) + { + int deleted; + + cursor (*(ibuff + pos)); + deleted = *(ibuff + pos); + memmove (ibuff + pos, ibuff + pos + 1, strlen (ibuff) - pos); + Move (RastPort, RastPort->cp_x + char_len (deleted), RastPort->cp_y); + move_text (-char_len (deleted), Window->Width - Window->BorderRight); + Move (RastPort, RastPort->cp_x - char_len (deleted), RastPort->cp_y); + cursor (*(ibuff + pos)); + } + break; + + case 131: + if (pos > 0) + { + if (qualifier & QUALIFIER_SHIFT) + { + cursor (*(ibuff + pos)); + cursor_left (ibuff, &pos); + cursor (*(ibuff + pos)); + } + else + { + cursor (*(ibuff + pos--)); + Move (RastPort, RastPort->cp_x - char_len (*(ibuff + pos)), RastPort->cp_y); + cursor (*(ibuff + pos)); + } + } + break; + + case 132: + if (pos < strlen (ibuff)) + { + if (qualifier & QUALIFIER_SHIFT) + { + cursor (*(ibuff + pos)); + cursor_right (ibuff, &pos); + cursor (*(ibuff + pos)); + } + else + { + cursor (*(ibuff + pos)); + Move (RastPort, RastPort->cp_x + char_len (*(ibuff + pos)), RastPort->cp_y); + pos++; + cursor (*(ibuff + pos)); + } + } + break; + + case 129: + if (qualifier & QUALIFIER_SHIFT) + { + HistoryPosition = 0; + while ((*(History + HistoryPosition) == 0) && (HistoryPosition < HISTORY_LINES)) + HistoryPosition++; + into_buffer (ibuff, HistoryPosition == HISTORY_LINES ? + (unsigned char *) "" : *(History + HistoryPosition), &pos, size); + } + else + { + if ((HistoryPosition > 0) && (*(History + HistoryPosition - 1) != 0)) + { + HistoryPosition--; + into_buffer (ibuff, *(History + HistoryPosition), &pos, size); + } + } + break; + + case 130: + if (qualifier & QUALIFIER_SHIFT) + { + HistoryPosition = HISTORY_LINES; + into_buffer (ibuff, "", &pos, size); + } + else + { + if ((HistoryPosition < HISTORY_LINES - 1) && (*(History + HistoryPosition + 1) != 0)) + { + HistoryPosition++; + into_buffer (ibuff, HistoryPosition == HISTORY_LINES ? + (unsigned char *) "" : *(History + HistoryPosition), &pos, size); + } + else + { + HistoryPosition = HISTORY_LINES; + into_buffer (ibuff, "", &pos, size); + } + } + break; + + default: + if (c >= 32 && c <= 127) + { + if ((strlen (ibuff) < size) && (fit_text (ibuff + pos, c))) + { + memmove (ibuff + pos + 1, ibuff + pos, strlen (ibuff) - pos + 1); + *(ibuff + pos) = c; + + pos++; + cursor (*(ibuff + pos)); + move_text (char_len (c), Window->Width - Window->BorderRight); + os_printchar (c); + cursor (*(ibuff + pos)); + } + } + break; + } + } +} + +char os_readchar (int millis) +{ + char c; + + MoreCount = 0; + c = get_key (0, -1, 0); + + if (c == 0) + { + Delay((millis*50)/1000); + c = get_key (0, -1, 0); + } + return c; +} + +L9BOOL os_stoplist (void) +{ + MoreCount = 0; + return get_key (0, -1, 0); +} + +void os_flush (void) +{ + static int semaphore = 0; + + if (Window == 0) + return; + if (TextBufferPtr < 1) + return; + + if (semaphore) + return; + semaphore = 1; + + char *ptr, *space, *lastspace; + int searching; + + *(TextBuffer+TextBufferPtr) = ' '; + ptr = TextBuffer; + while (check_len (ptr, TextBufferPtr)) + { + space = ptr; + lastspace = space; + searching = 1; + while (searching) + { + while (*space != ' ') + space++; + if (check_len (ptr, space - ptr)) + { + space = lastspace; + text (ptr, space - ptr); + os_printchar ('\r'); + if (*space == ' ') space++; + TextBufferPtr -= space - ptr; + ptr = space; + searching = 0; + } + else + lastspace = space; + space++; + } + } + text (ptr, TextBufferPtr); + TextBufferPtr = 0; + + semaphore = 0; +} + +L9BOOL os_save_file (L9BYTE * Ptr, int Bytes) +{ + char filename[256]; + FILE *f; + + filereq (SaveReq, filename, "Save Game", 1); + if (strcmp (filename, "") == 0) + return FALSE; + + if (f = fopen (filename, "w")) + { + fwrite (Ptr, 1, Bytes, f); + fclose (f); + return TRUE; + } + return FALSE; +} + +L9BOOL os_load_file(L9BYTE *Ptr,int *Bytes,int Max) +{ + char filename[256]; + FILE *f; + + filereq (SaveReq, filename, "Restore Game", 0); + if (strcmp (filename, "") == 0) + return FALSE; + + if (f = fopen (filename, "r")) + { + *Bytes = fread (Ptr, 1, Max, f); + fclose (f); + return TRUE; + } + return FALSE; +} + +L9BOOL os_get_game_file(char *NewName,int Size) +{ + filereq (GameReq, NewName, "Next Level9 Game File", 0); +} + +void os_set_filenumber(char *NewName,int Size,int n) +{ +char *file; +int i; + + file = FilePart(NewName); + for (i = strlen(file)-1; i >= 0; i--) + { + if (isdigit(*(file+i))) + { + *(file+i) = '0'+n; + return; + } + } +} + +void os_graphics(int mode) +{ +} + +void os_cleargraphics(void) +{ +} + +void os_setcolour(int colour, int index) +{ +} + +void os_drawline(int x1, int y1, int x2, int y2, int colour1, int colour2) +{ +} + +void os_fill(int x, int y, int colour1, int colour2) +{ +} + +int main (int argc, char **argv) +{ + amiga_init (""); + newgame (argc > 1 ? argv[1] : 0); + exit (0); +} + +wbmain (struct WBStartup *wbmsg) +{ + char startdir[256]; + char *dir; + + strcpy (startdir, ""); + if (Icon = GetDiskObject (wbmsg->sm_ArgList[0].wa_Name)) + if (dir = FindToolType (Icon->do_ToolTypes, "DIR")) + strcpy (startdir, dir); + + amiga_init (startdir); + newgame (0); + exit (0); +} + +void amiga_init (char *dir) +{ + if ((DefaultPubScreen = LockPubScreen (0)) == 0) + exit (1); + screen_ratio (DefaultPubScreen); + + char prog_name[256]; + static char font_name[MAXFONTPATH]; + char *font_desc, *name_ptr; + static WORD pens[] = + {-1}; + static struct TextAttr font = + {NULL, 0, FS_NORMAL, 0}; + int window = 0; + + if (Icon == NULL) + { + if (GetProgramName (prog_name, 256)) + Icon = GetDiskObject (prog_name); + } + + if (Icon) + { + if (FindToolType (Icon->do_ToolTypes, "WINDOW")) + window = 1; + if (font_desc = FindToolType (Icon->do_ToolTypes, "FONT")) + { + strcpy (font_name, font_desc); + if (name_ptr = strrchr (font_name, '/')) + { + font.ta_Name = font_name; + font.ta_YSize = atoi (name_ptr + 1); + strcpy (name_ptr, ".font"); + } + } + if (font.ta_Name) + Font = OpenDiskFont (&font); + } + if (Font == NULL) + { + font.ta_Name = "topaz.font"; + font.ta_YSize = 8; + Font = OpenFont (&font); + } + + if (window == 0) + { + if ((Screen = OpenScreenTags (0, + SA_Pens, pens, + SA_DisplayID, GetVPModeID (&DefaultPubScreen->ViewPort), + SA_Overscan, OSCAN_TEXT, + SA_Depth, 2, + SA_Type, CUSTOMSCREEN | AUTOSCROLL, + SA_Font, &font, + SA_Title, TitleBar, TAG_DONE)) == 0) + exit (1); + } + + if ((Window = OpenWindowTags (0, + WA_Left, 0, + WA_Top, Screen ? 2 : DefaultPubScreen->BarHeight + 1, + WA_Width, Screen ? Screen->Width : ScreenWidth, + WA_Height, Screen ? Screen->Height - 2 : ScreenHeight - DefaultPubScreen->BarHeight - 1, + WA_SmartRefresh, 1, + WA_NewLookMenus, 1, + WA_AutoAdjust, 1, + WA_Borderless, Screen ? 1 : 0, + WA_Backdrop, Screen ? 1 : 0, + WA_Activate, 1, + WA_CloseGadget, Screen ? 0 : 1, + WA_DragBar, Screen ? 0 : 1, + WA_DepthGadget, Screen ? 0 : 1, + WA_SizeGadget, Screen ? 0 : 1, + WA_SizeBBottom, Screen ? 0 : 1, + WA_Title, TitleBar, + WA_ScreenTitle, TitleBar, + WA_IDCMP, IDCMP_RAWKEY | IDCMP_VANILLAKEY | IDCMP_MENUPICK | IDCMP_CLOSEWINDOW | IDCMP_CHANGEWINDOW, + Screen ? WA_CustomScreen : WA_PubScreen, Screen ? Screen : DefaultPubScreen, + TAG_DONE)) == 0) + exit (1); + + ThisProcess = (struct Process *)FindTask(0); + OldWindowPtr = ThisProcess->pr_WindowPtr; + ThisProcess->pr_WindowPtr = Window; + + if ((Visual = GetVisualInfo (Window->WScreen, TAG_DONE)) == 0) + exit (1); + if ((Menus = CreateMenus (NewMenus, GTMN_NewLookMenus, TRUE, TAG_DONE)) == 0) + exit (1); + LayoutMenus (Menus, Visual, GTMN_NewLookMenus, TRUE, TAG_DONE); + SetMenuStrip (Window, Menus); + + if ((GameReq = alloc_freq (dir)) == 0) + exit (1); + if ((SaveReq = alloc_freq (dir)) == 0) + exit (1); + + RastPort = Window->RPort; + SetDrMd (RastPort, JAM2); + SetAPen (RastPort, 1); + SetBPen (RastPort, 0); + SetFont (RastPort, Font); + DisplayHeight = ((Window->Height - Window->BorderTop - Window->BorderBottom) / RastPort->TxHeight) - 1; + PreviousHeight = DisplayHeight; + + reset_cursor (); +} + +__autoexit void amiga_exit (void) +{ + int i; + + for (i = 0; i < HISTORY_LINES; i++) + { + if (*(History + i)) + FreeVec (*(History + i)); + *(History + i) = 0; + } + + if (GameReq) + FreeAslRequest (GameReq); + if (SaveReq) + FreeAslRequest (SaveReq); + if (Menus) + FreeMenus (Menus); + if (Visual) + FreeVisualInfo (Visual); + if (ThisProcess) + ThisProcess->pr_WindowPtr = OldWindowPtr; + if (Window) + CloseWindow (Window); + if (Screen) + CloseScreen (Screen); + if (Font) + CloseFont (Font); + if (DefaultPubScreen) + UnlockPubScreen (0, DefaultPubScreen); + if (Icon) + FreeDiskObject (Icon); +} + +void screen_ratio (struct Screen *screen) +{ + struct TagItem vti[] = + {VTAG_VIEWPORTEXTRA_GET, 0, + VTAG_END_CM, 0}; + struct ViewPortExtra *vpe; + + 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; + } + } +} + +void reset_cursor (void) +{ + Move (RastPort, Window->BorderLeft, Window->BorderTop + (DisplayHeight * RastPort->TxHeight)); +} + +void rect (long xMin, long yMin, long xMax, long yMax, unsigned long pen) +{ + unsigned long saved_pen; + + saved_pen = RastPort->FgPen; + SetAPen (RastPort, pen); + RectFill (RastPort, xMin, yMin, xMax, yMax); + SetAPen (RastPort, saved_pen); +} + +void text (char *s, int n) +{ + Move (RastPort, RastPort->cp_x, RastPort->cp_y + RastPort->TxBaseline); + Text (RastPort, s, n); + Move (RastPort, RastPort->cp_x, RastPort->cp_y - RastPort->TxBaseline); +} + +int check_len (char *s, int n) +{ + return TextLength (RastPort, s, n) > Window->Width - Window->BorderRight - RastPort->cp_x; +} + +void cursor (int under) +{ + int size; + + os_flush (); + size = (under == 0) ? RastPort->TxWidth : char_len (under); + + SetDrMd (RastPort, COMPLEMENT); + rect (RastPort->cp_x, RastPort->cp_y, + RastPort->cp_x + size - 1, RastPort->cp_y + RastPort->TxHeight - 1, 0); + SetDrMd (RastPort, JAM2); +} + +void set_window (int limit, int x) +{ + if (limit) + { + WindowLimits (Window, + RastPort->cp_x + RastPort->TxWidth + Window->BorderRight + x, + Window->BorderTop + Window->BorderBottom + (RastPort->TxHeight * 3), ~0, ~0); + } + else + { + WindowLimits (Window, Window->Width, Window->Height, Window->Width, + Window->Height); + } +} + +int get_key (UWORD * qualifier_addr, int c, int wait) +{ + struct IntuiMessage *imsg; + ULONG class; + UWORD code, qualifier; + int old_height; + + os_flush (); + while (1) + { + while (imsg = (struct IntuiMessage *) GetMsg (Window->UserPort)) + { + class = imsg->Class; + code = imsg->Code; + qualifier = imsg->Qualifier; + ReplyMsg ((struct Message *) imsg); + if (qualifier_addr) + *qualifier_addr = qualifier; + switch (class) + { + case IDCMP_RAWKEY: + switch (code) + { + case 0x4C: + return 129; + case 0x4D: + return 130; + case 0x4F: + return 131; + case 0x4E: + return 132; + case 0x5F: + help (); + break; + } + break; + case IDCMP_VANILLAKEY: + switch (code) + { + case 8: + return 127; + case 13: + return 13; + case 127: + return 260; + default: + if (code >= 32 && code <= 126) + return code; + break; + } + break; + case IDCMP_CLOSEWINDOW: + exit (0); + break; + case IDCMP_CHANGEWINDOW: + old_height = DisplayHeight; + DisplayHeight = ((Window->Height - Window->BorderTop - Window->BorderBottom) / RastPort->TxHeight) - 1; + + if (PreviousHeight > DisplayHeight) + { + rect (Window->BorderLeft, + Window->BorderTop + (DisplayHeight * RastPort->TxHeight), + Window->Width - Window->BorderRight - 1, + Window->Height - Window->BorderBottom - 1, 0); + + if (RastPort->cp_y + RastPort->TxHeight > Window->Height - Window->BorderBottom) + { + Move (RastPort, RastPort->cp_x, Window->BorderTop + (DisplayHeight * RastPort->TxHeight)); + rect (Window->BorderLeft, RastPort->cp_y, + Window->Width - Window->BorderRight - 1, + RastPort->cp_y + RastPort->TxHeight - 1, 0); + PreviousHeight = DisplayHeight; + return 264; + } + } + PreviousHeight = DisplayHeight; + break; + case IDCMP_MENUPICK: + if (code != MENUNULL) + { + if (MENUNUM (code) == 0) + { + switch (ITEMNUM (code)) + { + case 0: + if (c != -1) + { + cursor (c); + SetAPen (RastPort, 1); + int r = newgame (0); + SetAPen (RastPort, 2); + cursor (c); + if (r != 0) + return r; + } + break; + case 2: + if (c != -1) return -2; + break; + case 3: + if (c != -1) return -3; + break; + case 5: + help (); + break; + case 6: + about (); + break; + case 8: + exit (0); + break; + } + } + } + break; + } + } + if (wait) + WaitPort (Window->UserPort); + else + return 0; + } +} + +void redraw_line (unsigned char *buffer, int *pos, int max_size) +{ + char *current_buffer; + + cursor (*(buffer + *pos)); + if ((current_buffer = AllocVec (max_size, MEMF_CLEAR)) == 0) + return; + strcpy (current_buffer, buffer); + into_buffer (buffer, current_buffer, pos, max_size); + FreeVec (current_buffer); +} + +int char_len (int c) +{ + unsigned char buffer; + + if (c < 0) + c += 256; + buffer = (unsigned char) c; + return TextLength (RastPort, &buffer, 1); +} + +void move_text (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), RastPort->TxHeight, 0xC0); +} + +void cursor_left (unsigned char *buffer, int *pos) +{ + while (*pos > 0) + { + (*pos)--; + Move (RastPort, RastPort->cp_x - char_len (*(buffer + *pos)), RastPort->cp_y); + } +} + +void cursor_right (unsigned char *buffer, int *pos) +{ + while (*pos < strlen (buffer)) + { + Move (RastPort, RastPort->cp_x + char_len (*(buffer + *pos)), RastPort->cp_y); + (*pos)++; + } +} + +int fit_text (unsigned char *pointer, int new) +{ + int a, b; + + a = TextLength (RastPort, pointer, strlen (pointer)) + char_len (new) + RastPort->cp_x + RastPort->TxWidth; + b = Window->Width - Window->BorderRight; + return (a > b) ? 0 : 1; +} + +void into_buffer (unsigned char *buffer, unsigned char *new, int *pos, int max_size) +{ + if (new == 0) + return; + cursor (*(buffer + *pos)); + + int saved_x_position; + int saved_colour; + int i; + + cursor_left (buffer, pos); + saved_colour = RastPort->FgPen; + saved_x_position = RastPort->cp_x; + SetAPen (RastPort, RastPort->BgPen); + for (i = 0; i < strlen (buffer); i++) + os_printchar (*(buffer + i)); + os_flush (); + SetAPen (RastPort, saved_colour); + Move (RastPort, saved_x_position, RastPort->cp_y); + + *buffer = 0; + while (1) + { + if ((fit_text (buffer, *(new + *pos)) == 0) || (*(new + *pos) == 0) || (*pos == max_size)) + { + for (i = 0; i < strlen (buffer); i++) + os_printchar (*(buffer + i)); + os_flush (); + cursor (*(buffer + *pos)); + return; + } + else + { + *(buffer + *pos) = *(new + *pos); + (*pos)++; + *(buffer + *pos) = 0; + } + } +} + +void store_hist (unsigned char *line) +{ + int i; + + if ((*line != 0) && (cmp (*(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 cmp (const char *a, const char *b) +{ + if ((a == 0) || (b == 0)) + return 1; + return strcmp (a, b); +} + +void help (void) +{ + struct NewAmigaGuide guide = + {0, "Level9.guide", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + guide.nag_Screen = Window->WScreen; + if (AmigaGuideBase == 0) + AmigaGuideBase = OpenLibrary ("amigaguide.library", 34); + if (AmigaGuideBase != 0) + CloseAmigaGuide (OpenAmigaGuide (&guide, TAG_DONE)); +} + +void about (void) +{ + req ("Level 9 Interpreter v3.0\n" + "Copyright (c) 1996 Glen Summers\n" + "Copyright (c) 2002 Glen Summers and David Kinder\n\n" + "Level9 is released under the terms of the GNU General\n" + "Public License. See the file COPYING that is included\n" + "with this program for details.","Continue"); +} + +LONG req (UBYTE * text, UBYTE * gadgets,...) +{ + va_list arguments; + LONG return_value; + static struct EasyStruct requester = + {sizeof (struct EasyStruct), 0, TitleBar, 0, 0}; + + requester.es_TextFormat = text; + requester.es_GadgetFormat = gadgets; + va_start (arguments, gadgets); + busy (1); + return_value = EasyRequestArgs (Window, &requester, 0, arguments); + busy (0); + va_end (arguments); +} + +void busy (int busy) +{ + if (Window && IntuitionBase->lib_Version >= 39) + SetWindowPointer (Window, WA_BusyPointer, busy, TAG_DONE); +} + +struct FileRequester * alloc_freq (char *initdir) +{ + return AllocAslRequestTags (ASL_FileRequest, + strcmp (initdir, "") == 0 ? TAG_IGNORE : ASLFR_InitialDrawer, initdir, + ASLFR_SleepWindow, 1, + ASLFR_RejectIcons, 1, TAG_DONE); +} + +void filereq (struct FileRequester *freq, char *buffer, char *title, ULONG save) +{ + if (AslRequestTags (freq, + ASLFR_Window, Window, + ASLFR_TitleText, title, + ASLFR_DoSaveMode, save, TAG_DONE)) + { + strcpy (buffer, freq->fr_Drawer); + AddPart (buffer, freq->fr_File, 256); + } + else + strcpy (buffer, ""); +} + +int newgame (char *fname) +{ +char game[256]; +BPTR lock; + + fname ? strcpy (game, fname) : + filereq (GameReq, game, "Select a Level9 Game", 0); + + if (strcmp (game, "") == 0) + return 0; + + if (lock = Lock(game,ACCESS_READ)) + { + NameFromLock(lock,game,256); + UnLock(lock); + } + + if (LoadGame (game,NULL)) + { + if (Playing) + return -1; + Playing = TRUE; + while (RunGame ()); + Playing = FALSE; + } + else + req ("Unable to load game", "Cancel"); + return -1; +} diff --git a/Amiga/dmakefile b/Amiga/dmakefile new file mode 100755 index 0000000..46702c3 --- /dev/null +++ b/Amiga/dmakefile @@ -0,0 +1,13 @@ +SRCS = level9.c amiga.c generic.c +OBJS = $(SRCS:"*.c":"*.o") + +all : Level9 Level9StdIO + +Level9 : level9.o amiga.o + dcc %(right) -o %(left) + +Level9StdIO : level9.o generic.o + dcc %(right) -o %(left) + +$(OBJS) : $(SRCS) + dcc -DNO_SCAN_GRAPHICS -c %(right) -o %(left) diff --git a/COPYING b/COPYING new file mode 100755 index 0000000..60549be --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/DOS/dos.c b/DOS/dos.c new file mode 100755 index 0000000..73268db --- /dev/null +++ b/DOS/dos.c @@ -0,0 +1,409 @@ +/***********************************************************************\ +* +* Level 9 interpreter +* Version 3.0 +* Copyright (c) 1996 Glen Summers +* Copyright (c) 2002 Glen Summers and David Kinder +* +* Level9 16 bit DOS version by David Kinder. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. +* +\***********************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "level9.h" + +unsigned _stklen = 16384; + +#define TEXTBUFFER_SIZE 10240 +char TextBuffer[TEXTBUFFER_SIZE+1]; +int TextBufferPtr = 0; + +#define HISTORY_LINES 20 +unsigned char *History[HISTORY_LINES]; +int HistoryPosition = HISTORY_LINES; + +struct text_info TextInfo; +int Hotkey = 1; +int MoreCount = 0; + +#define KEY_BRK 3 +#define KEY_HOME 327 +#define KEY_CRSRU 328 +#define KEY_CRSRL 331 +#define KEY_CRSRR 333 +#define KEY_END 335 +#define KEY_CRSRD 336 +#define KEY_DEL 339 +#define KEY_F12 390 + +L9UINT32 filelength(FILE *f); + +int character(void); +void into_buffer(char *buffer,char *newb,int *x,int *i); +void store_hist(char *line); + +void os_printchar(char c) +{ + if (c == '\r') + { + os_flush(); + cprintf("\r\n"); + + if (++MoreCount >= TextInfo.screenheight) + { + MoreCount = 0; + cprintf("[More]"); + character(); + gotoxy(1,wherey()); + cprintf(" "); + gotoxy(1,wherey()); + } + return; + } + + if (isprint(c) == 0) return; + if (TextBufferPtr >= TEXTBUFFER_SIZE) os_flush(); + *(TextBuffer + (TextBufferPtr++)) = c; +} + +L9BOOL os_input(char *ibuff, int size) +{ +int x,y,c,i = 0; + + x = wherex(); + y = wherey(); + *ibuff = '\0'; + + while (1) + { + switch (c = character()) + { + case '\r': + if (strlen(ibuff) > 0 || Hotkey == 0) + { + os_printchar('\r'); + MoreCount = 0; + store_hist(ibuff); + return TRUE; + } + case '\b': + if (i > 0) + { + memmove(ibuff+i-1,ibuff+i,strlen(ibuff+i)+1); + i--; + gotoxy(x,y); + cprintf("%s ",ibuff); + gotoxy(x+i,y); + } + break; + case KEY_DEL: + if (i < strlen(ibuff)) + { + memmove(ibuff+i,ibuff+i+1,strlen(ibuff+i+1)+1); + gotoxy(x,y); + cprintf("%s ",ibuff); + gotoxy(x+i,y); + } + break; + case KEY_CRSRL: + if (i > 0) i--; + gotoxy(x+i,y); + break; + case KEY_CRSRR: + if (i < strlen(ibuff)) i++; + gotoxy(x+i,y); + break; + case KEY_HOME: + i = 0; + gotoxy(x,y); + break; + case KEY_END: + i = strlen(ibuff); + gotoxy(x+i,y); + break; + case KEY_CRSRU: + if ((HistoryPosition > 0) && (*(History+HistoryPosition-1) != NULL)) + { + HistoryPosition--; + into_buffer(ibuff,*(History+HistoryPosition),&x,&i); + } + break; + case KEY_CRSRD: + if ((HistoryPosition < HISTORY_LINES-1) && (*(History+HistoryPosition+1) != NULL)) + { + HistoryPosition++; + into_buffer(ibuff,HistoryPosition == HISTORY_LINES ? "" : *(History+HistoryPosition),&x,&i); + } + else + { + HistoryPosition = HISTORY_LINES; + into_buffer(ibuff,"",&x,&i); + } + break; + default: + if (c < 256 && i < size && isprint(c) && wherex()+strlen(ibuff+i) < TextInfo.screenwidth) + { + memmove(ibuff+i+1,ibuff+i,strlen(ibuff+i)+1); + *(ibuff+i++) = c; + gotoxy(x,y); + cprintf("%s",ibuff); + gotoxy(x+i,y); + } + break; + } + } +} + +char os_readchar(int millis) +{ + os_flush(); + if (kbhit() == 0) + delay(millis); + if (kbhit() != 0) + { + MoreCount = 0; + return character(); + } + else return 0; +} + +L9BOOL os_stoplist(void) +{ + os_flush(); + if (kbhit() != 0) + { + character(); + return TRUE; + } + return FALSE; +} + +void os_flush(void) +{ +static int semaphore = 0; +int ptr, space, lastspace, searching; + + if (TextBufferPtr < 1) return; + if (semaphore) return; + semaphore = 1; + + *(TextBuffer+TextBufferPtr) = ' '; + ptr = 0; + while (TextBufferPtr+wherex()-1 > TextInfo.screenwidth) + { + space = ptr; + lastspace = space; + searching = 1; + while (searching) + { + while (TextBuffer[space] != ' ') space++; + if (space-ptr+wherex()-1 > TextInfo.screenwidth) + { + space = lastspace; + cprintf("%.*s%s",space-ptr,TextBuffer+ptr,wherex()+space-ptr > TextInfo.screenwidth ? "" : "\r\n"); + if (TextBuffer[space] == ' ') space++; + TextBufferPtr -= (space-ptr); + ptr = space; + searching = 0; + } + else lastspace = space; + space++; + } + } + cprintf("%.*s", TextBufferPtr, TextBuffer+ptr); + TextBufferPtr = 0; + + semaphore = 0; +} + +L9BOOL os_save_file(L9BYTE * Ptr, int Bytes) +{ +char name[256]; +FILE *f; + + os_flush(); + cprintf("Save file: "); + Hotkey = 0; + os_input(name,256); + Hotkey = 1; + + f = fopen(name, "wb"); + if (!f) return FALSE; + fwrite(Ptr, 1, Bytes, f); + fclose(f); + return TRUE; +} + +L9BOOL os_load_file(L9BYTE *Ptr,int *Bytes,int Max) +{ +char name[256]; +FILE *f; + + os_flush(); + cprintf("Load file: "); + Hotkey = 0; + os_input(name,256); + Hotkey = 1; + + f = fopen(name, "rb"); + if (!f) return FALSE; + + *Bytes = filelength(f); + if (*Bytes > Max) + { + fclose(f); + return FALSE; + } + fread(Ptr, 1, *Bytes, f); + fclose(f); + return TRUE; +} + +L9BOOL os_get_game_file(char *NewName,int Size) +{ + os_flush(); + cprintf("Load next game: "); + Hotkey = 0; + os_input(NewName,Size); + Hotkey = 1; + return TRUE; +} + +void os_set_filenumber(char *NewName,int Size,int n) +{ +char *p; +int i; + + p = strrchr(NewName,'\\'); + if (p == NULL) p = NewName; + for (i = strlen(p)-1; i >= 0; i--) + { + if (isdigit(p[i])) + { + p[i] = '0'+n; + return; + } + } +} + +void os_graphics(int mode) +{ +} + +void os_cleargraphics(void) +{ +} + +void os_setcolour(int colour, int index) +{ +} + +void os_drawline(int x1, int y1, int x2, int y2, int colour1, int colour2) +{ +} + +void os_fill(int x, int y, int colour1, int colour2) +{ +} + +int main(int argc, char **argv) +{ +int i; + + for (i = 0; i < HISTORY_LINES; i++) *(History+i) = NULL; + gettextinfo(&TextInfo); + + if (argc != 2) + { + cprintf( + "Level 9 Interpreter v3.0\r\n" + "Copyright (c) 1996 Glen Summers\r\n" + "Copyright (c) 2002 Glen Summers and David Kinder\r\n" + "\r\n" + "Level9 is released under the terms of the GNU General Public License.\r\n" + "See the file COPYING that is included with this program for details.\r\n" + "\r\n" + "Syntax: level9 story-file\r\n" + " story-file is a Level 9 data file, a 48K Spectrum SNA snapshot\r\n" + " or any linear memory dump containing a Level 9 game.\r\n"); + return 0; + } + + clrscr(); + gotoxy(1,TextInfo.screenheight); + if (!LoadGame(argv[1],NULL)) + { + cprintf("Error: Unable to open game file\r\n"); + return 0; + } + while (RunGame()); + StopGame(); + FreeMemory(); + return 0; +} + +int character(void) +{ +int c; + + c = getch(); + if (c == 0) c = getch()+256; + if (c == KEY_F12 || c == KEY_BRK) + { + StopGame(); + FreeMemory(); + clrscr(); + exit(0); + } + return c; +} + +void into_buffer(char *buffer,char *newb,int *x,int *i) +{ + gotoxy(*x,wherey()); + strcpy(buffer,newb); + cprintf("%s",buffer); + clreol(); + *i = strlen(buffer); + gotoxy(*x+*i,wherey()); +} + +int cmp(const char *a,const char *b) +{ + if (a == 0 || b == 0) return 1; + return strcmp(a,b); +} + +void store_hist(char *line) +{ +int i; + + if (*line != NULL && cmp(*(History+HISTORY_LINES-1),line) != 0) + { + if (*History) free(*History); + for (i = 0; i < HISTORY_LINES-1; i++) *(History+i) = *(History+i+1); + *(History+HISTORY_LINES-1) = malloc(strlen(line)+1); + if (*(History+HISTORY_LINES-1)) strcpy(*(History+HISTORY_LINES-1),line); + } + HistoryPosition = HISTORY_LINES; +} + diff --git a/DOS32/Level9.gdt b/DOS32/Level9.gdt new file mode 100755 index 0000000000000000000000000000000000000000..0654749916442a79993259bc9d5045c8f5f0b314 GIT binary patch literal 9110 zcmdT~&2!U66kj=ZWarCC6A}n*ECMAVfaHYEv}B5b5@w(ULOTpSfU6OC%df`&8CH=jXWXB1CAh|Q@=e991 zofgxg#jIkb)Pm7(Yu4!LU=`BWYRRK@pS@0mZ zPThzW>#r=0_|OS`L9gsrCggKkVXjm*=BuXVp7m*q8r38ZNgom!=$muypZm)ux!I!=O#4~?oak*x3*$>r2xPehprVsb&4Xh)-| zqiwQ$z8$&ndj+owxP#OJ#gyD~yHl`ry_%Xct1j0~(sWeIHmYvtqHVakYE-SuZpzh{ zT=j~y`#{o<9Z1YOMbi->T`{#{iXJ7XcB*a8G!wdQn|6<7>y~Egs#it3<7)0@N3^tx z?z(zhvr7xf8a{P!5*4GWk0*G8QbyHqRf@J>G^$12PG}W(Z#llbXcW4=VpYdQYfJpY zl8}py=3-;HSZ*wq8|7`CH;Z~QXk7wUu3qUxqf54Cm5qXv;PH6DHm!J}VpyVUN@xp( zkdzLPY|+*hsZp_Fwu7rfrME7>{<+F!r%ZYvu}()@&{g!x)$JG>3T2atky6M6i8{T2 z1HsMoDLRNb)x8j-tcdzX&c^+mq93Mwk)$k39AL(7i=rr4f`w5EvM^wu7K)I73V95UOt!IUGQ9?5&}t0kKFL}gJzU+=e1zROeoSwDqR>1#mUQjlLKXAQcJ za3?~N9Lf0kQ{(`t{vQ7e0!|%LkU!6!c+uyn>79p+5mPgks8VRm!;zQx;ty(RUWn6V zCB#TuVo(OzO(Ozc6Z_lpf)6Sr0x9lf5^PxzSRTDy2W@{EQG5fE3%{X$R2l(x@;_90 z7}&}45s?Xpfqj5%5md%B7Yg<}H$0tgSYMxCWsJBBwQi=`TLphorO<%?vm8eg@khNZ z>ag!c8Da20LWT#r4+R=W4_w?D*g27jP5}2!+3d35PuJ^TvNoUthw~h^g1FQ|+xe{&%jkaKK_6n~~b+ z|24ArC*+92cz-V^<{w1DjQ$`$g^|$Tptgegf_InD`3O1dGEz416J}z07Qoz>9 zHZCZ^==Vd~O#Tts0!S-S{eL^k2x9?{L=ThSw}iBS2Z;n9|1YGokFE>s2AD|Ti@z3` z1NeNj{u-cmM&TVv)Vx6SH6UqX035{&(BVrM8~EM>f-S-pLV1DDeyGm@4Qqh)^fk1H zv0mDlgYHu8tuhDDFsiQs*KB3{&0}z26n)1>3ZT7R6xd0RVPK!4%0cggL&5$4Zk)h- zo|>@hWuvso0nN~(RnTr+@liJ#thW*EChYI2?@=8+mRq6FzJ($((k<-k z!9@3{($++K&3bh`U)3Dnz8#8)GRVXF_HUZ$+pU$SU+d}XCj8OgzHk4fm3%w5#oFAy z-imeA%pQGxPvd|n3i`BO4&&l^#d!P)PZ!F?AHqEoVYoqU1@+a!RvG(^>pJR9gDv|f zxlP~O1$ubtvEP8Ug1**5EbLq2TzBm(-_V=?WtM*xT8ILA*jYYM{$`x6h8pnFqYbU; z3SU91Tm2;kb+%<@`9?3lef3E9(e}JNU5Lu%dvI{k4)6o5M?su6e^Ffg1>_Hf_??W0 z_(oI}bhH+>ihplJedX=lV-H<^K}Itf-L-A1wI7=oiET@dIQWrPcXq@+S$B z^ls!3;q*10qu_*HHi~-BN!@X@k}fUcir-u$w^yUsxt=q}4Cjc6e>fCe!{mS3perll zah`owV|M(Nyz8I5x%S`5m1CgLaA4M=A zc@O{d?r#C=2vH<>jolBe#ypW&V6oWuF6Fm<|H(fdJ^b9ylzb+Ff<o7sp|6Il{4y^@kDC7V8bU0DN_m3#AIO&v6VD{5ii1;^Q|J$Sr{Z!hxq8X4SINzh z(DcWORUA6~Q-8=?$nNh?m;Y_Og&8bY|K!#4C{I=6?2E}FOT>9(IrD5zO+_BJY62Ix*FUJ#x%Q{-nHM2f&Z?}NUaSp8sqVg)Op-) zpAH|0wh6far;NJwgAhbqWrfofbURX7zmjKy%5m56-57GQSc%lFXMgC_GoqoF1#TXm z36;k(b)BHwOHc@Kt?U*(JU^7qwU%w28hS?Uw{@G04)3D=WVq}`(GcS_yI|NnI?ZnM6QWBA`S>sM=K#1NMbkaqJjadwIe9$9(wplcnn$>{1~!q?ZtIr@v}AB_ z;Aui=t@j~D=-MO554$6f!j5)#cZYbU2^hG=qJYRzEazt;uK}+EkP8;cB$4OrS-HwA z;A;k$pT-%RCo(v-V0T!A7|+Y3U?#n(aqepn_R3^dkLm>pzZ4;xE1m|+8uSYmiHl66 zd3+|A%;3u7=e$5Du=WGei01{PhF-n$CPov<3zipBiF*77#-$1b<6)@kVM3yZS7KF< zf;mx{2DBjqGityTuXA(?Sr36U(QN~2ky3?iTTq63YsPQN3ZlhM zO>r5Xc1!FXy{boO5GznitXWV`oU&tTH*yOl2#Z6rlI`pcUf@%m za}SVJmc&79O$Uaik>6s+32IeXRRV2TMPo_ptuMcN zc&RR)<&TRf_$FCA`Db{_SJ_$_5w0?@RirRH$~fxfW3~un6zyzmFky@EoU6v@v>JWC zUhT2PBAjn6&I>-zH%GBpk6Med%fvsqVbUAEfFFPBa{WFEESfb!MDS22PR%`cysLRBUWqekH5Q&Ukwo`-P3YQQfuIBKD99Ch|3P-;z7f3nh^xGY4L&Owh zh!QGDQSr32W1orFJP1UdGaRZw0Ilg+r&H~jv5BdRv}Aahbq>3=_C$O%-Xsy^bE{Ow znqbn9wMT5@y~zernWiPAw{)u}BjL!@lFi2Aeu7Rwld z%h?&c7N6V64{)AAi^2P1S*CXFu+LIS<#-yrZz=N)*6Jw`a@t}`kDKjuOWH$FbA_&2 z<2}Yve$*$^nocDVMGhPRwjadQovNqcd=P)g5MB__Y%htr0iwju1?tHtNH8=n#%asM0<(&2avVs7~<|fs*>uVTENrSgeF-R8yX1=d;8yW3i-TA(U`+POge|?`I z1#r*HLfo|OeIGN(6BWRszkUC9b*IVzmf`!id_P3QUq50wc^HU5roKP*eUpCdt#5oE zs1iOGiSJ_&&h1o$ngXPA2=y!92MQ8{c0U@05ejCT8;t_DGSW~A+cE;w+-S5W`03j- z373l>UET2g^{qy5PNMRQA^hDKmoI)Cf+Hhd7IF;L!jOkAsYY`t6K{^1;B?Z38-aRSn0R;&L5>l%Q6cP`FSFXH*Vv?2 z({%zhB4_HMVv*+~=${xC>T*I#049&=I}WGxMJLwJGV+iw$#l9l*RjzPuOzoMsp9wO zOk&|$SG@BjlxKJW2IB%(U{boWpp;2}yz0BdRU&GVt}u}Y9IreunI@viM4_KSu=9=J zO`(OD#DZ7_rJc&pkV*09mn=r>dyPX^b5K?M&VywfJ6N$5Fl~vHS2tnU#l*WU2%_Fx zssuQeX^IRtIYIK8m85F|@NiuwphErTVs*2QmpcKCx zKFC+zr%>Z``e_V)!VX8Dk+pxOP5d)5?tT5&SoGe*E1x%f7@rLu%JD;ao)&WSIjnN{ zIh@Mp7gXs?%vV18g4E~>sit34&9l+p$xSL9L3KI&Qifw(=!p!!ij(12ufB55Jb@v#*s5P(dj%!5wOj)fC@YTwD^!SpMu^DAOqM z498DIfYc0Mnc&6V6XFX>BoPk35ykMEWr9nMz6GZT-^$Uqu*cDNC|SJk;1QG2cc|*T zr#yl6KILlkG#2NRr=|$>j>+;y$Efax-%AR|TI)U18@lRj1eUQ?W5eupTDJwD3_uiY zasNK`_oyRlDVvAh=0_h@JHbXxYO*mzQ|{m2nqm*pl#f5ynqm(@cPY{!6SA8jL^Lv? zZ2*~#&ia|8xL$b>Gra{s1l+Z3v^s`8M6KYv0mGX)QlA

>nhwfWtY4{?S)7s?7mrk3gi;|y~+n0&M0 zWzNtJKm!MH5uRnEKT{?sb4>{K9s4-+-42bQA>ank7JQ z!^;dFu@j0?$cAdp)wp?T=^;3$>!FTq;jS!jW7kb!V^av+TDh?&u}YTo!bhLAfwci` zpamoCwMg-FpQ2_37@1$L<}&Fx5uvx>Et^}=#^zw8y|#HMeHs&g*fw4p+Q17ALGHWJ zWWWq^?QSv}Gq%WRz{3_Y8dKWHfRpYaV{^tf8JqA{GB&5Qkg`ug;niwkT=Yr%4L*gQBWiFpU|TgkX~fe2b3lO`2pNAW?X+ z5;GH!8iTM}Q%s!Vf8Q-ETQOmx9fr6sQvoxyd^^k;;cjafX;Y3A)Q+MEWSH3;1tC;u zCe-7c%jdPx8baD(=+xz!a6-SB_#S^oR8ZZ)!^yt^72(w% literal 0 HcmV?d00001 diff --git a/DOS32/allegro.c b/DOS32/allegro.c new file mode 100755 index 0000000..73a4ec3 --- /dev/null +++ b/DOS32/allegro.c @@ -0,0 +1,1667 @@ +/***********************************************************************\ +* +* Level 9 interpreter +* Version 3.0 +* Copyright (c) 1996 Glen Summers +* Copyright (c) 2002 Glen Summers and David Kinder +* +* Level9, 32 bit DOS and Windows Allegro versions. +* Allegro 32 bit interface by David Kinder. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. +* +* The font data in font.h is generated from default.fnt by the +* command +* +* bin2h default.fnt raw_font_data font.h +* +\***********************************************************************/ + +#include +#include +#include +#include + +#ifdef __DJGPP__ +#include +#endif + +#include + +#ifdef _WIN32 +#include +#include +#include "resource.h" +#endif + +/* Definitions from the interpreter. */ +#include "level9.h" + +/* Data for the font. */ +#include "font.h" + +/* Explicitly specify Allegro drivers to reduce executable size. */ +#ifdef __DJGPP__ +BEGIN_GFX_DRIVER_LIST + GFX_DRIVER_VESA3 + GFX_DRIVER_VESA2L + GFX_DRIVER_VESA2B + GFX_DRIVER_VESA1 +END_GFX_DRIVER_LIST + +BEGIN_COLOR_DEPTH_LIST + COLOR_DEPTH_8 + COLOR_DEPTH_16 +END_COLOR_DEPTH_LIST + +BEGIN_DIGI_DRIVER_LIST +END_DIGI_DRIVER_LIST + +BEGIN_MIDI_DRIVER_LIST +END_MIDI_DRIVER_LIST +#endif + +#ifdef _MAX_PATH +#define MAX_FILE_NAME _MAX_PATH +#else +#define MAX_FILE_NAME 256 +#endif + +/* File name buffers */ +char story_name[MAX_FILE_NAME]; +char gfx_name[MAX_FILE_NAME]; +char save_name[MAX_FILE_NAME]; + +/* Custom font variables */ +#define FONT_WIDTH_OFFSET 1536 +#define FONT_HEIGHT 16 +short* font_data = NULL; +char* font_width = NULL; + +/* Buffered output variables */ +#define OUTPUT_LIMIT 512 +char output_buffer[OUTPUT_LIMIT]; +int output_index = 0; + +/* Input line variables */ +#define DEFAULT_CURSOR_WIDTH 4 +#define INPUT_PENDING 0 +#define INPUT_LINE 1 +#define INPUT_LIMIT 256 +#define HISTORY_SIZE 20 +char input_buffer[INPUT_LIMIT]; +char input_history[HISTORY_SIZE][INPUT_LIMIT]; +int history_index = 0; + +/* Display bitmap variables */ +BITMAP* display = NULL; +PALETTE palette; +int gfx_mode = GFX_AUTODETECT_FULLSCREEN; +int screen_width = 640; +int screen_height = 480; +int text_x = 0; +int text_y = 0; +int top_y = 0; + +/* Paging variables */ +int page_counter = 0; +int page_limit = 0; + +/* Set this flag to indicate the interpreter should exit. */ +int exit_interpreter = FALSE; + +/* Graphics variables */ +int high_res_pics = 0; +int fast_pics = 0; +int gfx_width = 320; +int gfx_height = 96; +BITMAP* gfx = NULL; + +/* Colours for graphics */ +struct RGB colours[8] = +{ + { 0x00,0x00,0x00 }, + { 0x3F,0x00,0x00 }, + { 0x0C,0x39,0x0C }, + { 0x3F,0x3F,0x00 }, + { 0x00,0x00,0x3F }, + { 0x28,0x19,0x00 }, + { 0x00,0x3F,0x3F }, + { 0x3F,0x3F,0x3F } +}; + +/* Routines specific to this Allegro version of Level9. */ + +/* Free resources and exit. */ +void stop(void) +{ + StopGame(); + FreeMemory(); + exit(0); +} + +/* Exit the interpreter with a fatal error. */ +void fatal(const char* txt) +{ + allegro_exit(); +#ifdef _WIN32 + MessageBox(GetForegroundWindow(),txt,"Fatal Error",MB_OK|MB_ICONERROR); +#else + fprintf(stderr,"Fatal Error: %s\n",txt); +#endif + stop(); +} + +/* Given fromname, construct filename as having the same path + and name but with the extension ext. */ +void set_filename(char* filename, const char* fromname, const char* ext) +{ + int i, index = -1; + + strcpy(filename,fromname); + i = strlen(filename)-1; + + /* Search backwards to find either the first period or + backslash, whichever comes first. */ + while ((i >= 0) && (index < 0)) + { + switch (filename[i]) + { + case '.': + index = i; + break; + case '\\': + i = 0; + break; + } + i--; + } + + /* If there is no extension on fromname, just add ext + to filename. */ + if (index < 0) + index = strlen(filename); + strcpy(filename+index,ext); +} + +/* Check if a file exists. */ +int check_for_file(const char* filename) +{ + FILE* f = fopen(filename,"r"); + if (f != NULL) + { + fclose(f); + return TRUE; + } + return FALSE; +} + +/* Set all file names based on the name of the game file. */ +void set_game_file(const char* game_name) +{ + strcpy(story_name,game_name); + set_filename(save_name,game_name,".sav"); + + /* Try the name of the game with ".dat" appended. */ + if (check_for_file(story_name) == FALSE) + set_filename(story_name,game_name,".dat"); + + /* Look for a graphics file. */ + set_filename(gfx_name,game_name,".pic"); + if (check_for_file(gfx_name) == FALSE) + set_filename(gfx_name,game_name,".cga"); + if (check_for_file(gfx_name) == FALSE) + set_filename(gfx_name,game_name,".hrc"); +} + +/* Handle command line arguments. These will be either options + (which start with a dash) or the name of the game to be loaded. + If this function returns a non-zero value, a game file has been + taken from the command line. */ +int process_args(int argc, char** argv) +{ + int got_story = 0; + int got_gfx = 0; + int i = 1; + + while (i < argc) + { + /* Is this an option? */ + if (argv[i][0] == '-') + { + switch (argv[i][1]) + { + case 'd': + if (i < argc+1) + { + switch (atoi(argv[i+1])) + { + case 1: + screen_width = 640; + screen_height = 400; + break; + case 2: + screen_width = 640; + screen_height = 480; + break; + case 3: + screen_width = 800; + screen_height = 600; + break; + } + i++; + } + break; + case 'f': + fast_pics = 1; + break; + case 'h': + high_res_pics = 1; + break; + } + } + else if (got_story == 0) + { + set_game_file(argv[i]); + got_story = 1; + } + else if (got_gfx == 0) + { + strcpy(gfx_name,argv[i]); + got_gfx = 1; + } + i++; + } + return got_story; +} + +/* Set the default palette. */ +void set_default_palette(void) +{ + int i; + + get_palette(palette); + for (i = 0; i < 6; i++) + { + palette[0].r = 0x00; + palette[0].g = 0x00; + palette[0].b = 0x00; + } + palette[1].r = 0x3F; + palette[1].g = 0x3F; + palette[1].b = 0x3F; + set_palette(palette); +} + +/* Set up the font data. */ +void init_font(void) +{ + /* Set up a pointer to the font_data. */ + font_data = (short*)raw_font_data; + + /* Get a pointer to the table of character widths. */ + font_width = (char*)(font_data+FONT_WIDTH_OFFSET); +} + +/* Return the width of a character. */ +int char_length(const unsigned char c) +{ + return font_width[c-32]; +} + +/* Draw a character c at x,y on the display bitmap and + return the width of that character. */ +int draw_char(const unsigned char c, int x, int y, int colour) +{ + char* font_ptr = (char*)(font_data+(FONT_HEIGHT*(c-32))); + unsigned short value; + int i, j; + + for (i = 0; i < FONT_HEIGHT; i++) + { + /* Read the single plane bitmap for each line. */ + value = *((unsigned short*)font_ptr+i); + for (j = 0; j < 16; j++) + { + if (value & 1) + putpixel(display,x+16-j,y+i,colour); + value = value >> 1; + } + } + return char_length(c); +} + +/* Draw a string text starting at x,y. */ +void draw_string(const unsigned char* text, int x, int y) +{ + int i, l = strlen(text); + for (i = 0; i < l; i++) + x += draw_char(text[i],x,y,1); +} + +/* Draw the cursor. */ +void draw_cursor(int x, int y, int w) +{ + xor_mode(TRUE); + rectfill(display,x+1,y,x+w,y+FONT_HEIGHT-1,1); + xor_mode(FALSE); +} + +/* Return the total width of a string text */ +int string_length(const unsigned char* text) +{ + int i, sl = 0, l = strlen(text); + for (i = 0; i < l; i++) + sl += char_length(text[i]); + return sl; +} + +/* Draw the display bitmap onto the visible screen. */ +void screen_update(void) +{ + blit(display,screen,0,0,0,0,SCREEN_W,SCREEN_H); +} + +/* Wait for the given number of milliseconds. If a picture is + being drawn, run the appropriate number of picture opcodes.*/ +void wait(int millis) +{ + int gfx_status = TRUE; + int gfx_count = 0; + int gfx_limit = 2*millis; + + /* Run graphics */ + while (gfx_status && (fast_pics || (gfx_count < gfx_limit))) + { + gfx_status = RunGraphics(); + if (gfx_status) + gfx_count++; + } + + if (gfx_count > 0) + { + /* Copy the graphics into the main bitmap. */ + stretch_blit(gfx,display,0,0,gfx_width,gfx_height,0,0,SCREEN_W,top_y); + + /* Set the new palette. */ + set_palette(palette); + + screen_update(); + } + +#ifdef _WIN32 + Sleep(millis); +#else + delay(millis); +#endif +} + +/* Wait for a key to be pressed and return it */ +int timer_readkey(void) +{ + while (keypressed() == FALSE) + { + if (exit_interpreter) + { + allegro_exit(); + stop(); + } + wait(40); + } + return readkey(); +} + +/* Flush buffered output to the display. */ +void screen_flush(void) +{ + if (output_index > 0) + { + draw_string(output_buffer,text_x,text_y); + text_x += string_length(output_buffer); + + output_index = 0; + + /* Update the visible screen. */ + screen_update(); + } +} + +/* Scroll the display up one line. */ +void screen_scroll(int y) +{ + blit(display,display,0,top_y+FONT_HEIGHT,0,top_y, + SCREEN_W,SCREEN_H-top_y-FONT_HEIGHT); + rectfill(display,0,y,SCREEN_W-1,SCREEN_H-1,0); +} + +/* Move the current text position down to the start of + the next line. */ +void screen_newline(void) +{ + text_x = 0; + page_counter++; + + /* If the position is at the bottom of the display, + scroll the display. */ + if (text_y+(FONT_HEIGHT*2) > SCREEN_H) + { + screen_scroll(text_y); + + /* Is there a full page of text on the screen since + the last pause? If so, put up a [MORE] prompt. */ + if (page_counter >= page_limit) + { + const char* more = "[MORE]"; + + draw_string(more,text_x,text_y); + draw_cursor(text_x+string_length(more),text_y,DEFAULT_CURSOR_WIDTH); + screen_update(); + timer_readkey(); + rectfill(display,0,text_y,SCREEN_W-1,SCREEN_H-1,0); + + /* Set the counter to 1 so that the last line of this page + is the first line of the next. */ + page_counter = 1; + } + } + else + text_y += FONT_HEIGHT; + screen_update(); +} + +/* Check if the word just output runs off the right of the display. + If so, break at the start of the word and flush the line up to + the break, then return TRUE. */ +int check_break(void) +{ + if (text_x+string_length(output_buffer) > SCREEN_W) + { + /* Find the previous space */ + int i = output_index-1; + while (i > 0) + { + if (output_buffer[i] == ' ') + { + output_buffer[i] = 0; + draw_string(output_buffer,text_x,text_y); + screen_newline(); + + memmove(output_buffer,output_buffer+i+1,output_index-i-1); + output_index -= i+1; + output_buffer[output_index] = 0; + break; + } + i--; + } + return TRUE; + } + return FALSE; +} + +/* Store a character in the text output buffer. */ +void screen_character(char c) +{ + switch (c) + { + case '\n': /* Return or enter */ + check_break(); + screen_flush(); + screen_newline(); + break; + case '\b': /* Backspace */ + if (output_index > 0) + output_buffer[--output_index] = 0; + break; + case ' ': + /* Check if the line should be broken at the previous word. */ + if (check_break() == FALSE) + { + if (output_index >= OUTPUT_LIMIT-1) + screen_flush(); + } + output_buffer[output_index++] = c; + output_buffer[output_index] = 0; + break; + default: + if (isprint(c)) + { + if (output_index >= OUTPUT_LIMIT-1) + screen_flush(); + output_buffer[output_index++] = c; + output_buffer[output_index] = 0; + } + break; + } +} + +/* Write a string to the output buffer. */ +void screen_string(const char* text) +{ + int i, l = strlen(text); + for (i = 0; i < l; i++) + screen_character(text[i]); +} + +/* Set up the display after the game has been loaded. */ +void prepare_screen(void) +{ + int i; + + /* Set up the screen. */ + set_color_depth(8); + if (set_gfx_mode(gfx_mode,screen_width,screen_height,0,0) < 0) + fatal(allegro_error); + clear(screen); + set_default_palette(); + + /* Allocate a display bitmap. */ + display = create_bitmap(SCREEN_W,SCREEN_H); + clear(display); + + /* Set picture resolution, if needed. */ + if (high_res_pics) + { + gfx_width = SCREEN_W; + gfx_height = ((SCREEN_H/2) / FONT_HEIGHT) * FONT_HEIGHT; + } + + /* Initialize the input and input history buffers. */ + input_buffer[0] = 0; + for (i = 0; i < HISTORY_SIZE; i++) + input_history[i][0] = 0; + + page_limit = ((SCREEN_H-top_y)/FONT_HEIGHT)-1; + text_y = page_limit*FONT_HEIGHT; +} + +/* Prompt the user for a yes or no response. */ +int get_yes_no(const char* prompt) +{ + int key; + char c; + + /* Display the prompt. */ + screen_string(prompt); + screen_string("? (y/n) "); + screen_flush(); + draw_cursor(text_x,text_y,DEFAULT_CURSOR_WIDTH); + screen_update(); + + /* Get a response. */ + key = timer_readkey(); + c = key & 0xFF; + + draw_cursor(text_x,text_y,DEFAULT_CURSOR_WIDTH); + screen_character(c); + screen_character('\n'); + + return ((c == 'y') || (c == 'Y')); +} + +/* Clear the display to the right and below the input prompt. */ +void clear_input_display(void) +{ + rectfill(display,text_x,text_y,SCREEN_W-1,text_y+FONT_HEIGHT-1,0); + if (SCREEN_H > text_y+FONT_HEIGHT) + rectfill(display,0,text_y+FONT_HEIGHT,SCREEN_W-1,SCREEN_H-1,0); +} + +/* Add the input line to the output buffer (called after input + is complete). */ +void write_input(char* input) +{ + int i, l, x, x_step; + + l = strlen(input); + x = text_x; + for (i = 0; i < l; i++) + { + /* Check if the character will fit on this line. */ + x_step = char_length(input[i]); + if (x+x_step+DEFAULT_CURSOR_WIDTH >= SCREEN_W) + { + screen_character('\n'); + x = 0; + } + screen_character(input[i]); + x += x_step; + } +} + +/* Draw the current input and the cursor. */ +void draw_input(char* input, int cursor_index) +{ + int i, l, x, y, x_step, cursor_w; + + /* Clear the previous input. */ + clear_input_display(); + + l = strlen(input); + x = text_x; + y = text_y; + for (i = 0; i <= l; i++) + { + if (i < l) + { + /* Check if the character will fit on this line. */ + x_step = char_length(input[i]); + if (x+x_step+DEFAULT_CURSOR_WIDTH >= SCREEN_W) + { + x = 0; + + /* Move to the next line. */ + if (y+(FONT_HEIGHT*2) > SCREEN_H) + { + screen_scroll(y); + text_y -= FONT_HEIGHT; + } + else + y += FONT_HEIGHT; + } + draw_char(input[i],x,y,1); + } + else + x_step = 0; + + /* If this is the right place, draw the cursor. */ + if (i == cursor_index) + { + /* Get the cursor width. */ + if (i < l) + cursor_w = x_step; + else + cursor_w = DEFAULT_CURSOR_WIDTH; + + draw_cursor(x,y,cursor_w); + } + x += x_step; + } + + screen_update(); +} + +/* Formatting routine used when printing out help on in game commands.*/ +void help_string(const char* command, const char* info) +{ + screen_string(" #"); + screen_string(command); + screen_flush(); + text_x = 100; + screen_string(info); + screen_character('\n'); +} + +/* Handle the user pressing the help hot key */ +void hotkey_help(void) +{ + screen_string("\nHot key -- Help\n"); + screen_string("\n" + "Alt-C show copyright and license info\n" + "Alt-H help\n" + "Alt-X exit game\n"); + + screen_string("\nDuring input, the following commands can be entered:\n"); + help_string("save", + "Saves position file directly, bypassing any prompts."); + help_string("restore", + "Restores position file directly, bypassing any protection code."); + help_string("quit", + "Exits the interpreter."); + help_string("cheat", + "Tries to bypass restore protection on some games, can be slow."); + help_string("dictionary", + "Lists game dictionary, press any key to stop."); + help_string("picture ", + "Shows picture number ."); +} + +/* Handle the user pressing the information hot key */ +void hotkey_info(void) +{ + screen_string("\nHot key -- Copyright and License Information\n"); + + screen_string("\nLevel 9 Interpreter v3.0\n"); + screen_string("Copyright (c) 1996 Glen Summers\n"); + screen_string("Copyright (c) 2002 Glen Summers and David Kinder\n\n"); + + screen_string("Level9 is released under the terms of the GNU General Public License.\n"); + screen_string("See the file COPYING that is included with this program for details.\n"); +} + +/* Handle the user pressing the exit hot key */ +void hotkey_exit(void) +{ + screen_string("\nHot key -- Exit game\n"); + if (get_yes_no("Do you wish to quit")) + { + allegro_exit(); + stop(); + } +} + +/* Test for a user entered hot key. */ +void input_hotkeys(char c, char* input, int cursor_index) +{ + /* Is this a recognised hot key? */ + switch (c) + { + case KEY_C: + case KEY_H: + case KEY_X: + clear_input_display(); + write_input(input); + break; + default: + return; + } + + switch (c) + { + case KEY_C: /* Alt-C */ + hotkey_info(); + break; + case KEY_H: /* Alt-H */ + hotkey_help(); + break; + case KEY_X: /* Alt-X */ + hotkey_exit(); + } + + /* Redisplay the input line. */ + screen_character('\n'); + screen_flush(); + draw_input(input,cursor_index); + page_counter = 0; +} + +/* Get an input line from the history buffer. */ +char* get_history(int position) +{ + int index = history_index - position; + if (index < 0) + index += HISTORY_SIZE; + return input_history[index]; +} + +/* Get a line of input from the user. */ +void input_line(char* input, unsigned int input_size, int game_input) +{ + int status, key = 0; + unsigned int cursor_index = 0; + int history_position = -1; + char c, sc; + + screen_flush(); + page_counter = 0; + cursor_index = strlen(input); + draw_input(input,cursor_index); + + status = INPUT_PENDING; + while (status == INPUT_PENDING) + { + key = timer_readkey(); + c = key & 0xFF; + sc = (key & 0xFF00) >> 8; + + switch (c) + { + case '\n': /* Return or enter */ + case '\r': + status = INPUT_LINE; + + /* Add the input line to the output text. */ + clear_input_display(); + write_input(input); + + /* Store the line in the input history. */ + if (strcmp(input,"") != 0) + { + if (strcmp(input_history[history_index],input) != 0) + { + history_index++; + if (history_index >= HISTORY_SIZE) + history_index = 0; + strcpy(input_history[history_index],input); + } + } + break; + case '\b': /* Backspace */ + if (cursor_index > 0) + { + memmove(input+cursor_index-1,input+cursor_index, + strlen(input)-cursor_index+1); + cursor_index--; + draw_input(input,cursor_index); + } + break; + case 0: + /* If the ASCII code is 0, look at the scan code. */ + switch (sc) + { + case KEY_LEFT: + if (cursor_index > 0) + { + cursor_index--; + draw_input(input,cursor_index); + } + break; + case KEY_RIGHT: + if (cursor_index < strlen(input)) + { + cursor_index++; + draw_input(input,cursor_index); + } + break; + case KEY_HOME: + if (cursor_index > 0) + { + cursor_index = 0; + draw_input(input,cursor_index); + } + break; + case KEY_END: + if (cursor_index < strlen(input)) + { + cursor_index = strlen(input); + draw_input(input,cursor_index); + } + break; + case KEY_DEL: + if (cursor_index < strlen(input)) + { + memmove(input+cursor_index,input+cursor_index+1, + strlen(input)-cursor_index); + draw_input(input,cursor_index); + } + break; + case KEY_UP: + if (game_input) + { + /* Are we at the end of the history buffer? */ + if (history_position < HISTORY_SIZE-1) + { + /* Is there an entry in the history buffer here? */ + if (strcmp(get_history(history_position+1),"") != 0) + { + history_position++; + strcpy(input,get_history(history_position)); + + /* Redraw the input line. */ + cursor_index = strlen(input); + draw_input(input,cursor_index); + } + } + } + break; + case KEY_DOWN: + if (game_input) + { + if (history_position >= 0) + { + if (history_position > 0) + { + if (strcmp(get_history(history_position-1),"") != 0) + { + history_position--; + strcpy(input,get_history(history_position)); + } + } + else + { + history_position--; + strcpy(input,""); + } + cursor_index = strlen(input); + draw_input(input,cursor_index); + } + } + break; + case KEY_F12: + screen_update(); + break; + default: + if (game_input) + input_hotkeys(sc,input,cursor_index); + break; + } + break; + case 1: + /* If the ASCII code is 1, look at the scan code. */ + switch (sc) + { + case KEY_DEL: /* Shift-Delete */ + input[0] = 0; + cursor_index = 0; + draw_input(input,cursor_index); + break; + } + break; + default: + if (isprint(c) && ((c&0x80) == 0)) + { + if (strlen(input) < input_size-1) + { + memmove(input+cursor_index+1,input+cursor_index, + strlen(input)-cursor_index+1); + input[cursor_index++] = c; + draw_input(input,cursor_index); + } + } + break; + } + } + page_counter = -1; +} + +/* Hook called when the close button is pressed. */ +void close_button_hook(void) +{ + /* Set a flag to indicate the interpreter should exit. */ + exit_interpreter = TRUE; +} + +/* Hook called to handle application switching. */ +void display_switch_hook(void) +{ + screen_update(); +} + +/* Run the interpreter. */ +void run_game(void) +{ + /* Set up the font. */ + init_font(); + + /* Initialize the Allegro graphics library. */ + install_allegro(SYSTEM_AUTODETECT,&errno,atexit); + + /* Add a hook to handle the close button. */ + set_window_close_hook(close_button_hook); + + /* Add a hook to handle application switching. */ + set_display_switch_callback(SWITCH_IN,display_switch_hook); + + /* Set up the keyboard handler. */ + install_keyboard(); + + /* Set the window title. */ + set_window_title("Level 9 Interpreter"); + +#ifdef _WIN32 + /* Set the window icon. */ + SendMessage(win_get_window(),WM_SETICON,ICON_BIG, + (LPARAM)LoadIcon(GetModuleHandle(NULL),MAKEINTRESOURCE(IDI_ICON))); +#endif + + /* Set up the screen display. */ + prepare_screen(); + + /* Try to load the game into the interpreter. */ + if (LoadGame(story_name,gfx_name) == 0) + { + fatal("Failed to start game"); + return; + } + + /* Run interpreter opcodes until the game exits. */ + while (RunGame() != 0); + + /* Exit from the game. */ + stop(); +} + +/* Filters for the file dialogs */ +const char* game_filter = + "Level 9 Game Files (*.dat)\0*.dat\0" + "Spectrum Snapshots (*.sna)\0*.sna\0" + "All Files (*.*)\0*.*\0"; +const char* save_filter = + "Level 9 Saved Games (*.sav)\0*.sav\0" + "All Files (*.*)\0*.*\0"; + +#ifdef _WIN32 + +DWORD display_type = 0; +DWORD display_width = 0; +DWORD display_height = 0; + +#define NUMBER_MODES 4 + +struct DisplayMode +{ + const char* name; + int width; + int height; +}; + +struct DisplayMode display_modes[NUMBER_MODES] = +{ + { "Window",-1,-1 }, + { "Full Screen (640x400)",640,400 }, + { "Full Screen (640x480)",640,480 }, + { "Full Screen (800x600)",800,600 } +}; + +/* Set the display control. */ +void set_display_control(HWND wnd) +{ + HWND control = GetDlgItem(wnd,IDC_DISPLAY); + if (control) + { + int i; + + /* Clear any list items. */ + SendMessage(control,CB_RESETCONTENT,0,0); + + /* Add all display modes. */ + for (i = 0; i < NUMBER_MODES; i++) + SendMessage(control,CB_ADDSTRING,0,(LPARAM)display_modes[i].name); + + /* Select the current display mode. */ + SendMessage(control,CB_SETCURSEL,display_type,0); + } +} + +/* Set the width and height controls. */ +void set_size_controls(HWND wnd, int update) +{ + HWND control; + char number[256]; + + /* Get the display mode control. */ + control = GetDlgItem(wnd,IDC_DISPLAY); + if (control) + { + /* Get the display mode. */ + int display = SendMessage(control,CB_GETCURSEL,0,0); + if (display >= 0) + { + control = GetDlgItem(wnd,IDC_WIDTH); + if (control) + { + /* If the mode's width is -1, use the user entered value. */ + int width = display_modes[display].width; + int previous = EnableWindow(control,(width < 0)); + + /* Always update if going from or to a fixed width mode. */ + if (update || (width >= 0) || previous) + { + _snprintf(number,256,"%d",(width < 0) ? display_width : width); + SendMessage(control,WM_SETTEXT,0,(LPARAM)number); + } + } + + control = GetDlgItem(wnd,IDC_HEIGHT); + if (control) + { + /* If the mode's height is -1, use the user entered value. */ + int height = display_modes[display].height; + int previous = EnableWindow(control,(height < 0)); + + /* Always update if going from or to a fixed width mode. */ + if (update || (height >= 0) || previous) + { + _snprintf(number,256,"%d",(height < 0) ? display_height : height); + SendMessage(control,WM_SETTEXT,0,(LPARAM)number); + } + } + } + } +} + +/* Read a value from a control. */ +void read_control(HWND wnd, UINT id) +{ + HWND control = GetDlgItem(wnd,id); + if (control) + { + switch (id) + { + case IDC_DISPLAY: + { + /* Get the currently selected display mode. */ + int display = SendMessage(control,CB_GETCURSEL,0,0); + if (display >= 0) + display_type = display; + } + break; + case IDC_WIDTH: + case IDC_HEIGHT: + { + /* Get the text in the control. */ + char number[256]; + SendMessage(control,WM_GETTEXT,256,(LPARAM)number); + + /* Convert the text in the control to a number. */ + switch (id) + { + case IDC_WIDTH: + sscanf(number,"%d",&display_width); + break; + case IDC_HEIGHT: + sscanf(number,"%d",&display_height); + break; + } + } + break; + } + } +} + +/* Centre a window in the screen. */ +void centre_window(HWND wnd) +{ + RECT wnd_size, screen_size; + + if (GetWindowRect(wnd,&wnd_size)) + { + /* Get the size of the workspace. */ + if (SystemParametersInfo(SPI_GETWORKAREA,0,&screen_size,0)) + { + int x = (screen_size.bottom-wnd_size.bottom+wnd_size.top)/2; + int y = (screen_size.right-wnd_size.right+wnd_size.left)/2; + + /* Resize the window. */ + SetWindowPos(wnd,0,x,y,0,0, + SWP_NOOWNERZORDER|SWP_NOSIZE|SWP_NOZORDER); + } + } +} + +/* Hook function to for file dialogs. */ +UINT APIENTRY file_dlg_hook(HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uiMsg) + { + case WM_NOTIFY: + { + HWND hwnd = GetParent(hdlg); + if ((hwnd != 0) && (lParam != 0)) + { + if (((LPNMHDR)lParam)->code == CDN_INITDONE) + { + /* Centre the dialog in the screen. */ + centre_window(hwnd); + + /* Set up extra display controls. */ + set_display_control(hdlg); + set_size_controls(hdlg,TRUE); + } + } + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_DISPLAY: + /* Update the width and height controls. */ + set_size_controls(hdlg,FALSE); + read_control(hdlg,LOWORD(wParam)); + break; + case IDC_WIDTH: + case IDC_HEIGHT: + /* Has the user tabbed away from the control? */ + if (HIWORD(wParam) == EN_KILLFOCUS) + read_control(hdlg,LOWORD(wParam)); + break; + } + break; + } + return 0; +} + +/* Entry point for the Windows version */ +int main(int argc, char** argv) +{ + const char* key_name = "Software\\Level 9\\Graphical Interpreter"; + HKEY registry_key = 0; + DWORD key_action = 0; + + char initial_dir[MAX_FILE_NAME]; + char game_filename[MAX_FILE_NAME]; + DWORD filter = 0; + int got_game = 0; + + /* Default width and height. */ + display_width = (GetSystemMetrics(SM_CXFULLSCREEN)*3)/4; + display_height = (GetSystemMetrics(SM_CYFULLSCREEN)*3)/4; + + /* Open the registry key. */ + RegCreateKeyEx(HKEY_CURRENT_USER,key_name,0,"",REG_OPTION_NON_VOLATILE, + KEY_ALL_ACCESS,NULL,®istry_key,&key_action); + + /* Set up initial directory and display options. */ + game_filename[0] = '\0'; + initial_dir[0] = '\0'; + if (registry_key != 0) + { + DWORD buffer_size = MAX_FILE_NAME; + DWORD word_size; + + RegQueryValueEx(registry_key,"Directory",NULL,NULL, + initial_dir,&buffer_size); + word_size = sizeof(DWORD); + RegQueryValueEx(registry_key,"Filter",NULL,NULL, + (LPBYTE)&filter,&word_size); + + word_size = sizeof(DWORD); + RegQueryValueEx(registry_key,"Display",NULL,NULL, + (LPBYTE)&display_type,&word_size); + word_size = sizeof(DWORD); + RegQueryValueEx(registry_key,"Width",NULL,NULL, + (LPBYTE)&display_width,&word_size); + word_size = sizeof(DWORD); + RegQueryValueEx(registry_key,"Height",NULL,NULL, + (LPBYTE)&display_height,&word_size); + } + + /* Check for command line arguments. */ + if (process_args(argc,argv)) + got_game = 1; + else + { + OPENFILENAME open_file; + + /* Set up an open file dialog. */ + ZeroMemory(&open_file,sizeof(OPENFILENAME)); + open_file.lStructSize = sizeof(OPENFILENAME); + open_file.hInstance = GetModuleHandle(NULL); + open_file.lpstrFilter = game_filter; + open_file.nFilterIndex = filter; + open_file.lpstrFile = game_filename; + open_file.nMaxFile = MAX_FILE_NAME; + open_file.lpstrInitialDir = initial_dir; + open_file.lpstrTitle = "Open a Level 9 Game"; + open_file.Flags = OFN_FILEMUSTEXIST|OFN_HIDEREADONLY| + OFN_ENABLEHOOK|OFN_ENABLETEMPLATE|OFN_EXPLORER; + open_file.lpfnHook = file_dlg_hook; + open_file.lpTemplateName = MAKEINTRESOURCE(IDD_OPENEXTRA); + + /* Get a file name from the user. */ + if (GetOpenFileName(&open_file)) + { + set_game_file(open_file.lpstrFile); + filter = open_file.nFilterIndex; + got_game = 1; + + /* Store the user's settings for next time. */ + if (registry_key != 0) + { + char* p; + + /* Remove the file name. */ + p = strrchr(game_filename,'\\'); + if (p != NULL) + *p = '\0'; + + RegSetValueEx(registry_key,"Directory",0,REG_SZ, + game_filename,strlen(game_filename)+1); + RegSetValueEx(registry_key,"Filter",0,REG_DWORD, + (LPBYTE)&filter,sizeof(DWORD)); + RegSetValueEx(registry_key,"Display",0,REG_DWORD, + (LPBYTE)&display_type,sizeof(DWORD)); + RegSetValueEx(registry_key,"Width",0,REG_DWORD, + (LPBYTE)&display_width,sizeof(DWORD)); + RegSetValueEx(registry_key,"Height",0,REG_DWORD, + (LPBYTE)&display_height,sizeof(DWORD)); + } + } + } + + if (got_game) + { + /* Get the display mode, width and height. */ + if (display_type == 0) + { + gfx_mode = GFX_AUTODETECT_WINDOWED; + screen_width = display_width; + screen_height = display_height; + } + else + { + gfx_mode = GFX_AUTODETECT_FULLSCREEN; + screen_width = display_modes[display_type].width; + screen_height = display_modes[display_type].height; + } + + /* Run the interpreter. */ + run_game(); + } + return 0; +} + +END_OF_MAIN() + +#else + +/* Entry point for the interpreter. */ +int main(int argc, char** argv) +{ + /* Set up this port of Level9. */ + if (process_args(argc,argv) == FALSE) + { + /* Print help information and exit. */ + puts( + "Level 9 Interpreter v3.0\n" + "Copyright (c) 1996 Glen Summers\n" + "Copyright (c) 2002 Glen Summers and David Kinder\n" + "\n" + "Level9 is released under the terms of the GNU General Public License.\n" + "See the file COPYING that is included with this program for details.\n" + "\n" + "Syntax: level9 [options] story-file [graphics-file]\n\n" + " -d # display mode: 1 is 640x400, 2 is 640x480, 3 is 800x600\n" + " -f draw pictures as fast as possible\n" + " -h use high resolution for pictures, may cause fill errors\n" + " story-file is a Level 9 data file, a 48K Spectrum SNA snapshot\n" + " or any linear memory dump containing a Level 9 game."); + exit(0); + } + + /* Run the interpreter. */ + run_game(); + return 0; +} + +#endif + +/* Get a file name from the user. The buffer must be of + length MAX_FILE_NAME. */ +int get_file_name(const char* prompt, const char* filter, char* buffer, int save) +{ +#ifdef _WIN32 + if (gfx_mode == GFX_AUTODETECT_WINDOWED) + { + OPENFILENAME open_file; + BOOL got_file = FALSE; + char filename[MAX_FILE_NAME]; + + strcpy(filename,buffer); + + /* Set up a file dialog. */ + ZeroMemory(&open_file,sizeof(OPENFILENAME)); + open_file.lStructSize = sizeof(OPENFILENAME); + open_file.lpstrFilter = filter; + open_file.lpstrFile = filename; + open_file.nMaxFile = MAX_FILE_NAME; + open_file.lpstrTitle = prompt; + open_file.Flags = OFN_HIDEREADONLY|OFN_ENABLEHOOK|OFN_EXPLORER; + open_file.lpfnHook = file_dlg_hook; + + /* Get a file name from the user. */ + if (save) + got_file = GetSaveFileName(&open_file); + else + got_file = GetOpenFileName(&open_file); + + if (got_file) + { + strcpy(buffer,filename); + return TRUE; + } + return FALSE; + } +#endif + + screen_flush(); + if (text_x > 0) + screen_character('\n'); + screen_string(prompt); + screen_string(": "); + + input_line(buffer,MAX_FILE_NAME,FALSE); + screen_character('\n'); + return TRUE; +} + +/* Level9 interface routines. */ + +/* Load a file into the interpreter's memory. */ +L9BOOL os_load_file(L9BYTE* Ptr, int* Bytes, int Max) +{ + FILE *fp; + + if (get_file_name("Game to load",save_filter,save_name,FALSE) == FALSE) + return FALSE; + + fp = fopen(save_name,"rb"); + if (fp == NULL) + return FALSE; + + *Bytes = fread(Ptr,1,Max,fp); + fclose(fp); + return TRUE; +} + +/* Save a file from the interpreter's memory. */ +L9BOOL os_save_file(L9BYTE* Ptr, int Bytes) +{ + FILE *fp; + + if (get_file_name("Game to save",save_filter,save_name,TRUE) == FALSE) + return FALSE; + + fp = fopen(save_name,"wb"); + if (fp == NULL) + return FALSE; + + fwrite(Ptr,1,Bytes,fp); + fclose(fp); + return TRUE; +} + +/* Print a character. */ +void os_printchar(char c) +{ + if (c == '\r') + c = '\n'; + screen_character(c); +} + +/* Flush any buffered output. */ +void os_flush(void) +{ + screen_flush(); +} + +/* Get a line of input from the user. */ +L9BOOL os_input(char* ibuff, int size) +{ + ibuff[0] = '\0'; + while (ibuff[0] == '\0') + input_line(ibuff,size,TRUE); + return TRUE; +} + +/* Get a character from the user. waiting for at least the + argument number of millseconds. */ +char os_readchar(int millis) +{ + if (exit_interpreter) + { + allegro_exit(); + stop(); + } + + /* If no key has been pressed, wait. */ + if (millis > 0) + { + if (keypressed() == FALSE) + wait(millis); + } + + if (keypressed()) + { + int key = readkey(); + + /* Check for Alt-X or F12 ... */ + if (key == KEY_X << 8) + hotkey_exit(); + else if (key == KEY_F12 << 8) + screen_update(); + + /* ... otherwise, return the key pressed. */ + return key; + } + return 0; +} + +/* Check if the listing should be stopped. */ +L9BOOL os_stoplist(void) +{ + return (os_readchar(0) != 0); +} + +/* Get the next game file name from the user. */ +L9BOOL os_get_game_file(char* NewName, int Size) +{ + if (get_file_name("Load next game",game_filter,story_name,FALSE) == FALSE) + return FALSE; + + if ((int)strlen(story_name) < Size) + { + strcpy(NewName,story_name); + return TRUE; + } + return FALSE; +} + +/* Work out the file name of a game part. */ +void os_set_filenumber(char* NewName, int Size, int n) +{ + char *p; + int i; + + p = strrchr(NewName,'\\'); + if (p == NULL) + p = NewName; + for (i = strlen(p)-1; i >= 0; i--) + { + if (isdigit(p[i])) + { + p[i] = '0'+n; + return; + } + } +} + +/* Set the graphics mode. */ +void os_graphics(int mode) +{ + if (mode) + { + /* Get the height of the picture in terms of text + lines, and use it to set the size of the text area. */ + top_y = ((SCREEN_H/2) / FONT_HEIGHT) * FONT_HEIGHT; + } + else + top_y = 0; + page_limit = ((SCREEN_H-top_y)/FONT_HEIGHT)-1; +} + +/* Clear the graphics window. */ +void os_cleargraphics(void) +{ + if (top_y > 0) + { + /* If needed, allocate the graphics bitmap. */ + if (gfx == NULL) + { + /* Get the picture size from the interpreter. */ + if (high_res_pics == 0) + GetPictureSize(&gfx_width,&gfx_height); + + gfx = create_bitmap(gfx_width,gfx_height); + } + + /* Clear the graphics bitmap */ + rectfill(gfx,0,0,gfx_width-1,gfx_height-1,2); + } +} + +/* Set a colour from the 8 colour palette. */ +void os_setcolour(int colour, int index) +{ + palette[colour+2] = colours[index]; +} + +/* Callback to draw lines. */ +void line_callback(BITMAP* bmp, int x, int y, int d) +{ + int colour1 = ((d >> 2) & 3) + 2; + int colour2 = (d & 3) + 2; + + int pixel = getpixel(bmp,x,y); + if (pixel == colour2) + { + /* getpixel() returns -1 for pixels outside the bitmap, + so the much faster _putpixel() can be used. */ + _putpixel(bmp,x,y,colour1); + } +} + +/* Draw a line on the graphics window. */ +void os_drawline(int x1, int y1, int x2, int y2, int colour1, int colour2) +{ + if (top_y > 0) + { + if (high_res_pics) + { + int pic_width; + GetPictureSize(&pic_width,NULL); + + x1 *= ((double)gfx_width) / 320.0; + y1 *= ((double)gfx_height) / 96.0; + x2 *= ((double)gfx_width) / 320.0; + y2 *= ((double)gfx_height) / 96.0; + + if (pic_width < 320) + { + x1 *= 2; + x2 *= 2; + } + } + + /* Draw the line. */ + do_line(gfx,(int)x1,(int)y1,(int)x2,(int)y2, + (colour1<<2)+colour2,line_callback); + } +} + +/* Fill the graphics window from the given point. */ +void os_fill(int x, int y, int colour1, int colour2) +{ + if (top_y > 0) + { + int pixel; + + if (high_res_pics) + { + int pic_width; + GetPictureSize(&pic_width,NULL); + + x *= ((double)gfx_width) / 320.0; + y *= ((double)gfx_height) / 96.0; + + if (pic_width < 320) + x *= 2; + } + + /* Flood fill the graphics window. */ + pixel = getpixel(gfx,(int)x,(int)y); + if (pixel == colour2+2) + floodfill(gfx,(int)x,(int)y,colour1+2); + } +} diff --git a/DOS32/allegro.cfg b/DOS32/allegro.cfg new file mode 100755 index 0000000..c94b5fa --- /dev/null +++ b/DOS32/allegro.cfg @@ -0,0 +1,536 @@ +# +# Configuration file for the Allegro library. +# +# If you leave a parameter blank, Allegro will autodetect (ie. guess :-) +# +# This file should be kept either in the same directory as your program, +# or in a location pointed to by the "ALLEGRO" environment variable. +# +# On Unix, this file may also be stored as ~/.allegrorc or /etc/allegrorc. + + + + +[system] + +# Unix system drivers: +# +# XWIN - XWindows +# LNXC - Linux console + +system = + + + +# Keyboard mapping file (this can be a .cfg file on disk, or an object from +# keyboard.dat). Currently available layouts are: +# +# BE - Belgium +# BR - Brazil +# CH - Switzerland +# CZ - Czech Republic +# DE - Germany +# DK - Denmark +# DVORAK - Dvorak +# ES - Spain +# FI - Finland +# FR - France +# IT - Italy +# NO - Norway +# PL - Poland +# PT - Portugal +# RU - Russia +# SE - Sweden +# SK - Slovakia +# UK - United Kingdom +# US - United States + +keyboard = UK + + + +# Language translation file (this can be a .cfg file on disk, or an +# object from language.dat). Currently available translations are: +# +# CZ - Czech +# DE - German +# DK - Danish +# EN - English +# ES - Spanish +# FI - Finnish +# FR - French +# IT - Italian +# NO - Norwegian +# PL - Polish +# PT - Portuguese +# RU - Russian +# SE - Swedish +# SK - Slovak + +language = + + + +# how long to take for menus to auto-open (time in msecs or -1 to disable) +menu_opening_delay = + + + + +[graphics] + +# DOS graphics drivers: +# +# VGA - Standard VGA +# MODX - Mode-X +# VBE1 - VESA 1.x +# VB2B - VBE 2.0 (banked) +# VB2L - VBE 2.0 (linear) +# VBE3 - VBE 3.0 +# VBAF - VBE/AF +# XTND - Xtended mode +# +# Windows graphics drivers: +# +# DXAC - DirectX with hardware acceleration +# DXSO - DirectX without hardware acceleration +# DXSA - DirectX in safe-mode, with all the fancy features disabled +# DXWN - DirectX in windowed mode +# DXOV - DirectX overlay (fast windowed mode, only works on some cards) +# GDIB - GDI mode (slow windowed mode, works on all cards) +# +# Linux console graphics drivers: +# +# FB - fbcon device +# VBAF - VBE/AF +# SVGA - SVGAlib +# VGA - Standard VGA +# MODX - Mode-X +# +# X graphics drivers: +# +# XWIN - standard X Windows +# XWFS - Fullscreen X Windows +# XDGA - XFree86 Direct Graphics Access 1.0 (DGA) +# XDFS - Fullscreen DGA 1.0 mode +# DGA2 - DGA 2.0 mode +# DGAS - DGA 2.0 software only mode +# +# BeOS graphics drivers: +# +# BFS - Fullscreen (BWindowScreen, hardware acceleration) +# BFSS - Fullscreen safe (BWindowScreen, software only) +# BWN - Windowed (BDirectWindow) +# +# QNX graphics drivers: +# +# QPHD - Photon direct (Fullscreen) +# QPH - Photon +# +# You can also specify different drivers for a particular mode or color +# depth, eg. "gfx_card_640x480x16 = VBE3", "gfx_card_24bpp = VBE1", or +# you can provide a number of alternative drivers, for example +# "gfx_card1 = VGA", "gfx_card2 = MODX", etc. + +gfx_card = + + + +# driver to use when autodetecting windowed modes +gfx_cardw = + + + +# DOS and Linux: where to look for the VBE/AF driver +vbeaf_driver = + + + +# Linux only: fbcon device file (see below for fbcon timing data) +framebuffer = + + + + +# Linux/fbcon mode timings. Duplicate then fill in `X', `Y' and +# the timings themselves. You can copy them from fb.modes directly -- the +# first two figures on the geometry line are X and Y here, and the figures +# on the timings line are the first fields here (up to `vsync_len') + +[fb_mode_XxY] +pixclock = +left_margin = +right_margin = +upper_margin = +lower_margin = +hsync_len = +vsync_len = +vmode = +sync = + + + + +[mouse] + +# DOS mouse drivers: +# +# MICK - mickey mode driver (normally the best) +# I33 - int 0x33 callback driver +# POLL - timer polling +# WNT - Windows NT 3.5x/4.0 specific driver +# W2K - Windows 2000 specific driver +# +# Linux console mouse drivers: +# +# LPS2 - PS2 mouse +# LIPS - PS2 mouse with Intellimouse extension +# MS - Microsoft serial mouse +# IMS - Microsoft serial mouse with Intellimouse extension +# GPMD - GPM repeater data (Mouse Systems protocol) + +mouse = + + + +# number of buttons (specifying this overrides autodetection) +num_buttons = + + + +# whether to emulate the third mouse button (yes or no) +emulate_three = + + + +# Linux only: name of the mouse device file (eg. /dev/mouse) +mouse_device = + + + +# Windows only: mouse acceleration factor (1 by default, 0 to disable) +mouse_accel_factor = + + + + +[sound] + +# DOS digital sound drivers: +# +# 0 - none +# SB - Sound Blaster (autodetect breed) +# SB10 - Sound Blaster 1.0 +# SB15 - Sound Blaster 1.5 +# SB20 - Sound Blaster 2.0 +# SBP - Sound Blaster Pro +# SB16 - Sound Blaster 16 +# ESS - ESS AudioDrive +# ESC - Ensoniq Soundscape +# WSS - Windows Sound System +# +# Windows digital sound drivers: +# +# 0 - none +# DXA - first DirectSound device +# DXB - second DirectSound device +# ... - etc +# AXA - first DirectSound device using Allegro mixer +# AXB - second DirectSound device using Allegro mixer +# ... - etc +# WOA - high quality WaveOut device +# WOB - low quality WaveOut device +# +# Unix digital sound drivers: +# +# 0 - none +# OSSD - Open Sound System +# ESDD - Enlightened Sound Daemon +# ALSA - ALSA Sound System +# +# BeOS digital sound drivers: +# +# 0 - none +# BDIG - BeOS digital sound system +# + +digi_card = + + + +# DOS music drivers: +# +# 0 - none +# OPL - Adlib (autodetect OPL version) +# OPL2 - OPL2 FM synth +# OPLX - Dual OPL2 (SB Pro-1) +# OPL3 - OPL3 FM synth +# SB - raw SB MIDI interface +# MPU - MPU-401 MIDI interface +# DIGI - DIGMID software wavetable +# AWE - AWE32 +# +# Windows music drivers: +# +# 0 - none +# W32M - win32 MIDI mapper +# W32A - first win32 MIDI device +# W32B - second win32 MIDI device +# ... - etc +# DIGI - DIGMID software wavetable +# +# Linux music drivers: +# +# 0 - none +# OSSM - Open Sound System +# DIGI - DIGMID software wavetable +# AMID - ALSA Sound System +# +# BeOS music drivers: +# +# 0 - none +# BMID - BeOS MIDI synthesizer +# DIGI - DIGMID software wavetable +# + +midi_card = + + + +# sound input drivers (same format as above, can usually be left blank) +digi_input_card = +midi_input_card = + + + +# how many voices to reserve for each driver +digi_voices = +midi_voices = + + + +# how loud? (range 0-255) +digi_volume = +midi_volume = + + + +# sample mixing quality (0=fastest, 1=full 16 bit precision, 2=interpolation) +quality = + + + +# toggling this between 0 and 1 reverses the left/right panning of samples +flip_pan = + + + +# DOS, Unix and BeOS: sample output frequency (eg. 44100) +sound_freq = + + + +# Unix and BeOS: preferred number of bits (8 or 16) +sound_bits = + + + +# Unix and BeOS: stereo output (1 or 0) +sound_stereo = + + + +# DOS only: soundcard port address (usually 220) +sound_port = + + + +# DOS only: soundcard DMA channel (usually 1) +sound_dma = + + + +# DOS only: soundcard IRQ number (usually 7) +sound_irq = + + + +# DOS only: FM synth port address (usually 388) +fm_port = + + + +# DOS only: MPU-401 port address (usually 330) +mpu_port = + + + +# DOS only: MPU-401 IRQ number (usually the same as sound_irq) +mpu_irq = + + + +# DOS only: instrument definitions for the Adlib driver +ibk_file = +ibk_drum_file = + + + +# Unix only: OSS device driver name (usually /dev/dsp or /dev/audio) +oss_driver = + + + +# Unix only: number of OSS driver fragments (buffers) and size of each buffer +oss_numfrags = +oss_fragsize = + + + +# Unix only: OSS MIDI device name (usually /dev/sequencer) +oss_midi_driver = + + + +# Unix only: OSS mixer device name (usually /dev/mixer) +oss_mixer_driver = + + + +# Unix only: name of the Enlightened Sound Daemon server +esd_server = + + + +# Unix only: card number and PCM device for the ALSA driver +alsa_card = +alsa_pcmdevice = + + + +# Unix only: size of ALSA driver fragments (buffers) +alsa_numfrags = + + + +# BeOS only: MIDI synthesizer instruments quality (0=low, 1=high) +be_midi_quality = + + + +# BeOS only: MIDI sample mixing frequency in Hz (11025, 22050 or 44100) +be_midi_freq = + + + +# BeOS only: MIDI interpolation (0=none, 1=fast linear, 2=linear) +be_midi_interpolation = + + + +# BeOS only: MIDI reverberation intensity (0 to 5) +be_midi_reverb = + + + +# patch set for the DIGMID software wavetable driver +patches = + + + + +[joystick] + +# DOS joystick drivers : +# +# STD - standard 2-button +# 2PAD - dual standard 2-button +# 4BUT - standard 4-button +# 6BUT - standard 6-button +# 8BUT - standard 8-button +# FPRO - CH Flightstick Pro +# WING - Logitech Wingman Extreme +# SW - Microsoft Sidewinder digital pad +# SWAG - Microsoft Sidewinder digital pad (aggressive) +# GPRO - Gravis GamePad Pro +# GRIP - Gravis GrIP +# GRI4 - Gravis GrIP (4-axis only) +# SNE1 - SNES joypads connected to LPT1 +# SNE2 - SNES joypads connected to LPT2 +# SNE3 - SNES joypads connected to LPT3 +# PSX1 - PSX joypads connected to LPT1 +# PSX2 - PSX joypads connected to LPT2 +# PSX3 - PSX joypads connected to LPT3 +# N641 - N64 joypads connected to LPT1 +# N642 - N64 joypads connected to LPT2 +# N643 - N64 joypads connected to LPT3 +# DB91 - Pair of 2-button joysticks connected to LPT1 +# DB92 - Pair of 2-button joysticks connected to LPT2 +# DB93 - Pair of 2-button joysticks connected to LPT3 +# TGX1 - TurboGraFX joysticks connected to LPT1 +# TGX2 - TurboGraFX joysticks connected to LPT2 +# TGX3 - TurboGraFX joysticks connected to LPT3 +# SEGI - IF-SEGA joystick interface card (ISA bus) +# SEGP - IF-SEGA joystick interface card (PCI bus) +# SGPF - IF-SEGA joystick interface card (fast PCI bus) +# WWAR - Wingman Warrior + +joytype = + + + +# BeOS only: joystick device port name (as reported on system joystick prefs) +joystick_device = + + + +# Linux only: which axis number the throttle is located at +throttle_axis = + +# alternatively you can specify it for each joystick: +# +# throttle_axis_0 = +# throttle_axis_1 = +# ... + + + + +[grabber] + +# stored grabber variables (editor state information) +xgrid = 16 +ygrid = 16 +backups = n +dither = n +transparency = n +griddle_xgrid = +griddle_ygrid = +griddle_mode = +griddle_empties = +griddle_autocrop = +griddle_type = +griddle_depth = + + + +# edit these shell commands to whatever helper programs you want to use... +data = start /w notepad +bmp = start /w "c:\program files\accessories\mspaint.exe" +rle = start /w "c:\program files\accessories\mspaint.exe" +cmp = start /w "c:\program files\accessories\mspaint.exe" +xcmp = start /w "c:\program files\accessories\mspaint.exe" +pal = start /w "c:\program files\accessories\mspaint.exe" +font = start /w "c:\program files\accessories\mspaint.exe" +samp = start /w "c:\program files\cooledit\cool.exe" +midi = start /w "c:\program files\cakewalk\wincake.exe" + +# or if you are using Linux you might prefer something more like: +# +# data = xterm -e fed -b +# bmp = gimp +# rle = gimp +# cmp = gimp +# xcmp = gimp +# pal = gimp +# font = gimp diff --git a/DOS32/default.fnt b/DOS32/default.fnt new file mode 100755 index 0000000000000000000000000000000000000000..b86378b047bbcf5a1802a0e8a157146678444195 GIT binary patch literal 3168 zcmbVOy-wUf5T0Oz?Sn*)l)@GA5emvfN^yz^+B*!oL@9_wq(q`Pp(yp06erjq3LYb6 z9>ZlGAtjHH2Z)kV+;0LN(A^8{@!2nK4RC$vi|6xV6_wCvZy2(_SXg97{&9nzRy1r9#@U{G{&Dgp0h zTORQ_Xk+7%7VM=RM@ipkMn44OGBpR^96!&x%*T(l+&+4pT&=FUeF%w5*! zj%P8R`FYk4^a&axPs)m(q{d}ZlX~$W!`P=>B9poKtL?_cCFZ1&I!K9j0H`VMpm4l*A8V5q% zQnNo>v_+RR(hy-KeAy!d-;OXMWs~}v_s0NQpEjkLGB8bSCD#IPt`l% z4N$2{h9m2#J_aVb(qjPClyYY6kW5`-gZ}K^4xw56cL( z8`2s*GWx!x*MH0##&1~*;n2){NB-u0^bXmxE1$Kdjpkp zYaDWO>v`XYx4#=#1^)YG50%-+kFo52J*6{eJ)_elbR2KjKc>CFzCw(bbmZcXpm&Ai z$Ed8rGmI|{@2z83rtQXu{5+#)u@UXH@6U<0Wqo@-p^!q z?AAOjLm4pJ4zJ3B@8Pao(Ur0Df0^O;ZG82OzM=O9d`@)b8ap#En9z0C40g%S|3}n; z*SYxq&jU*#zTaBth4X5wwy0T$Fs3~LY8Sstukr2thpa}cNwSeealD#DaTF(wL=me` vvMlRmX|ES0X|mqyW%$Fu<1|a-7~XN3CTSX_3EZP~sA-%aFk4I3K%LQF;D7c6 literal 0 HcmV?d00001 diff --git a/DOS32/font.h b/DOS32/font.h new file mode 100755 index 0000000..c7862ac --- /dev/null +++ b/DOS32/font.h @@ -0,0 +1,98 @@ +unsigned char raw_font_data[] = { +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,64,0,64,0,64,0,64,0,64,0,64,0,64,0,64,0,0,0,64,0,0,0,0,0,0,0,0, +0,0,0,0,0,80,0,80,0,80,0,80,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,20,0,20,0,20,0,126,0,40,0,40,0,252,0,80,0,80,0,80,0,0,0,0,0,0,0,0,0,0, +0,0,0,16,0,56,0,84,0,84,0,80,0,56,0,20,0,20,0,84,0,56,0,16,0,0,0,0,0,0,0,0,0, +0,0,48,64,72,128,72,0,49,0,2,0,4,192,8,32,17,32,33,192,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,24,0,36,0,36,0,36,0,24,0,40,0,68,0,69,0,66,0,61,0,0,0,0,0,0,0,0,0,0,0,0,0, +64,0,64,0,64,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16, +0,32,0,32,0,64,0,64,0,64,0,64,0,64,0,64,0,64,0,32,0,32,0,16,0,0,0,0,0,0,0,128,0, +64,0,64,0,32,0,32,0,32,0,32,0,32,0,32,0,32,0,64,0,64,0,128,0,0,0,0,0,0,0,32,0,168, +0,112,0,216,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,16,0,16,0,124,0,16,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,16,0,16,0,32,0, +32,0,32,0,64,0,64,0,64,0,128,0,128,0,128,0,0,0,0,0,0,0,0,0,56,0,68,0,68,0,68,0,68, +0,68,0,68,0,68,0,68,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,112,0,16,0,16,0,16,0, +16,0,16,0,16,0,16,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,56,0,68,0,68,0,4,0,8,0,16, +0,32,0,64,0,64,0,124,0,0,0,0,0,0,0,0,0,0,0,0,0,56,0,68,0,4,0,4,0,24,0,4,0, +4,0,4,0,68,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,12,0,20,0,20,0,36,0,68,0,124, +0,4,0,4,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,124,0,64,0,64,0,64,0,120,0,68,0,4,0, +4,0,68,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,56,0,68,0,64,0,64,0,88,0,100,0,68,0,68, +0,68,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,124,0,4,0,4,0,8,0,8,0,16,0,16,0,32,0, +32,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,56,0,68,0,68,0,68,0,56,0,68,0,68,0,68,0,68, +0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,56,0,68,0,68,0,68,0,76,0,52,0,4,0,4,0,68,0, +56,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,64, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,64,0, +128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,24,0,48,0,96,0,48,0,24,0,12,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,124,0,0,0,124,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,48,0,24,0,12,0,24,0,48,0,96,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,56,0,68,0,68,0,4,0,8,0,16,0,16,0,16,0,0,0,16,0,0,0,0,0, +0,0,0,0,0,128,7,96,24,16,32,16,32,200,67,72,68,72,68,136,68,112,35,0,32,48,24,192,7,0,0,0,0, +0,0,0,0,0,0,0,8,0,8,0,20,0,20,0,34,0,34,0,127,0,65,128,128,128,128,0,0,0,0,0,0,0, +0,0,0,0,0,0,126,0,65,0,65,0,65,0,126,0,65,0,65,0,65,0,65,0,126,0,0,0,0,0,0,0,0, +0,0,0,0,0,30,0,33,128,64,0,64,0,64,0,64,0,64,128,64,0,33,0,30,0,0,0,0,0,0,0,0,0, +0,0,0,0,126,0,65,128,64,128,64,128,64,128,64,128,64,128,64,0,65,0,126,0,0,0,0,0,0,0,0,0,0, +0,0,0,127,0,64,0,64,0,64,0,126,0,64,0,64,0,64,0,64,0,127,0,0,0,0,0,0,0,0,0,0,0, +0,0,127,0,64,0,64,0,64,0,126,0,64,0,64,0,64,0,64,0,64,0,0,0,0,0,0,0,0,0,0,0,0, +0,30,0,33,128,64,0,64,0,64,128,67,128,64,128,64,128,33,128,30,0,0,0,0,0,0,0,0,0,0,0,0,128, +64,128,64,128,64,128,64,128,127,128,64,128,64,128,64,128,64,128,64,0,0,0,0,0,0,0,0,0,0,0,0,0,64, +0,64,0,64,0,64,0,64,0,64,0,64,0,64,0,64,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0, +4,0,4,0,4,0,4,0,4,0,4,0,68,0,68,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,66,0,68, +0,72,0,80,0,96,0,80,0,72,0,68,0,66,0,65,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,64,0, +64,0,64,0,64,0,64,0,64,0,64,0,64,0,126,0,0,0,0,0,0,0,0,0,0,0,0,64,64,64,64,192,96, +192,96,64,81,64,81,64,74,64,74,64,68,64,68,0,0,0,0,0,0,0,0,0,0,0,0,128,64,128,96,128,80,128, +80,128,72,128,68,128,66,128,66,128,65,128,64,0,0,0,0,0,0,0,0,0,0,0,0,0,30,0,33,128,64,128,64, +128,64,128,64,128,64,128,64,0,33,0,30,0,0,0,0,0,0,0,0,0,0,0,0,0,126,0,65,128,64,128,64,0, +65,0,126,0,64,0,64,0,64,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,30,0,33,128,64,128,64,128,64, +128,64,128,64,128,66,0,33,128,30,0,0,0,0,0,0,0,0,0,0,0,0,0,126,0,65,128,64,128,64,0,65,0, +127,128,64,128,64,128,64,64,64,0,0,0,0,0,0,0,0,0,0,0,0,0,62,0,65,0,65,0,64,0,56,0,6, +0,1,0,65,0,65,0,62,0,0,0,0,0,0,0,0,0,0,0,0,128,255,0,8,0,8,0,8,0,8,0,8,0, +8,0,8,0,8,0,8,0,0,0,0,0,0,0,0,0,0,0,0,128,64,128,64,128,64,128,64,128,64,128,64,128,64, +128,64,0,33,0,30,0,0,0,0,0,0,0,0,0,0,0,0,128,128,128,128,0,65,0,65,0,34,0,34,0,20,0, +20,0,8,0,8,0,0,0,0,0,0,0,0,0,0,0,0,8,130,8,130,8,130,16,69,16,69,160,40,160,40,160,40, +64,16,64,16,0,0,0,0,0,0,0,0,0,0,0,0,0,129,0,129,0,66,0,36,0,24,0,24,0,36,0,66,0, +129,0,129,0,0,0,0,0,0,0,0,0,0,0,0,128,128,128,128,0,65,0,34,0,20,0,8,0,8,0,8,0,8, +0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,126,0,2,0,4,0,4,0,8,0,16,0,32,0,32,0,64,0, +126,0,0,0,0,0,0,0,0,0,0,0,0,0,112,0,64,0,64,0,64,0,64,0,64,0,64,0,64,0,64,0,64, +0,64,0,64,0,112,0,0,0,0,0,0,0,128,0,128,0,128,0,64,0,64,0,64,0,32,0,32,0,32,0,16,0, +16,0,16,0,0,0,0,0,0,0,0,0,224,0,32,0,32,0,32,0,32,0,32,0,32,0,32,0,32,0,32,0,32, +0,32,0,224,0,0,0,0,0,16,0,40,0,68,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,254, +0,0,0,0,0,0,0,0,0,64,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,60,0,66,0,62,0,66,0,66,0,66,0,61,0,0,0,0,0,0, +0,0,0,0,0,0,0,64,0,64,0,64,0,92,0,98,0,66,0,66,0,66,0,98,0,92,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,60,0,66,0,64,0,64,0,64,0,66,0,60,0,0,0,0,0,0,0,0, +0,0,0,0,0,2,0,2,0,2,0,58,0,70,0,66,0,66,0,66,0,70,0,58,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,60,0,66,0,66,0,126,0,64,0,66,0,60,0,0,0,0,0,0,0,0,0,0, +0,0,0,32,0,64,0,64,0,224,0,64,0,64,0,64,0,64,0,64,0,64,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,58,0,70,0,66,0,66,0,66,0,70,0,58,0,2,0,2,0,60,0,0,0,0,0,0, +0,64,0,64,0,64,0,88,0,100,0,68,0,68,0,68,0,68,0,68,0,0,0,0,0,0,0,0,0,0,0,0,0, +64,0,0,0,0,0,64,0,64,0,64,0,64,0,64,0,64,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,64, +0,0,0,0,0,64,0,64,0,64,0,64,0,64,0,64,0,64,0,64,0,64,0,128,0,0,0,0,0,0,0,64,0, +64,0,64,0,68,0,72,0,80,0,112,0,72,0,68,0,66,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,64, +0,64,0,64,0,64,0,64,0,64,0,64,0,64,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,128,89,64,102,64,68,64,68,64,68,64,68,64,68,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,88,0,100,0,68,0,68,0,68,0,68,0,68,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +60,0,66,0,66,0,66,0,66,0,66,0,60,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,92, +0,98,0,66,0,66,0,66,0,98,0,92,0,64,0,64,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,58,0, +70,0,66,0,66,0,66,0,70,0,58,0,2,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,80,0,96, +0,64,0,64,0,64,0,64,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,56,0,68,0, +64,0,56,0,4,0,68,0,56,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,64,0,224,0,64,0,64, +0,64,0,64,0,64,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,68,0,68,0,68,0, +68,0,68,0,76,0,52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,130,0,130,0,68,0,68, +0,40,0,40,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,136,128,136,0,85,0,85,0, +85,0,34,0,34,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132,0,72,0,48,0,48,0,48, +0,72,0,132,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,130,0,130,0,68,0,68,0,40,0, +40,0,16,0,16,0,32,0,192,0,0,0,0,0,0,0,0,0,0,0,0,0,120,0,8,0,16,0,32,0,32,0,64, +0,120,0,0,0,0,0,0,0,0,0,0,0,0,0,24,0,32,0,32,0,32,0,32,0,32,0,192,0,32,0,32,0, +32,0,32,0,32,0,24,0,0,0,0,0,0,0,64,0,64,0,64,0,64,0,64,0,64,0,64,0,64,0,64,0,64, +0,64,0,64,0,64,0,0,0,0,0,0,0,192,0,32,0,32,0,32,0,32,0,32,0,24,0,32,0,32,0,32,0, +32,0,32,0,192,0,0,0,0,0,0,0,49,0,73,0,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,5,3,5,7,7,12,9,3,4,4,5,7,3,4,3,4,7,7,7,7,7,7,7,7,7,7,3,3,7,7, +7,7,14,9,9,9,10,9,8,10,10,3,7,8,7,11,10,10,9,10,10,9,9,10,9,13,8,9,8,4,4,4,7, +7,4,8,8,7,8,8,3,8,7,3,3,7,3,11,7,8,8,8,4,7,3,7,7,9,6,7,6,5,3,5,9,0, +}; diff --git a/DOS32/keyboard.dat b/DOS32/keyboard.dat new file mode 100755 index 0000000000000000000000000000000000000000..91677606e51e3045345fc2c0bdd4ce8a9992440b GIT binary patch literal 10927 zcmcI~d0-S(zIENY)tv=Mr|VWoqDeaGsvsbczDregWgrbnvxF^>1Z5FpHeyIfFbfEy zGmg&@2XqEAj@vv)9QFi-sK_26Ar2q|IxLF90HZJhqr?ERM9I4y@X6DB^Z35+y+4vv zs=KPI>v!%s=XZWpS^DsxVPnS*5(Gh+T~R*UKP-DV-wvk(u{n@PjyCHRvkrbH67mgf1J--Hpkb^6 zoA@a(UVB+u39#n_XuK)1wf8IU0&F%wvqbhz1&CNqj6Z zx=f={pSk>{WAG{28;zcq(~Zk;iOe>^H8M-q+WmlC7J{V8hl^(ntEvsoR~p3;T9c>) zG-|IHsvQya%kb}_zDug{1HBizAnKbW%c)DzisEDq@Sy9o>xwHRNw_;{V3IFsVp4um z^`JX0d*)BdUU6I5FL!ocKh<5?D{|)-m-Y(1@_p6wJ8xAz|HQ4T8+yHBBe9`07Fddn z{lW^xU6t=>OZT|#lWf>ypA^Q5f>Z2W^)E)e{9_Ccum2EX1<>n*R~WpM<$RlCf^4Ag zNmeN!#xl`dxdvGD6dVEcH=uQ91Yo9QcL!gT3f$GI zExi%-0&I9AqZm;mt!%VI#N;6~)D>;jp{X(^2||AF7f|~R(Re8QMO$fAQAK@kM25-U z6NrqL&H5J*Jt~`SL}LKn^o?_&vp+u_c_zwqk5r9esuaGiw>e!n+&kKvjc#ykWb?CR zNExN&a|>-+3?P3~X7qCs`Vbn=f9I-`aHE?&gvbiFUZ!sHf)B+-1FC1wUeXV1IZC73 z^B&yp_MAX_)ZgIu;1LzaYpD^7`V--mq&n27j#Rg+*=Ub8QWcM=Y@jyWmoIh<8mY=u zC{yET9qP}@RFg@O6*l43K`s3g1UR;1kd}NU>emwAk|YqbBn9y7gm&Z=9U%o3UJ4b zF7gO)M`wUM0^BhzKpqB(wh?#eVd@4n+Qw2iijo2Z!fE9m`&8Q=tlOuCvC`lk97U`0 zi;JT;jEn?8k6No?60q26k?47k0MbXO2k!ww_eledM}Xm74ZVlNRAFX4unqz8E;Kx+ zMBQ91nn6x#?0_f5Mp7k&JI4T~uNJK*f}DB$zXUN)=piXWC0d!luQhI;v_ z)StkA{%!D|$KhYr4gAyQ7v?`4pIO*ByE?D5IR9T0DXEXKk;G8SAs@qubXdquA{`cz zNT%7a*FG(bO$!c_Y6CJ_e0M-a6ZHx}`UwrdST9x!TN(gYMMCKs=$+Zo#tulQd&uZVU#x(L}3?s~%QVy+Rc z$*!5M$6P(_l?MXLqSn-BE|lo@G{cYNUwI^TKfAz=(F^=&JoH;OnNC+le*|@;;SHKV+&iUy8AVcU z$_8~PxEvuyxy@#n=su=2(<^8{6E>)yGMassM$pTw#9u(fvkLhfg^b=Htpkq4eg5EG zN^IJ5L&&g<2-YrdP=(;E>8g?yK?~D%C-3H;E9_>^rXA)Vdzj^BZIC`kscX~86789+ z_eTG5GW}}eopQMUC*H5%cJKEl_kKgV0rAMfidng3^Zx(6-`Otj$9)_jw6qTP=U%Ul z({@K&5hkP#z6*%nDjnu_fUT9h+zuM`+#s$Jy>*b|*V0*?Xm$dsZ-z&~Don5qaYq%7 z%=TDdEDqwHjUzF!q&C=C>u3_K1Yx(0VqHQXhhaTSX$KUuR;d#Zb`^%keeK-@RoCL6 zfNhvmRSOA!T|%^zpV%<3AQRB5koMgxL+xF_O1LQyW06Ws?2JX@;;i$qFfNUd66L7a zDxDVbqe|4v@_kys5S)jhXp^KhMRQxp5eoDMZYsaF4n(&K-iQDXrLvhw8Xr61)-Np=5CqK{J!V>4bKz~)Sfd1?exK_6G$SAk) zlC3m|Tg&}XXCkWAvFN1y3VjY;Olt*@Yo7b#aQ9<4sCE?@>Q7NA4G7`jh;Hfd?n`lRNhLj#P-OdQ~3dD@kv5Z~Rt zfj*y4oanO(EQ03vp7FivTk8vM@;xF4$z*=~9pcPBJnP5&SXlW3KZ1_V&W`YI@HM=0 zc5X{!?tjCN`uF^3-Oi5-IX{jI!{Zz|4x2qUBjD=-=?G^=Oj9@|a&9zc^S`fiZnO-} zj9eNwaLx>L%8XNE%`D)3lE3abEAEEoeV4g)+XhX}V{YN(4G=3n$>WADX5_V9=Gfr) zv*W1bNl~8t!f`zWTPY~a6@RN+A&AU%(iM!#^fEjIDQCoV@B3(}Y`%{&o$PTGO%I4; z5aDzuhos@MH70s5nvO_;>zO&qA?dPcK8_aO&FN5kDZq!Hp(V1lEZQHxl+z(+ z!zSfZfcufXXqRm4jiSrYaXAf}el1fTqa5JfnKYV*ClB_u{OV#v+`{0OY*k%U_>Pw8 zEFsjd63&Ax1@)fGTkHD~_LDSiBJsB*^iL*U-!8tbk3mmNr=Ou>winHz_lNgSrowZ+ zLvRJZ{^|4r-+u@`HizCbgywMW9Gnb1F9fH;zx$S<70jI!e~VLk6|>U_y-t?>0D+%?*>LAipxp^J@UT_*#7Tj_O9Dx_CaCG z_ae)YGl*w^ z81BVR24U+2>~uI?q2Q*pCLwYL=#=}VfUs`lC|E)N3F#vF8l*b`8@&b6{Q+Hl!^v?Z zT|&BS{LD4ZmE(Hku6Fnv?^DXf0CayIkW}rd0EYev7s}S+Xtxk|6GIJAEfE)CK~HtN zOi#cgTv4}ZH9LWI34JA7J=NJ;rEod!64ms=I&G&&&+}jU zd2MAtRky%1BE1Ij$(>@dGKpMCNEpnKkxG+2mz16G65oaY3FR}QXEB;Z@g>wGpG(L~ zNG3_XJawdxus=f~mtV*%mZ#3AgX2#Ru1V>+1+MWCd=0kH!Ri4fY=OsUP2VcM?iQmS z{ylzS63tU{`5m6c?)Cq}wq~qSlQP~S)4ZM{>u1_AD#i_A8#4y0+3XU^<#$h-#U|cQ z!tp~7)TG$_8|h5fD9^p=*gFzigYTHzM*JlHn{iwGCw|e{d39-bpsdI(%P;I!JAV@Y zaVqPI|88}y7VlcK0^wg)7|Y@kdb4W{NGN%|EBt+}EBl>B>1!hOtOjfg>=VNqf-Sr) zV1tEi;P2i*2D}Xn=cGpZZ-D^{JS!P*i?f9KWx#wJkXFdmn7!&ixVO_XUxPgPYvoz6 z@tqi>gz$9dP(L&i;LVVy!q~I-LukAuvI=DfcaQaJp}7OFZy*VA;uZKqh(8xwyOQ9A zA41d1y__-}@bGov)3SFnG-Jf)gSbR?MZ$H^Eo1Y^DaiO(+zbcF@xY9*JjdZk`D8?v z%p(tt2cKRCDv?cw9+hdHHfjer4r^pQNn0v=85-3fkJdKIYzk_T8AJ91{2+!tmFHW44)ngL5B)~kByW~>$*t0Gpr4g$z61Ic6fEmZ>Fs2~ zT4=v_xD=B#C~0U?W>P^?{G6m`l3q<(o7B{o_I4hJ-|k7e5P8#w33b88;hQ4x9_NURpzHGYgSdC=28nQJZ1&Qkg_+ zUa8Nh7dW${qKZ1{Er2ij--TzTXL960*&m5kFZMHpz1Gll_MP~R6yhAKM94-=RJ2v7 z@JN-Mg;~8{uq_?e#qe3#gJjb8qQ4<^mqd7o+LOHn|GpODhSc@W^AGR^DStN=k5w8{ zJufS+yyc4gmgt$n|HYr&^Puf|4*%|Q{;yd>>R=XE@C|$m2Cq=O`Cm*Y8Rbi}AI?{^ zMOPjfAT|Ls>d`T>YeF15p#!{#^ ziexD??&CIqE`{DkfL+4WI$$+&VO$z$k~df_&}`%Xt%2r?sXWtMDq3ye7&P1O2F7oL z!n%57XUP(2yW>Cz^DCb@E;}9zy2};Y1-d2hFez}-nTS5(#Kx;UFVcBFv=N2KX7)yu z;$+7l-7De-G|WtJUPQ~pW@Vjd%~T{E96&}YSR*jghMII+3@9cjB< ztUShH{wXqR`U3sm6dmXXICQO(VNn;63;DzQ23q4kSCp47k>a%HIjF`IRhUs$r?MAV z2ZDhyQ)!gL?zqz(pYh|$m6hLCx!>~2J>E^_#+O&j&E=)*X4vun)+sO==oHRdq&C1_ zyt&;{8?bPEK2Zz3{hI*$K(d+ujTc@6uY2C-RjU=vuK|6P*D#IO?kbD{#$&$=P){IZ zTk6=qK8*LayBtS&<6!?ASKkk$8zDD%(iX~NxInlz5RuQh{$7EB@rALATj~|`14v(% zGZEbm*wk~7PH{548JUqzKhMKt!-_6d#>QD!;MhXf+`F)mi5AK19hhkL1N!s)cs?3? zN&ebe68(-4*A-u&^3s9mf=tdTQ=HAV)P8_`AWdxr3a(bJhK8VOs3oNu( zreoC4xJvhCHQRv&)DGFQP>>tHLYAQQBKDq>rpQDXbH_S1VeF49JG=C@$_kyG-&x&M zc0zfVx9YC4|EXi1Og_fyXiKoudqdd0A@6=a#lnl-_DtKLpn@<~5NHKE170?V_vrbB z6#+Tpd}T}(9{8%wAC zH=yw$@GkCE4QJ0@)+0f9+kOPLKrf0vAn`UCwn;4_-XmS%)xHt(o#d2F>-nIul2GS( z)4@UD;4a?L)3qW>xW{F=#<-@t%3Z&5{XX_ijp~G-@Z0R$_)U!E{q$Ge-ph)r>Z07z z6J6t+z%-|`^42K$mtEtWz%-}9FnZgFE0ATImp#30>}OqxP*z}`prOIB}1(QB3eILM^ZZ9uTbyk`?{(7cd31^Dj_|fNW_& z+5n*Ylw_d)5{2=VqC|HHFL$kiKNUq8m5L~DQFttd(~U*S2$_^d&s(ECd@PKK zf?CASh+Y}~vTb5O4EGdH)EeD%i5AE0>42BeS#Ez8AwEXjEp0mGUX!pTFhAVa-=i&4 zPPy?f(U)$Ig086vcss0c|9irg{$zJj2Nd~SWj*{-y#TLi9nen2jRQ%n?neiLeHBJDa+wi zsSr0p6Ddt1_H2TQ_zlisjr2N z(qAEIg4W_aN~W?^`51b)Dm*|Q?zskyk0qd;hL#-_8l61d)OyO;a{`8#hH?#>ex;!+ zsJS_BSsG$&f;Ekh*-;bJi_rU!X$|mWsJFt?kUwzH4%79ms1xc4^+d*Bp&YH~$F${; zYwvvRm{!vS4Lh2s=nDH78roJgOJPMyJ7m?$Zf)@x<*@d;au;G-`PXGnU4uIny+P{5 z8l?B3Aw0!i)1G3dr4v#=c0xK0Lkxsejh$hKwN~iU&N54Df@W8U17}nfCCy7(l(Zyi zUDD1Z;b79~q#qKSi9R9USM6Kid&Sq_+wMEyJLMBD`fM4IT~DXn$+Ik3KbdD#-Zsw= zxkJ15bT`1tnp0R&TvYhK*lNFFn|@R9d80iIj$R48JXG##wOjsct9=Pj9^CN2M(_Bq zt@hE9Wzr`iW~aEyx1E$OIdX5Mb#N!&AtSOzwBpqLyv=@+r`ms@TM-@! z$QC6^#$S`G;kTOZLhS*qS){A@FMXB9FPZ2E;Xg!r9pYy3SNJH;xn;zGk@$)1eIPC} z!^Y_?IN7sR8PyE8D*XxmlE2%-KP@!{uhcAVD|f>aD)88qYF|FIrNpU?>^c+-ex0c_ zR?DDQ(YGYs8Ezl@T*^T>Aj5MNrSf|sd_?24oQyf5Ek#B#>X*9_OAu7$3DcRluo9DK8t-tQ7m z#<8C2HFQR?CoIxrcZl8uU2c6Sm+q**yfm)Sxt3k`s(T>rV0JNOi* zVmXX=Ptmg7tK0cU7bfC?ZoE>(BSvvgIKiLozMihMU?IKG{GUMo~delx__qs{qlzQlW)sYI|8nApkv-M>{PDZ9*U?VD-5sS7 zq(0>rV5(1hDEnggzddkdRAX9CRaletthynqgr@t`_$Q_Lg>`g-|7=#Hgw&zgFuzF} z`f^$cb(B|2ca2(@N}Cemx0N0lb)=xg(_=J?&e)wT6r>HEl8mLtj*OpJ5TCVD6&I!v z-yOH$lYX*Hs&88+LStv=jUC-AlSyU8RfPrdV>*k=iVDhS{c8&m!)HHdfHPWZ`#Y14ktL#)pzZJ#5(r|V67s3ox_*b zw~EFYV4R0WyJ#*UtwER=txy9Pkn?x`<$p4dsJ?9;Ax!V=?40y}{_A#mH#@3 zxSIzNS_subnD%`Tp@mKkqm#p!FgZ9tX15+2?J=? +#include +#include +#include +#include +#include +#include +#include +#include + +#include "level9.h" + +#include "glk.h" + +#if TARGET_OS_MAC +# include "macglk_startup.h" +#else +# include "glkstart.h" +#endif + + +/*---------------------------------------------------------------------*/ +/* Module variables, miscellaneous other stuff */ +/*---------------------------------------------------------------------*/ + +/* Glk Level9 port version number. */ +static const glui32 GLN_PORT_VERSION = 0x00010306; + +/* + * We use the main Glk window for pretty much everything. Level9 doesn't + * generate a status line, but we have a "status" window to use as a + * banner at the top of the window, and as a handy place to hang an Xglk + * workround. + */ +static winid_t gln_main_window = NULL; +static winid_t gln_status_window = NULL; + +/* + * Transcript stream and input log. These are NULL if there is no + * current collection of these strings. + */ +static strid_t gln_transcript_stream = NULL; +static strid_t gln_inputlog_stream = NULL; + +/* Input read log stream, for reading back an input log. */ +static strid_t gln_readlog_stream = NULL; + +/* Options that may be turned off by command line flags. */ +static int gln_intercept_enabled = TRUE; +static int gln_prompt_enabled = TRUE; +static int gln_loopcheck_enabled = TRUE; +static int gln_abbreviations_enabled = TRUE; +static int gln_commands_enabled = TRUE; + +/* Note indicating whether the Glk library has timers, or not. */ +static int gln_timeouts_possible = TRUE; + +/* Reason for stopping the game, used to detect restarts and ^C exits. */ +static enum { STOP_NONE, STOP_FORCE, STOP_RESTART, STOP_EXIT } + gln_stop_reason = STOP_NONE; + +/* Level9 standard input prompt string. */ +static const char *GLN_INPUT_PROMPT = "> "; + +/* Internal interpreter symbols used for our own deviant purposes. */ +extern void save (void); +extern void restore (void); +extern L9BOOL Cheating; + +/* Forward declaration of event wait functions. */ +static void gln_event_wait (glui32 wait_type, event_t *event); +static void gln_event_wait_2 (glui32 wait_type_1, + glui32 wait_type_2, event_t *event); + +/* Forward declaration of the confirmation function. */ +static int gln_confirm (const char *prompt); + + +/*---------------------------------------------------------------------*/ +/* Glk arguments list */ +/*---------------------------------------------------------------------*/ + +#if !TARGET_OS_MAC +glkunix_argumentlist_t glkunix_arguments[] = { + { (char *) "-nc", glkunix_arg_NoValue, + (char *) "-nc No local handling for Glk special commands" }, + { (char *) "-na", glkunix_arg_NoValue, + (char *) "-na Turn off abbreviation expansions" }, + { (char *) "-ni", glkunix_arg_NoValue, + (char *) "-ni No local handling for 'quit', 'restart'" + " 'save', and 'restore'" }, + { (char *) "-np", glkunix_arg_NoValue, + (char *) "-np Turn off additional interpreter prompt" }, + { (char *) "-nl", glkunix_arg_NoValue, + (char *) "-nl Turn off infinite loop detection" }, + { (char *) "", glkunix_arg_ValueCanFollow, + (char *) "filename game to run" }, +{ NULL, glkunix_arg_End, NULL } +}; +#endif + + +/*---------------------------------------------------------------------*/ +/* Glk port utility functions */ +/*---------------------------------------------------------------------*/ + +/* + * gln_fatal() + * + * Fatal error handler. The function returns, expecting the caller to + * abort() or otherwise handle the error. + */ +void +gln_fatal (const char *string) +{ + /* + * If the failure happens too early for us to have a window, print + * the message to stderr. + */ + if (gln_main_window == NULL) + { + fprintf (stderr, "\n\nINTERNAL ERROR: "); + fprintf (stderr, "%s", string); + fprintf (stderr, "\n"); + + fprintf (stderr, "\nPlease record the details of this error,"); + fprintf (stderr, " try to note down everything you did to"); + fprintf (stderr, " cause it, and email this information to"); + fprintf (stderr, " simon_baldwin@yahoo.com.\n\n"); + return; + } + + /* Cancel all possible pending window input events. */ + glk_cancel_line_event (gln_main_window, NULL); + glk_cancel_char_event (gln_main_window); + + /* Print a message indicating the error, and exit. */ + glk_set_window (gln_main_window); + glk_set_style (style_Normal); + glk_put_string ("\n\nINTERNAL ERROR: "); + glk_put_string ((char *) string); + glk_put_string ("\n"); + + glk_put_string ("\nPlease record the details of this error,"); + glk_put_string (" try to note down everything you did to"); + glk_put_string (" cause it, and email this information to"); + glk_put_string (" simon_baldwin@yahoo.com.\n\n"); +} + + +/* + * gln_malloc() + * + * Non-failing malloc and realloc; call gln_fatal and exit if memory + * allocation fails. + */ +static void * +gln_malloc (size_t size) +{ + void *pointer; /* Return value pointer. */ + + /* Malloc, and call gln_fatal if the malloc fails. */ + pointer = malloc (size); + if (pointer == NULL) + { + gln_fatal ("GLK: Out of system memory"); + glk_exit (); + } + + /* Return the allocated pointer. */ + return pointer; +} + +static void * +gln_realloc (void *ptr, size_t size) +{ + void *pointer; /* Return value pointer. */ + + /* Realloc, and call gln_fatal() if the realloc fails. */ + pointer = realloc (ptr, size); + if (pointer == NULL) + { + gln_fatal ("GLK: Out of system memory"); + glk_exit (); + } + + /* Return the allocated pointer. */ + return pointer; +} + + +/* + * gln_strncasecmp() + * gln_strcasecmp() + * + * Strncasecmp and strcasecmp are not ANSI functions, so here are local + * definitions to do the same jobs. + * + * They're global here so that the core interpreter can use them; otherwise + * it tries to use the non-ANSI str[n]icmp() functions. + */ +int +gln_strncasecmp (const char *s1, const char *s2, size_t n) +{ + size_t index; /* Strings iterator. */ + + /* Compare each character, return +/- 1 if not equal. */ + for (index = 0; index < n; index++) + { + if (glk_char_to_lower (s1[ index ]) + < glk_char_to_lower (s2[ index ])) + return -1; + else if (glk_char_to_lower (s1[ index ]) + > glk_char_to_lower (s2[ index ])) + return 1; + } + + /* Strings are identical to n characters. */ + return 0; +} +int +gln_strcasecmp (const char *s1, const char *s2) +{ + size_t s1len, s2len; /* String lengths. */ + int result; /* Result of strncasecmp. */ + + /* Note the string lengths. */ + s1len = strlen (s1); + s2len = strlen (s2); + + /* Compare first to shortest length, return any definitive result. */ + result = gln_strncasecmp (s1, s2, (s1len < s2len) ? s1len : s2len); + if (result != 0) + return result; + + /* Return the longer string as being the greater. */ + if (s1len < s2len) + return -1; + else if (s1len > s2len) + return 1; + + /* Same length, and same characters -- identical strings. */ + return 0; +} + + +/*---------------------------------------------------------------------*/ +/* Glk port stub graphics functions */ +/*---------------------------------------------------------------------*/ + +/* + * Glk libraries aren't really geared up for the type of line drawing and + * point-plotting graphics that the Level9 games contain, so for now, + * these are stub functions. + */ +void os_graphics (int mode) {} +void os_cleargraphics (void) {} +void os_setcolour (int colour, int index) {} +void os_drawline (int x1, int y1, int x2, int y2, + int colour1, int colour2) {} +void os_fill (int x, int y, int colour1, int colour2) {} + + +/*---------------------------------------------------------------------*/ +/* Glk port infinite loop detection functions */ +/*---------------------------------------------------------------------*/ + +/* Short timeout to wait purely in order to get the display updated. */ +static const glui32 GLN_WATCHDOG_FIXUP = 50; + +/* + * Timestamp of the last watchdog tick call, and timeout. This is used to + * monitor the elapsed time since the interpreter made an I/O call. If it + * remains silent for long enough, set by the timeout, we'll offer the + * option to end the game. A timeout of zero disables the watchdog. + */ +static time_t gln_watchdog_monitor = 0; +static int gln_watchdog_timeout_secs = 0; + + +/* + * gln_watchdog_start() + * gln_watchdog_stop() + * + * Start and stop watchdog monitoring. + */ +static void +gln_watchdog_start (int timeout) +{ + assert (timeout >= 0); + + gln_watchdog_timeout_secs = timeout; + gln_watchdog_monitor = time (NULL); +} + +static void +gln_watchdog_stop (void) +{ + gln_watchdog_timeout_secs = 0; +} + + +/* + * gln_watchdog_tick() + * + * Set the watchdog timestamp to the current system time. + * + * This function should be called just before almost every os_* function + * returns to the interpreter, as a means of timing how long the interp- + * reter dwells in running game code. + */ +static void +gln_watchdog_tick (void) +{ + gln_watchdog_monitor = time (NULL); +} + + +/* + * gln_watchdog_timeout() + * + * Check to see if too much time has elapsed since the last tick. If it + * has, offer the option to stop the game, and if accepted, return TRUE. + * Otherwise, if no timeout, or if the watchdog is disabled, return FALSE. + */ +static int +gln_watchdog_timeout (void) +{ + time_t current_time; /* Current system time. */ + double delta_time; /* Time difference. */ + + /* + * If loop detection is off, or if the timeout is set to zero, + * do nothing. + */ + if (!gln_loopcheck_enabled + || gln_watchdog_timeout_secs == 0) + return FALSE; + + /* Find elapsed time in seconds since the last watchdog. */ + current_time = time (NULL); + delta_time = difftime (current_time, gln_watchdog_monitor); + + /* If too much time has passed, offer to end the game. */ + if (delta_time >= gln_watchdog_timeout_secs) + { + /* It looks like the game is in an endless loop. */ + if (gln_confirm ("\nThe game may be in an infinite loop." + " Do you want to stop it? [Y or N] ")) + { + /* Return TRUE -- timed out, and stop requested. */ + gln_watchdog_monitor = time (NULL); + return TRUE; + } + + /* + * If we have timers, set a really short timeout and let it + * expire. This is to force a display update with the + * response of the confirm -- without this, we may not get + * a screen update for a while since at this point the + * game isn't, by definition, doing any input or output. + * If we don't have timers, no biggie. + */ + if (gln_timeouts_possible) + { + event_t event; /* Glk event buffer. */ + + glk_request_timer_events (GLN_WATCHDOG_FIXUP); + gln_event_wait (evtype_Timer, &event); + glk_request_timer_events (0); + } + + /* Reset the monitor and return FALSE -- stop rejected. */ + gln_watchdog_monitor = time (NULL); + return FALSE; + } + + /* No timeout yet. */ + return FALSE; +} + + +/*---------------------------------------------------------------------*/ +/* Glk port status line functions */ +/*---------------------------------------------------------------------*/ + +/* + * gln_status_redraw() + * + * Level9 doesn't produce a status line, so we've nothing to do here except + * print a default piece of text describing the interpreter. However, we + * need this spot to hang the Xglk workround on. This function should be + * called on the appropriate Glk window resize and arrange events. + */ +static void +gln_status_redraw (void) +{ + /* If there is a status window, update it. */ + if (gln_status_window != NULL) + { + winid_t parent; /* Status window parent. */ + strid_t status_stream; /* Status window stream. */ + + /* + * Rearrange the status window, without changing its actual + * arrangement in any way. This is a hack to work round + * incorrect window repainting in Xglk; it forces a complete + * repaint of affected windows on Glk window resize and + * arrange events, and works in part because Xglk doesn't + * check for actual arrangement changes in any way before + * invalidating its windows. The hack should be harmless to + * Glk libraries other than Xglk, moreover, we're careful to + * activate it only on resize and arrange events. + */ + parent = glk_window_get_parent (gln_status_window); + glk_window_set_arrangement (parent, + winmethod_Above|winmethod_Fixed, + 1, NULL); + + /* ...now update the status window. */ + glk_window_clear (gln_status_window); + status_stream = glk_window_get_stream (gln_status_window); + glk_put_string_stream (status_stream, "Glk Level9 version 3.0"); + } +} + + +/*---------------------------------------------------------------------*/ +/* Glk port output functions */ +/*---------------------------------------------------------------------*/ + +/* + * Output buffer. We receive characters one at a time, and it's a bit + * more efficient for everyone if we buffer them, and output a complete + * string on a flush call. + */ +static const int GLN_BUFFER_INCREMENT = 1024; +static char *gln_output_buffer = NULL; +static int gln_output_size = 0; +static int gln_output_length = 0; + +/* + * Output activity flag. Set when os_printchar() is called, and queried + * periodically by os_readchar(). Helps os_readchar() judge whether it must + * request input, or when it's being used as a crude scroll control. + */ +static int gln_output_activity = FALSE; + +/* + * Flag to indicate if the last buffer flushed looked like it ended in a + * "> " prompt. Some later games switch to this mode after a while, and + * it's nice not to duplicate this prompt with our own. + */ +static int gln_output_prompt = FALSE; + + +/* + * gln_output_notify() + * + * Register recent text output from the interpreter. This function is + * called by os_printchar(). + */ +static void +gln_output_notify (void) +{ + /* Set the output activity flag. */ + gln_output_activity = TRUE; +} + + +/* + * gln_recent_output() + * + * Return TRUE if the interpreter has recently output text, FALSE otherwise. + * Clears the flag, so that more output text is required before the next + * call returns TRUE. + */ +static int +gln_recent_output (void) +{ + int result; /* Return value. */ + + /* Save the current flag value, and reset the main flag. */ + result = gln_output_activity; + gln_output_activity = FALSE; + + /* Return the old value. */ + return result; +} + + +/* + * gln_game_prompted() + * + * Return TRUE if the last game output appears to have been a "> " prompt. + * Once called, the flag is reset to FALSE, and requires more game output + * to set it again. + */ +static int +gln_game_prompted (void) +{ + int result; /* Return value. */ + + /* Save the current flag value, and reset the main flag. */ + result = gln_output_prompt; + gln_output_prompt = FALSE; + + /* Return the old value. */ + return result; +} + + +/* + * gln_detect_game_prompt() + * + * See if the last non-newline-terminated line in the output buffer seems + * to be a prompt, and set the game prompted flag if it does, otherwise + * clear it. + */ +static void +gln_detect_game_prompt (void) +{ + int index; /* Output buffer iterator. */ + + /* Begin with a clear prompt flag. */ + gln_output_prompt = FALSE; + + /* Search across any last unterminated buffered line. */ + for (index = gln_output_length - 1; + index >= 0 && gln_output_buffer[ index ] != '\n'; + index--) + { + /* Looks like a prompt if a non-space character found. */ + if (gln_output_buffer[ index ] != ' ') + { + gln_output_prompt = TRUE; + break; + } + } +} + + +/* + * gln_output_delete() + * + * Delete all buffered output text. Free all malloc'ed buffer memory, and + * return the buffer variables to their initial values. + */ +static void +gln_output_delete (void) +{ + /* Clear and free the buffer of current contents. */ + if (gln_output_buffer != NULL) + free (gln_output_buffer); + gln_output_buffer = NULL; + gln_output_size = 0; + gln_output_length = 0; +} + + +/* + * gln_output_flush() + * + * Flush any buffered output text to the Glk main window, and clear the + * buffer. Check in passing for game prompts that duplicate our's. + */ +static void +gln_output_flush (void) +{ + assert (glk_stream_get_current () != NULL); + + /* Do nothing if the buffer is currently empty. */ + if (gln_output_length > 0) + { + /* See if the game issued a standard prompt. */ + gln_detect_game_prompt (); + + /* + * Print the buffer to the stream for the main window, in + * game output style. + */ + glk_set_style (style_Normal); + glk_put_buffer (gln_output_buffer, gln_output_length); + + /* Clear and free the buffer of current contents. */ + gln_output_delete (); + } +} + + +/* + * os_printchar() + * + * Buffer a character for eventual printing to the main window. + */ +void +os_printchar (char c) +{ + assert (gln_output_length <= gln_output_size); + + /* Note that the game created some output. */ + gln_output_notify (); + + /* Grow the output buffer if necessary. */ + if (gln_output_length == gln_output_size) + { + gln_output_size += GLN_BUFFER_INCREMENT; + gln_output_buffer = gln_realloc (gln_output_buffer, + gln_output_size); + } + + /* Handle return as a newline. */ + if (c == '\r') + { + gln_output_buffer[ gln_output_length++ ] = '\n'; + return; + } + + /* Add the character to the buffer. */ + gln_output_buffer[ gln_output_length++ ] = c; +} + + +/* + * gln_standout_string() + * gln_standout_char() + * + * Print a string in a style that stands out from game text. + */ +static void +gln_standout_string (const char *message) +{ + assert (message != NULL); + + /* + * Print the message, in a style that hints that it's from the + * interpreter, not the game. + */ + glk_set_style (style_Emphasized); + glk_put_string ((char *) message); + glk_set_style (style_Normal); +} + +static void +gln_standout_char (char c) +{ + /* Print the character, in a message style. */ + glk_set_style (style_Emphasized); + glk_put_char (c); + glk_set_style (style_Normal); +} + + +/* + * gln_normal_string() + * gln_normal_char() + * + * Print a string in normal text style. + */ +static void +gln_normal_string (const char *message) +{ + assert (message != NULL); + + /* Print the message, in normal text style. */ + glk_set_style (style_Normal); + glk_put_string ((char *) message); +} + +static void +gln_normal_char (char c) +{ + /* Print the character, in normal text style. */ + glk_set_style (style_Normal); + glk_put_char (c); +} + + +/* + * gln_header_string() + * gln_banner_string() + * + * Print text messages for the banner at the start of a game run. + */ +static void +gln_header_string (const char *banner) +{ + assert (banner != NULL); + + /* Print the string in a header style. */ + glk_set_style (style_Header); + glk_put_string ((char *) banner); + glk_set_style (style_Normal); +} + +static void +gln_banner_string (const char *banner) +{ + assert (banner != NULL); + + /* Print the banner in a subheader style. */ + glk_set_style (style_Subheader); + glk_put_string ((char *) banner); + glk_set_style (style_Normal); +} + + +/* + * ms_flush() + * + * Handle a core interpreter call to flush the output buffer. Because + * Glk only flushes its buffers and displays text on glk_select(), we + * can ignore these calls as long as we call glk_output_flush() when + * reading line or character input. + */ +void os_flush (void) +{ +} + + +/*---------------------------------------------------------------------*/ +/* Glk command escape functions */ +/*---------------------------------------------------------------------*/ + +/* Valid command control values. */ +static const char *GLN_COMMAND_ON = "on"; +static const char *GLN_COMMAND_OFF = "off"; + + +/* + * gln_command_script() + * + * Turn game output scripting (logging) on and off. + */ +static void +gln_command_script (const char *argument) +{ + assert (argument != NULL); + + /* Set up a transcript according to the argument given. */ + if (!gln_strcasecmp (argument, GLN_COMMAND_ON)) + { + frefid_t fileref; /* Glk file reference. */ + + /* See if a transcript is already active. */ + if (gln_transcript_stream != NULL) + { + gln_normal_string ("Glk transcript is already "); + gln_normal_string (GLN_COMMAND_ON); + gln_normal_string (".\n"); + return; + } + + /* Get a Glk file reference for a transcript. */ + fileref = glk_fileref_create_by_prompt + (fileusage_Transcript | fileusage_TextMode, + filemode_WriteAppend, 0); + if (fileref == NULL) + { + gln_standout_string ("Glk transcript failed.\n"); + return; + } + + /* Open a Glk stream for the fileref. */ + gln_transcript_stream = glk_stream_open_file + (fileref, filemode_WriteAppend, 0); + if (gln_transcript_stream == NULL) + { + glk_fileref_destroy (fileref); + gln_standout_string ("Glk transcript failed.\n"); + return; + } + glk_fileref_destroy (fileref); + + /* Set the new transcript stream as the main echo stream. */ + glk_window_set_echo_stream (gln_main_window, + gln_transcript_stream); + + /* Confirm action. */ + gln_normal_string ("Glk transcript is now "); + gln_normal_string (GLN_COMMAND_ON); + gln_normal_string (".\n"); + } + else if (!gln_strcasecmp (argument, GLN_COMMAND_OFF)) + { + /* If a transcript is active, close it. */ + if (gln_transcript_stream != NULL) + { + glk_stream_close (gln_transcript_stream, NULL); + gln_transcript_stream = NULL; + + /* Clear the main echo stream. */ + glk_window_set_echo_stream (gln_main_window, NULL); + + /* Confirm action. */ + gln_normal_string ("Glk transcript is now "); + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } + else + { + /* Note that scripts are already disabled. */ + gln_normal_string ("Glk transcript is already "); + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } + } + else if (strlen (argument) == 0) + { + /* + * There was no argument on the line, so print out the current + * transcript mode. + */ + gln_normal_string ("Glk transcript is "); + if (gln_transcript_stream != NULL) + gln_normal_string (GLN_COMMAND_ON); + else + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } + else + { + /* + * The command argument isn't a valid one, so print a list of + * valid command arguments. + */ + gln_normal_string ("Glk transcript can be "); + gln_standout_string (GLN_COMMAND_ON); + gln_normal_string (", or "); + gln_standout_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } +} + + + +/* + * gln_command_inputlog() + * + * Turn game input logging on and off. + */ +static void +gln_command_inputlog (const char *argument) +{ + assert (argument != NULL); + + /* Set up an input log according to the argument given. */ + if (!gln_strcasecmp (argument, GLN_COMMAND_ON)) + { + frefid_t fileref; /* Glk file reference. */ + + /* See if an input log is already active. */ + if (gln_inputlog_stream != NULL) + { + gln_normal_string ("Glk input logging is already "); + gln_normal_string (GLN_COMMAND_ON); + gln_normal_string (".\n"); + return; + } + + /* Get a Glk file reference for an input log. */ + fileref = glk_fileref_create_by_prompt + (fileusage_InputRecord | fileusage_BinaryMode, + filemode_WriteAppend, 0); + if (fileref == NULL) + { + gln_standout_string ("Glk input logging failed.\n"); + return; + } + + /* Open a Glk stream for the fileref. */ + gln_inputlog_stream = glk_stream_open_file + (fileref, filemode_WriteAppend, 0); + if (gln_inputlog_stream == NULL) + { + glk_fileref_destroy (fileref); + gln_standout_string ("Glk input logging failed.\n"); + return; + } + glk_fileref_destroy (fileref); + + /* Confirm action. */ + gln_normal_string ("Glk input logging is now "); + gln_normal_string (GLN_COMMAND_ON); + gln_normal_string (".\n"); + } + else if (!gln_strcasecmp (argument, GLN_COMMAND_OFF)) + { + /* If an input log is active, close it. */ + if (gln_inputlog_stream != NULL) + { + glk_stream_close (gln_inputlog_stream, NULL); + gln_inputlog_stream = NULL; + + /* Confirm action. */ + gln_normal_string ("Glk input log is now "); + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } + else + { + /* Note that there is no current input log. */ + gln_normal_string ("Glk input logging is already "); + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } + } + else if (strlen (argument) == 0) + { + /* + * There was no argument on the line, so print out the current + * input logging mode. + */ + gln_normal_string ("Glk input logging is "); + if (gln_inputlog_stream != NULL) + gln_normal_string (GLN_COMMAND_ON); + else + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } + else + { + /* + * The command argument isn't a valid one, so print a list of + * valid command arguments. + */ + gln_normal_string ("Glk input logging can be "); + gln_standout_string (GLN_COMMAND_ON); + gln_normal_string (", or "); + gln_standout_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } +} + + +/* + * gln_command_readlog() + * + * Set the game input log, to read input from a file. + */ +static void +gln_command_readlog (const char *argument) +{ + assert (argument != NULL); + + /* Set up a read log according to the argument given. */ + if (!gln_strcasecmp (argument, GLN_COMMAND_ON)) + { + frefid_t fileref; /* Glk file reference. */ + + /* See if a read log is already active. */ + if (gln_readlog_stream != NULL) + { + gln_normal_string ("Glk read log is already "); + gln_normal_string (GLN_COMMAND_ON); + gln_normal_string (".\n"); + return; + } + + /* Get a Glk file reference for a read log. */ + fileref = glk_fileref_create_by_prompt + (fileusage_InputRecord | fileusage_BinaryMode, + filemode_Read, 0); + if (fileref == NULL) + { + gln_standout_string ("Glk read log failed.\n"); + return; + } + + /* + * Reject the file reference if we're expecting to read + * from it, and the referenced file doesn't exist. + */ + if (!glk_fileref_does_file_exist (fileref)) + { + glk_fileref_destroy (fileref); + gln_standout_string ("Glk read log failed.\n"); + return; + } + + /* Open a Glk stream for the fileref. */ + gln_readlog_stream = glk_stream_open_file + (fileref, filemode_Read, 0); + if (gln_readlog_stream == NULL) + { + glk_fileref_destroy (fileref); + gln_standout_string ("Glk read log failed.\n"); + return; + } + glk_fileref_destroy (fileref); + + /* Confirm action. */ + gln_normal_string ("Glk read log is now "); + gln_normal_string (GLN_COMMAND_ON); + gln_normal_string (".\n"); + } + else if (!gln_strcasecmp (argument, GLN_COMMAND_OFF)) + { + /* If a read log is active, close it. */ + if (gln_readlog_stream != NULL) + { + glk_stream_close (gln_readlog_stream, NULL); + gln_readlog_stream = NULL; + + /* Confirm action. */ + gln_normal_string ("Glk read log is now "); + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } + else + { + /* Note that there is no current read log. */ + gln_normal_string ("Glk read log is already "); + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } + } + else if (strlen (argument) == 0) + { + /* + * There was no argument on the line, so print out the current + * read logging mode. + */ + gln_normal_string ("Glk read log is "); + if (gln_readlog_stream != NULL) + gln_normal_string (GLN_COMMAND_ON); + else + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } + else + { + /* + * The command argument isn't a valid one, so print a list of + * valid command arguments. + */ + gln_normal_string ("Glk read log can be "); + gln_standout_string (GLN_COMMAND_ON); + gln_normal_string (", or "); + gln_standout_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } +} + + +/* + * gln_command_abbreviations() + * + * Turn abbreviation expansions on and off. + */ +static void +gln_command_abbreviations (const char *argument) +{ + assert (argument != NULL); + + /* Set up abbreviation expansions according to the argument given. */ + if (!gln_strcasecmp (argument, GLN_COMMAND_ON)) + { + /* See if expansions are already on. */ + if (gln_abbreviations_enabled) + { + gln_normal_string ("Glk abbreviation expansions" + " are already "); + gln_normal_string (GLN_COMMAND_ON); + gln_normal_string (".\n"); + return; + } + + /* The user has turned expansions on. */ + gln_abbreviations_enabled = TRUE; + gln_normal_string ("Glk abbreviation expansions are now "); + gln_normal_string (GLN_COMMAND_ON); + gln_normal_string (".\n"); + } + else if (!gln_strcasecmp (argument, GLN_COMMAND_OFF)) + { + /* See if expansions are already off. */ + if (!gln_abbreviations_enabled) + { + gln_normal_string ("Glk abbreviation expansions" + " are already "); + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + return; + } + + /* The user has turned expansions off. */ + gln_abbreviations_enabled = FALSE; + gln_normal_string ("Glk abbreviation expansions are now "); + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } + else if (strlen (argument) == 0) + { + /* + * There was no argument on the line, so print out the current + * expansion mode. + */ + gln_normal_string ("Glk abbreviation expansions are "); + if (gln_abbreviations_enabled) + gln_normal_string (GLN_COMMAND_ON); + else + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } + else + { + /* + * The command argument isn't a valid one, so print a list of + * valid command arguments. + */ + gln_normal_string ("Glk abbreviation expansions can be "); + gln_standout_string (GLN_COMMAND_ON); + gln_normal_string (", or "); + gln_standout_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } +} + + +/* + * gln_command_print_version_number() + * gln_command_version() + * + * Print out the Glk library version number. + */ +static void +gln_command_print_version_number (glui32 version) +{ + char buffer[64]; /* Output buffer string. */ + + /* Print out the three version number component parts. */ + sprintf (buffer, "%lu.%lu.%lu", + (version & 0xFFFF0000) >> 16, + (version & 0x0000FF00) >> 8, + (version & 0x000000FF) ); + gln_normal_string (buffer); +} +static void +gln_command_version (const char *argument) +{ + glui32 version; /* Glk lib version number. */ + + /* Get the Glk library version number. */ + version = glk_gestalt (gestalt_Version, 0); + + /* Print the Glk library and port version numbers. */ + gln_normal_string ("The Glk library version is "); + gln_command_print_version_number (version); + gln_normal_string (".\n"); + gln_normal_string ("This is version "); + gln_command_print_version_number (GLN_PORT_VERSION); + gln_normal_string (" of the Glk Level9 port.\n"); +} + + +/* + * gln_command_loopchecks() + * + * Turn loop checking (for game infinite loops) on and off. + */ +static void +gln_command_loopchecks (const char *argument) +{ + assert (argument != NULL); + + /* Set up loopcheck according to the argument given. */ + if (!gln_strcasecmp (argument, GLN_COMMAND_ON)) + { + /* See if loop checks are already on. */ + if (gln_loopcheck_enabled) + { + gln_normal_string ("Glk loop detection" + " is already "); + gln_normal_string (GLN_COMMAND_ON); + gln_normal_string (".\n"); + return; + } + + /* The user has turned loop checking on. */ + gln_loopcheck_enabled = TRUE; + gln_normal_string ("Glk loop detection is now "); + gln_normal_string (GLN_COMMAND_ON); + gln_normal_string (".\n"); + } + else if (!gln_strcasecmp (argument, GLN_COMMAND_OFF)) + { + /* See if loop checks are already off. */ + if (!gln_loopcheck_enabled) + { + gln_normal_string ("Glk loop detection" + " is already "); + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + return; + } + + /* The user has turned loop checks off. */ + gln_loopcheck_enabled = FALSE; + gln_normal_string ("Glk loop detection is now "); + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } + else if (strlen (argument) == 0) + { + /* + * There was no argument on the line, so print out the current + * loop check mode. + */ + gln_normal_string ("Glk loop detection is "); + if (gln_loopcheck_enabled) + gln_normal_string (GLN_COMMAND_ON); + else + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } + else + { + /* + * The command argument isn't a valid one, so print a list of + * valid command arguments. + */ + gln_normal_string ("Glk loop detection can be "); + gln_standout_string (GLN_COMMAND_ON); + gln_normal_string (", or "); + gln_standout_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } +} + + +/* + * gln_command_locals() + * + * Turn local interpretation of "quit" etc. on and off. + */ +static void +gln_command_locals (const char *argument) +{ + assert (argument != NULL); + + /* Set up local commands according to the argument given. */ + if (!gln_strcasecmp (argument, GLN_COMMAND_ON)) + { + /* See if local commands are already on. */ + if (gln_intercept_enabled) + { + gln_normal_string ("Glk local commands" + " are already "); + gln_normal_string (GLN_COMMAND_ON); + gln_normal_string (".\n"); + return; + } + + /* The user has turned local commands on. */ + gln_intercept_enabled = TRUE; + gln_normal_string ("Glk local commands are now "); + gln_normal_string (GLN_COMMAND_ON); + gln_normal_string (".\n"); + } + else if (!gln_strcasecmp (argument, GLN_COMMAND_OFF)) + { + /* See if local commands are already off. */ + if (!gln_intercept_enabled) + { + gln_normal_string ("Glk local commands" + " are already "); + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + return; + } + + /* The user has turned local commands off. */ + gln_intercept_enabled = FALSE; + gln_normal_string ("Glk local commands are now "); + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } + else if (strlen (argument) == 0) + { + /* + * There was no argument on the line, so print out the current + * local command mode. + */ + gln_normal_string ("Glk local commands are "); + if (gln_intercept_enabled) + gln_normal_string (GLN_COMMAND_ON); + else + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } + else + { + /* + * The command argument isn't a valid one, so print a list of + * valid command arguments. + */ + gln_normal_string ("Glk local commands can be "); + gln_standout_string (GLN_COMMAND_ON); + gln_normal_string (", or "); + gln_standout_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } +} + + +/* + * gln_command_prompts() + * + * Turn the extra "> " prompt output on and off. + */ +static void +gln_command_prompts (const char *argument) +{ + assert (argument != NULL); + + /* Set up prompt according to the argument given. */ + if (!gln_strcasecmp (argument, GLN_COMMAND_ON)) + { + /* See if prompt is already on. */ + if (gln_prompt_enabled) + { + gln_normal_string ("Glk extra prompts are already "); + gln_normal_string (GLN_COMMAND_ON); + gln_normal_string (".\n"); + return; + } + + /* The user has turned prompt on. */ + gln_prompt_enabled = TRUE; + gln_normal_string ("Glk extra prompts are now "); + gln_normal_string (GLN_COMMAND_ON); + gln_normal_string (".\n"); + + /* Check for a game prompt to clear the flag. */ + gln_game_prompted (); + } + else if (!gln_strcasecmp (argument, GLN_COMMAND_OFF)) + { + /* See if prompt is already off. */ + if (!gln_prompt_enabled) + { + gln_normal_string ("Glk extra prompts are already "); + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + return; + } + + /* The user has turned prompt off. */ + gln_prompt_enabled = FALSE; + gln_normal_string ("Glk extra prompts are now "); + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } + else if (strlen (argument) == 0) + { + /* + * There was no argument on the line, so print out the current + * extra prompt mode. + */ + gln_normal_string ("Glk extra prompts are "); + if (gln_prompt_enabled) + gln_normal_string (GLN_COMMAND_ON); + else + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } + else + { + /* + * The command argument isn't a valid one, so print a list of + * valid command arguments. + */ + gln_normal_string ("Glk extra prompts can be "); + gln_standout_string (GLN_COMMAND_ON); + gln_normal_string (", or "); + gln_standout_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } +} + + +/* + * gln_command_commands() + * + * Turn command escapes off. Once off, there's no way to turn them back on. + * Commands must be on already to enter this function. + */ +static void +gln_command_commands (const char *argument) +{ + assert (argument != NULL); + + /* Set up command handling according to the argument given. */ + if (!gln_strcasecmp (argument, GLN_COMMAND_ON)) + { + /* Commands must already be on. */ + gln_normal_string ("Glk commands are already "); + gln_normal_string (GLN_COMMAND_ON); + gln_normal_string (".\n"); + } + else if (!gln_strcasecmp (argument, GLN_COMMAND_OFF)) + { + /* The user has turned commands off. */ + gln_commands_enabled = FALSE; + gln_normal_string ("Glk commands are now "); + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } + else if (strlen (argument) == 0) + { + /* + * There was no argument on the line, so print out the current + * command mode. + */ + gln_normal_string ("Glk commands are "); + if (gln_commands_enabled) + gln_normal_string (GLN_COMMAND_ON); + else + gln_normal_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } + else + { + /* + * The command argument isn't a valid one, so print a list of + * valid command arguments. + */ + gln_normal_string ("Glk commands can be "); + gln_standout_string (GLN_COMMAND_ON); + gln_normal_string (", or "); + gln_standout_string (GLN_COMMAND_OFF); + gln_normal_string (".\n"); + } +} + + +/* Escape introducer string, and special intercepted commands. */ +static const char *GLN_COMMAND_ESCAPE = "glk"; +static const char *GLN_COMMAND_QUIT = "quit"; +static const char *GLN_COMMAND_RESTART = "restart"; +static const char *GLN_COMMAND_SAVE = "save"; +static const char *GLN_COMMAND_RESTORE = "restore"; +static const char *GLN_COMMAND_LOAD = "load"; + +/* Small table of Glk subcommands and handler functions. */ +struct gln_command { + const char *command; /* Glk subcommand. */ + void (*handler) (const char *argument); + /* Subcommand handler. */ + const int takes_argument; /* Argument flag. */ +}; +typedef const struct gln_command* gln_commandref_t; +static const struct gln_command GLN_COMMAND_TABLE[] = { + { "script", gln_command_script, TRUE }, + { "inputlog", gln_command_inputlog, TRUE }, + { "readlog", gln_command_readlog, TRUE }, + { "abbreviations", gln_command_abbreviations, TRUE }, + { "loopchecks", gln_command_loopchecks, TRUE }, + { "locals", gln_command_locals, TRUE }, + { "prompts", gln_command_prompts, TRUE }, + { "version", gln_command_version, FALSE }, + { "commands", gln_command_commands, TRUE }, + { NULL, NULL, FALSE } +}; + +/* List of whitespace command-argument separator characters. */ +static const char *GLN_COMMAND_WHITESPACE = "\t "; + + +/* + * gln_command_dispatch() + * + * Given a command string and an argument, this function finds and runs any + * handler for it. + * + * It searches for the first unambiguous command to match the string passed + * in. The return is the count of command matches found; zero represents no + * match (fail), one represents an unambiguous match (success, and handler + * run), and more than one represents an ambiguous match (fail). + */ +static int +gln_command_dispatch (const char *command, const char *argument) +{ + gln_commandref_t entry; /* Table search entry. */ + gln_commandref_t matched; /* Matched table entry. */ + int matches; /* Count of command matches. */ + assert (command != NULL && argument != NULL); + + /* + * Search for the first unambiguous table command string matching + * the command passed in. + */ + matches = 0; + matched = NULL; + for (entry = GLN_COMMAND_TABLE; + entry->command != NULL; entry++) + { + if (!gln_strncasecmp (command, + entry->command, strlen (command))) + { + matches++; + matched = entry; + } + } + + /* If the match was unambiguous, call the command handler. */ + if (matches == 1) + { + gln_normal_char ('\n'); + (matched->handler) (argument); + + /* Issue a brief warning if an argument was ignored. */ + if (!matched->takes_argument && strlen (argument) > 0) + { + gln_normal_string ("[The "); + gln_standout_string (matched->command); + gln_normal_string (" command ignores arguments.]\n"); + } + } + + /* Return the count of matching table entries. */ + return matches; +} + + +/* + * gln_command_usage() + * + * On an empty, invalid, or ambiguous command, print out a list of valid + * commands and perhaps some Glk status information. + */ +static void +gln_command_usage (const char *command, int is_ambiguous) +{ + gln_commandref_t entry; /* Command table iteration entry. */ + assert (command != NULL); + + /* Print a blank line separator. */ + gln_normal_char ('\n'); + + /* If the command isn't empty, indicate ambiguous or invalid. */ + if (strlen (command) > 0) + { + gln_normal_string ("The Glk command "); + gln_standout_string (command); + if (is_ambiguous) + gln_normal_string (" is ambiguous.\n"); + else + gln_normal_string (" is not valid.\n"); + } + + /* Print out a list of valid commands. */ + gln_normal_string ("Glk commands are"); + for (entry = GLN_COMMAND_TABLE; entry->command != NULL; entry++) + { + gln_commandref_t next; /* Next command table entry. */ + + next = entry + 1; + gln_normal_string (next->command != NULL ? " " : " and "); + gln_standout_string (entry->command); + gln_normal_string (next->command != NULL ? "," : ".\n"); + } + + /* Write a note about abbreviating commands. */ + gln_normal_string ("Glk commands may be abbreviated, as long as"); + gln_normal_string (" the abbreviations are unambiguous.\n"); + + /* + * If no command was given, call each command handler function with + * an empty argument to prompt each to report the current setting. + */ + if (strlen (command) == 0) + { + gln_normal_char ('\n'); + for (entry = GLN_COMMAND_TABLE; + entry->command != NULL; entry++) + (entry->handler) (""); + } +} + + +/* + * gln_skip_characters() + * + * Helper function for command escapes. Skips over either whitespace or + * non-whitespace in string, and returns the revised string pointer. + */ +static char * +gln_skip_characters (char *string, int skip_whitespace) +{ + char *result; /* Return string pointer. */ + assert (string != NULL); + + /* Skip over leading characters of the specified type. */ + for (result = string; *result != '\0'; result++) + { + int is_whitespace; /* Whitespace flag. */ + + /* Break if encountering a character not the required type. */ + is_whitespace = + (strchr (GLN_COMMAND_WHITESPACE, *result) != NULL); + if ((skip_whitespace && !is_whitespace) + || (!skip_whitespace && is_whitespace)) + break; + } + + /* Return the revised pointer. */ + return result; +} + + +/* + * gln_command_escape() + * + * This function is handed each input line. If the line contains a specific + * Glk port command, handle it and return TRUE, otherwise return FALSE. + */ +static int +gln_command_escape (char *string) +{ + char *temporary; /* Temporary string pointer */ + char *string_copy; /* Destroyable string copy. */ + char *command; /* Glk subcommand. */ + char *argument; /* Glk subcommand argument. */ + int matches; /* Dispatcher matches. */ + assert (string != NULL); + + /* + * Return FALSE if the string doesn't begin with the Glk command + * escape introducer. + */ + temporary = gln_skip_characters (string, TRUE); + if (gln_strncasecmp (temporary, GLN_COMMAND_ESCAPE, + strlen (GLN_COMMAND_ESCAPE))) + return FALSE; + + /* Take a copy of the string, without any leading space. */ + string_copy = gln_malloc (strlen (temporary) + 1); + strcpy (string_copy, temporary); + + /* Find the subcommand; the word after the introducer. */ + command = gln_skip_characters (string_copy + + strlen (GLN_COMMAND_ESCAPE), TRUE); + + /* Skip over command word, be sure it terminates with NUL. */ + temporary = gln_skip_characters (command, FALSE); + if (*temporary != '\0') + { + *temporary = '\0'; + temporary++; + } + + /* Now find any argument data for the command. */ + argument = gln_skip_characters (temporary, TRUE); + + /* Ensure that argument data also terminates with a NUL. */ + temporary = gln_skip_characters (argument, FALSE); + *temporary = '\0'; + + /* + * Try to handle the command and argument as a Glk subcommand. If + * it doesn't run unambiguously, print command usage. + */ + matches = gln_command_dispatch (command, argument); + if (matches != 1) + { + if (matches == 0) + gln_command_usage (command, FALSE); + else + gln_command_usage (command, TRUE); + } + + /* Done with the copy of the string. */ + free (string_copy); + + /* Return TRUE to indicate string contained a Glk command. */ + return TRUE; +} + + +/* + * gln_command_intercept() + * + * The Level 9 games handle the commands "quit" and "restart" oddly, and + * somewhat similarly. Both prompt "Press SPACE to play again", and + * then ignore all characters except space. This makes it especially + * hard to exit from a game without killing the interpreter process. + * It also handles "restore" via an odd security mechanism which has no + * real place here (the base Level9 interpreter sidesteps this with its + * "#restore" command), and has some bugs in "save". + * + * To try to improve these, here we'll catch and special case the input + * lines "quit", "save", "restore", and "restart". "Load" is a synonym + * for "restore". + * + * On "quit" or "restart", the function sets the interpreter stop reason + * code, stops the current game run. On "save" or "restore" it calls the + * appropriate internal interpreter function. + * + * The return value is TRUE if an intercepted command was found, otherwise + * FALSE. + */ +static int +gln_command_intercept (char *string) +{ + char *first, *trailing; /* Start and end of the first word */ + char *end; /* Skipped spaces after first word */ + assert (string != NULL); + + /* + * Find the first significant character in string, and the space + * or NUL after the first word. + */ + first = gln_skip_characters (string, TRUE); + trailing = gln_skip_characters (first, FALSE); + + /* + * Find the first character, or NUL, following the whitespace + * after the first word. + */ + end = gln_skip_characters (trailing, TRUE); + + /* Forget it if string isn't a single word only. */ + if (strlen (end) != 0) + return FALSE; + + /* If this command was "quit", confirm, then call StopGame(). */ + if (!gln_strncasecmp (first, GLN_COMMAND_QUIT, + strlen (GLN_COMMAND_QUIT)) + && first + strlen (GLN_COMMAND_QUIT) == trailing) + { + /* Confirm quit just as a Level 9 game does. */ + if (gln_confirm ("\nDo you really want to stop? [Y or N] ")) + { + gln_stop_reason = STOP_EXIT; + StopGame (); + } + return TRUE; + } + + /* If this command was "restart", confirm, then call StopGame(). */ + if (!gln_strncasecmp (first, GLN_COMMAND_RESTART, + strlen (GLN_COMMAND_RESTART)) + && first + strlen (GLN_COMMAND_RESTART) == trailing) + { + /* Confirm restart somewhat as a Level 9 game does. */ + if (gln_confirm ("\nDo you really want to restart? [Y or N] ")) + { + gln_stop_reason = STOP_RESTART; + StopGame (); + } + return TRUE; + } + + /* If this command was "save", simply call save(). */ + if (!gln_strncasecmp (first, GLN_COMMAND_SAVE, + strlen (GLN_COMMAND_SAVE)) + && first + strlen (GLN_COMMAND_SAVE) == trailing) + { + /* Print a message and call the level9 internal save. */ + gln_standout_string ("\nSaving using interpreter\n\n"); + save (); + return TRUE; + } + + /* If this command was "restore" or "load", call restore(). */ + if ((!gln_strncasecmp (first, GLN_COMMAND_RESTORE, + strlen (GLN_COMMAND_RESTORE)) + && first + strlen (GLN_COMMAND_RESTORE) == trailing) + || (!gln_strncasecmp (first, GLN_COMMAND_LOAD, + strlen (GLN_COMMAND_LOAD)) + && first + strlen (GLN_COMMAND_LOAD) == trailing)) + { + /* + * Print a message and call the level9 restore. There is no + * need for confirmation since the file selection can be + * canceled. + */ + gln_standout_string ("\nRestoring using interpreter\n\n"); + restore (); + return TRUE; + } + + /* No special buffer contents found. */ + return FALSE; +} + + +/*---------------------------------------------------------------------*/ +/* Glk port input functions */ +/*---------------------------------------------------------------------*/ + +/* Ctrl-C and Ctrl-U character constants. */ +static const char GLN_CONTROL_C = '\003'; +static const char GLN_CONTROL_U = '\025'; + +/* + * os_readchar() call count limit, after which we really read a character. + * Also, call count limit on os_stoplist calls, after which we poll for a + * character press to stop the listing, and a stoplist poll timeout. + */ +static const int GLN_READCHAR_LIMIT = 1024; +static const int GLN_STOPLIST_LIMIT = 10; +static const glui32 GLN_STOPLIST_TIMEOUT = 50; + +/* Quote used to suppress abbreviation expansion and local commands. */ +static const char GLN_QUOTED_INPUT = '\''; + +/* Definition of whitespace characters to skip over. */ +static const char *GLN_WHITESPACE = "\t "; + +/* + * Note of when the interpreter is in list output. The last element of + * any list generally lacks a terminating newline, and unless we do + * something special with it, it'll look like a valid prompt to us. + */ +static int gln_inside_list = FALSE; + + +/* + * gln_char_is_whitespace() + * + * Check for ASCII whitespace characters. Returns TRUE if the character + * qualifies as whitespace (NUL is not whitespace). + */ +static int +gln_char_is_whitespace (char c) +{ + return (c != '\0' && strchr (GLN_WHITESPACE, c) != NULL); +} + + +/* + * gln_skip_leading_whitespace() + * + * Skip over leading whitespace, returning the address of the first non- + * whitespace character. + */ +static char * +gln_skip_leading_whitespace (char *string) +{ + char *result; /* Return string pointer. */ + assert (string != NULL); + + /* Move result over leading whitespace. */ + for (result = string; gln_char_is_whitespace (*result); ) + result++; + return result; +} + + +/* Table of single-character command abbreviations. */ +struct gln_abbreviation { + const char abbreviation; /* Abbreviation character. */ + const char *expansion; /* Expansion string. */ +}; +typedef const struct gln_abbreviation* gln_abbreviationref_t; +static const struct gln_abbreviation GLN_ABBREVIATIONS[] = { + { 'c', "close" }, { 'g', "again" }, { 'i', "inventory" }, + { 'k', "attack" }, { 'l', "look" }, { 'p', "open" }, + { 'q', "quit" }, { 'r', "drop" }, { 't', "take" }, + { 'x', "examine" }, { 'y', "yes" }, { 'z', "wait" }, + { '\0', NULL } +}; + +/* + * gln_expand_abbreviations() + * + * Expand a few common one-character abbreviations commonly found in other + * game systems, but not always normal in Magnetic Scrolls games. + */ +static void +gln_expand_abbreviations (char *buffer, int size) +{ + char *command; /* Single-character command. */ + gln_abbreviationref_t + entry, match; /* Table search entry, and match. */ + assert (buffer != NULL); + + /* Skip leading spaces to find command, and return if nothing. */ + command = gln_skip_leading_whitespace (buffer); + if (strlen (command) == 0) + return; + + /* If the command is not a single letter one, do nothing. */ + if (strlen (command) > 1 + && !gln_char_is_whitespace (command[1])) + return; + + /* Scan the abbreviations table for a match. */ + match = NULL; + for (entry = GLN_ABBREVIATIONS; entry->expansion != NULL; entry++) + { + if (entry->abbreviation == glk_char_to_lower + ((unsigned char) command[0])) + { + match = entry; + break; + } + } + if (match == NULL) + return; + + /* Match found, check for a fit. */ + if (strlen (buffer) + strlen (match->expansion) - 1 >= size) + return; + + /* Replace the character with the full string. */ + memmove (command + strlen (match->expansion) - 1, + command, strlen (command) + 1); + memcpy (command, match->expansion, strlen (match->expansion)); + + /* Provide feedback on the expansion. */ + gln_standout_string ("["); + gln_standout_char (match->abbreviation); + gln_standout_string (" -> "); + gln_standout_string (match->expansion); + gln_standout_string ("]\n"); +} + + +/* + * gln_output_endlist() + * + * The core interpreter doesn't terminate lists with a newline, so we take + * care of that here; a fixup for input functions. + */ +static void +gln_output_endlist (void) +{ + /* Ignore the call if not inside a list. */ + if (gln_inside_list) + { + /* + * Supply the missing newline, using os_printchar() so that + * list output doesn't look like a prompt when we come to + * flush it. + */ + os_printchar ('\n'); + + /* Clear the list indicator. */ + gln_inside_list = FALSE; + } +} + + +/* + * os_input() + * + * Read a line from the keyboard. This function makes a special case of + * some command strings, and will also perform abbreviation expansion. + */ +L9BOOL +os_input (char *buffer, int size) +{ + event_t event; /* Glk event buffer. */ + assert (buffer != NULL); + + /* Flush any pending buffered output, terminating any open list. */ + gln_output_endlist (); + gln_output_flush (); + + /* + * Level 9 games tend not to issue a prompt after reading an empty + * line of input, and the Adrian Mole games don't issue a prompt at + * all when outside the 1/2/3 menuing system. This can make for a + * very blank looking screen. + * + * To slightly improve things, if it looks like we didn't get a + * prompt from the game, do our own. + */ + if (gln_prompt_enabled + && !gln_game_prompted ()) + { + gln_normal_char ('\n'); + gln_normal_string (GLN_INPUT_PROMPT); + } + + /* + * If we have an input log to read from, use that until it is + * exhausted. On end of file, close the stream and resume input + * from line requests. + */ + if (gln_readlog_stream != NULL) + { + glui32 chars; /* Characters read. */ + + /* Get the next line from the log stream. */ + chars = glk_get_line_stream + (gln_readlog_stream, buffer, size); + if (chars > 0) + { + /* Echo the line just read in input style. */ + glk_set_style (style_Input); + glk_put_buffer (buffer, chars); + glk_set_style (style_Normal); + + /* Tick the watchdog, and return. */ + gln_watchdog_tick (); + return TRUE; + } + + /* + * We're at the end of the log stream. Close it, and then + * continue on to request a line from Glk. + */ + glk_stream_close (gln_readlog_stream, NULL); + gln_readlog_stream = NULL; + } + + /* Set up the read buffer for the main window, and wait. */ + glk_request_line_event (gln_main_window, buffer, size - 1, 0); + gln_event_wait (evtype_LineInput, &event); + + /* Terminate the input line with a NUL. */ + assert (event.val1 <= size - 1); + buffer[ event.val1 ] = '\0'; + + /* + * If neither abbreviations nor local commands are enabled, nor + * game command interceptions, then return the data read above + * without further massaging. + */ + if (gln_abbreviations_enabled + || gln_commands_enabled + || gln_intercept_enabled) + { + char *command; /* Command part of buffer. */ + + /* Find the first non-space character in the input buffer. */ + command = gln_skip_leading_whitespace (buffer); + + /* + * If the first non-space input character is a quote, bypass + * all abbreviation expansion and local command recognition, + * and use the unadulterated input, less introductory quote. + */ + if (command[0] == GLN_QUOTED_INPUT) + { + /* Delete the quote with memmove(). */ + memmove (command, command + 1, strlen (command)); + } + else + { + /* Check for, and expand, and abbreviated commands. */ + if (gln_abbreviations_enabled) + gln_expand_abbreviations (buffer, size); + + /* + * Check for Glk port special commands, and if found + * then suppress the interpreter's use of this input. + */ + if (gln_commands_enabled + && gln_command_escape (buffer)) + { + gln_watchdog_tick (); + return FALSE; + } + + /* + * Check for intercepted commands, and if found then + * suppress the interpreter's use of this input. + */ + if (gln_intercept_enabled + && gln_command_intercept (buffer)) + { + gln_watchdog_tick (); + return FALSE; + } + } + } + + /* + * If there is an input log active, log this input string to it. + * Note that by logging here we get any abbreviation expansions but + * we won't log glk special commands, nor any input read from a + * current open input log. + */ + if (gln_inputlog_stream != NULL) + { + glk_put_string_stream (gln_inputlog_stream, buffer); + glk_put_char_stream (gln_inputlog_stream, '\n'); + } + + /* Return TRUE since data buffered. */ + gln_watchdog_tick (); + return TRUE; +} + + +/* + * os_readchar() + * + * Poll the keyboard for characters, and return the character code of any key + * pressed, or 0 if none pressed. + * + * Simple though this sounds, it's tough to do right in a timesharing OS, and + * requires something close to an abuse of Glk. + * + * The initial, tempting, implementation is to wait inside this function for + * a key press, then return the code. Unfortunately, this causes problems in + * the Level9 interpreter. Here's why: the interpreter is a VM emulating a + * single-user microprocessor system. On such a system, it's quite okay for + * code to spin in a loop waiting for a keypress; there's nothing else + * happening on the system, so it can burn CPU. To wait for a keypress, game + * code might first wait for no-keypress (0 from this function), then a + * keypress (non-0), then no-keypress again (and it does indeed seem to do + * just this). If, in os_readchar(), we simply wait for and return key codes, + * we'll never return a 0, so the above wait for a keypress in the game will + * hang forever. + * + * To make matters more complex, some Level 9 games poll for keypresses as a + * way for a user to halt scrolling. For these polls, we really want to + * return 0, otherwise the output grinds to a halt. Moreover, some games even + * use key polling as a crude form of timeout - poll and increment a counter, + * and exit when either os_readchar() returns non-0, or after some 300 or so + * polls. + * + * So, this function MUST return 0 sometimes, and real key codes other times. + * The solution adopted is best described as expedient. Depending on what Glk + * provides in the way of timers, we'll do one of two things: + * + * o If we have timers, we'll set up a timeout, and poll for a key press + * within that timeout. As a way to smooth output for games that use key + * press polling for scroll control, we'll ignore calls until we get two + * in a row without intervening character output. + * + * o If we don't have timers, then we'll return 0 most of the time, and then + * really wait for a key one time out of some number. A game polling for + * keypresses to halt scrolling will probably be to the point where it + * cannot continue without user input at this juncture, and once we've + * rejected a few hundred calls we can now really wait for Glk key press + * event, and avoid a spinning loop. A game using key polling as crude + * timing may, or may not, time out in the calls for which we return 0. + * + * Empirically, this all seems to work. The only odd behaviour is with the + * DEMO mode of Adrian Mole where Glk has no timers, and this is primarily + * because the DEMO mode relies on the delay of keyboard polling for part of + * its effect; on a modern system, the time to call through here is nowhere + * near the time consumed by the original platform. The other point of note + * is that this all means that we can't return characters from any readlog + * with this function; it's timing stuff and it's general polling nature make + * it impossible to connect to readlog, so it just won't work at all with the + * Adrian Mole games, Glk timers or otherwise. + */ +char +os_readchar (int millis) +{ + static int call_count = 0; /* Calls count (no timers). */ + + event_t event; /* Glk event buffer. */ + char character; /* Character read. */ + + /* + * Here's the way we try to emulate keyboard polling for the case of + * no Glk timers. We'll say nothing is pressed for some number of + * consecutive calls, then continue after that number of calls. + */ + if (!gln_timeouts_possible) + { + call_count++; + if (call_count < GLN_READCHAR_LIMIT) + { + /* Call tick as we may be outside an opcode loop. */ + glk_tick (); + gln_watchdog_tick (); + return 0; + } + else + call_count = 0; + } + + /* + * If we have Glk timers, we can smooth game output with games that + * continuously use this input function by pretending that there is + * no keypress if the game printed output since the last call. This + * helps with the Adrian Mole games, which check for a keypress at + * the end of a line as a way to temporarily halt scrolling. + */ + if (gln_timeouts_possible) + { + if (gln_recent_output ()) + { + /* Call tick, and return no keypress. */ + glk_tick (); + gln_watchdog_tick (); + return 0; + } + } + + /* + * Now flush any pending buffered output. We do it here rather + * than earlier as it only needs to be done when we're going to + * request Glk input, and we may have avoided this with the checks + * above. + */ + gln_output_endlist (); + gln_output_flush (); + + /* + * Set up a character event request, and a timeout if the Glk + * library can do them, and wait until one or the other occurs. + * Loop until we read an acceptable ASCII character (if we don't + * time out). + */ + do + { + glk_request_char_event (gln_main_window); + if (gln_timeouts_possible) + { + /* Wait for a character or a timeout event. */ + glk_request_timer_events (millis); + gln_event_wait_2 (evtype_CharInput, + evtype_Timer, &event); + glk_request_timer_events (0); + + /* + * If the event was a timeout, cancel the unfilled + * character request, and return no-keypress value. + */ + if (event.type == evtype_Timer) + { + glk_cancel_char_event (gln_main_window); + gln_watchdog_tick (); + return 0; + } + } + else + { + /* Wait for only character events. */ + gln_event_wait (evtype_CharInput, &event); + } + } + while (event.val1 > UCHAR_MAX && event.val1 != keycode_Return); + + /* Extract the character from the event, converting Return, no echo. */ + character = (event.val1 == keycode_Return) ? '\n' : event.val1; + + /* + * Special case ^U as a way to press a key on a wait, yet return a + * code to the interpreter as if no key was pressed. Useful if + * scrolling stops where there are no Glk timers, to get scrolling + * started again. ^U is always active. + */ + if (character == GLN_CONTROL_U) + { + /* Pretend there was no key press after all. */ + gln_watchdog_tick (); + return 0; + } + + /* + * Special case ^C to quit the program. Without this, there's no + * easy way to exit from a game that never uses os_input(), but + * instead continually uses just os_readchar(). ^C handling can be + * disabled with command line options. + */ + if (gln_intercept_enabled + && character == GLN_CONTROL_C) + { + /* Confirm the request to quit the game. */ + if (gln_confirm ("\n\nDo you really want to stop? [Y or N] ")) + { + gln_stop_reason = STOP_EXIT; + StopGame (); + + /* Pretend there was no key press after all. */ + gln_watchdog_tick (); + return 0; + } + } + + /* + * If there is a transcript stream, send the input to it as a one- + * line string, otherwise it won't be visible in the transcript. + */ + if (gln_transcript_stream != NULL) + { + glk_put_char_stream (gln_transcript_stream, character); + glk_put_char_stream (gln_transcript_stream, '\n'); + } + + /* Return the single character read. */ + gln_watchdog_tick (); + return character; +} + + +/* + * os_stoplist() + * + * This is called from #dictionary listings to poll for a request to stop + * the listing. A check for keypress is usual at this point. However, Glk + * cannot check for keypresses without a delay, which slows listing consid- + * erably, since it also adjusts and renders the display. As a compromise, + * then, we'll check for keypresses on a small percentage of calls, say one + * in ten, which means that listings happen with only a short delay, but + * there's still an opportunity to stop them. + * + * This applies only where the Glk library has timers. Where it doesn't, we + * can't check for keypresses without blocking, so we do no checks at all, + * and let lists always run to completion. + */ +L9BOOL +os_stoplist (void) +{ + static int call_count = 0; /* Calls count (timers only). */ + + event_t event; /* Glk event buffer. */ + int status; /* Confirm status. */ + + /* Note that the interpreter is producing a list. */ + gln_inside_list = TRUE; + + /* + * If there are no Glk timers, then polling for a keypress but + * continuing on if there isn't one is not an option. So flush + * output, return FALSE, and just keep listing on to the end. + */ + if (!gln_timeouts_possible) + { + gln_output_flush (); + gln_watchdog_tick (); + return FALSE; + } + + /* Increment the call count, and return FALSE if under the limit. */ + call_count++; + if (call_count < GLN_STOPLIST_LIMIT) + { + /* Call tick as we may be outside an opcode loop. */ + glk_tick (); + gln_watchdog_tick (); + return FALSE; + } + else + call_count = 0; + + /* + * Flush any pending buffered output, delayed to here in case it's + * avoidable. + */ + gln_output_flush (); + + /* + * Look for a keypress, with a very short timeout in place, in a + * similar way as done for os_readchar() above. + */ + glk_request_char_event (gln_main_window); + glk_request_timer_events (GLN_STOPLIST_TIMEOUT); + gln_event_wait_2 (evtype_CharInput, evtype_Timer, &event); + glk_request_timer_events (0); + + /* + * If the event was a timeout, cancel the unfilled character + * request, and return FALSE to continue listing. + */ + if (event.type == evtype_Timer) + { + glk_cancel_char_event (gln_main_window); + gln_watchdog_tick (); + return FALSE; + } + + /* Keypress detected, so offer to stop listing. */ + assert (event.type == evtype_CharInput); + status = gln_confirm ("\n\nStop listing? [Y or N] "); + + /* + * As we've output a newline, we no longer consider that we're + * inside a list. Clear the flag, and also and prompt detection. + */ + gln_inside_list = FALSE; + gln_game_prompted (); + + /* Return TRUE if stop was confirmed, FALSE to keep listing. */ + gln_watchdog_tick (); + return status; +} + + +/* + * gln_confirm() + * + * Print a confirmation prompt, and read a single input character, taking + * only [YyNn] input. If the character is 'Y' or 'y', return TRUE. + */ +static int +gln_confirm (const char *prompt) +{ + event_t event; /* Glk event buffer. */ + unsigned char response; /* Response character. */ + assert (prompt != NULL); + + /* + * Print the confirmation prompt, in a style that hints that it's + * from the interpreter, not the game. + */ + gln_standout_string (prompt); + + /* Wait for a single 'Y' or 'N' character response. */ + do + { + /* Wait for a standard key, ignoring Glk special keys. */ + do + { + glk_request_char_event (gln_main_window); + gln_event_wait (evtype_CharInput, &event); + } + while (event.val1 > UCHAR_MAX); + response = glk_char_to_upper (event.val1); + } + while (response != 'Y' && response != 'N'); + + /* Echo the confirmation response, and a blank line. */ + glk_set_style (style_Input); + glk_put_string (response == 'Y' ? "Yes" : "No"); + glk_set_style (style_Normal); + glk_put_string ("\n\n"); + + /* Return TRUE if 'Y' was entered. */ + return (response == 'Y'); +} + + +/*---------------------------------------------------------------------*/ +/* Glk port event functions */ +/*---------------------------------------------------------------------*/ + +/* + * gln_event_wait_2() + * gln_event_wait() + * + * Process Glk events until one of the expected type, or types, arrives. + * Return the event of that type. + */ +static void +gln_event_wait_2 (glui32 wait_type_1, glui32 wait_type_2, event_t *event) +{ + assert (event != NULL); + + /* Get events, until one matches one of the requested types. */ + do + { + /* Get next event. */ + glk_select (event); + + /* Handle events of interest locally. */ + switch (event->type) + { + case evtype_Arrange: + case evtype_Redraw: + /* Refresh any sensitive windows on size events. */ + gln_status_redraw (); + break; + } + } + while (event->type != wait_type_1 + && event->type != wait_type_2); +} + +static void +gln_event_wait (glui32 wait_type, event_t *event) +{ + assert (event != NULL); + gln_event_wait_2 (wait_type, evtype_None, event); +} + + +/*---------------------------------------------------------------------*/ +/* Glk port file functions */ +/*---------------------------------------------------------------------*/ + +/* + * os_save_file () + * os_load_file () + * + * Save the current game state to a file, and load a game state. + */ +L9BOOL +os_save_file (L9BYTE *ptr, int bytes) +{ + frefid_t fileref; /* Glk file reference. */ + strid_t stream; /* Glk stream reference. */ + assert (ptr != NULL); + + /* Flush any pending buffered output. */ + gln_output_flush (); + + /* Get a Glk file reference for a game save file. */ + fileref = glk_fileref_create_by_prompt + (fileusage_SavedGame, filemode_Write, 0); + if (fileref == NULL) + { + gln_watchdog_tick (); + return FALSE; + } + + /* Open a Glk stream for the fileref. */ + stream = glk_stream_open_file (fileref, filemode_Write, 0); + if (stream == NULL) + { + glk_fileref_destroy (fileref); + gln_watchdog_tick (); + return FALSE; + } + + /* Write the game state data. */ + glk_put_buffer_stream (stream, ptr, bytes); + + /* Close and destroy the Glk stream and fileref. */ + glk_stream_close (stream, NULL); + glk_fileref_destroy (fileref); + + /* All done. */ + gln_watchdog_tick (); + return TRUE; +} + +L9BOOL +os_load_file (L9BYTE *ptr, int *bytes, int max) +{ + frefid_t fileref; /* Glk file reference. */ + strid_t stream; /* Glk stream reference. */ + assert (ptr != NULL && bytes != NULL); + + /* Flush any pending buffered output. */ + gln_output_flush (); + + /* Get a Glk file reference for a game save file. */ + fileref = glk_fileref_create_by_prompt + (fileusage_SavedGame, filemode_Read, 0); + if (fileref == NULL) + { + gln_watchdog_tick (); + return FALSE; + } + + /* + * Reject the file reference if we're expecting to read from it, + * and the referenced file doesn't exist. + */ + if (!glk_fileref_does_file_exist (fileref)) + { + glk_fileref_destroy (fileref); + gln_watchdog_tick (); + return FALSE; + } + + /* Open a Glk stream for the fileref. */ + stream = glk_stream_open_file (fileref, filemode_Read, 0); + if (stream == NULL) + { + glk_fileref_destroy (fileref); + gln_watchdog_tick (); + return FALSE; + } + + /* Read back the game state data. */ + *bytes = glk_get_buffer_stream (stream, ptr, max); + + /* Close and destroy the Glk stream and fileref. */ + glk_stream_close (stream, NULL); + glk_fileref_destroy (fileref); + + /* All done. */ + gln_watchdog_tick (); + return TRUE; +} + + +/*---------------------------------------------------------------------*/ +/* Glk port multi-file game functions */ +/*---------------------------------------------------------------------*/ + +/* File path delimiter, used to be #defined in v2 interpreter. */ +#if defined(_Windows) || defined(__MSDOS__) || defined (_WIN32) \ + || defined (__WIN32__) +static const char GLN_FILE_DELIM = '\\'; +#else +static const char GLN_FILE_DELIM = '/'; +#endif + +/* + * os_get_game_file () + * + * This function is a bit of a cheat. It's called when the emulator has + * detected a request from the game to restart the tape, on a tape-based + * game. Ordinarily, we should prompt the player for the name of the + * system file containing the next game part. Unfortunately, Glk doesn't + * make this at all easy. The requirement is to return a filename, but Glk + * hides these inside fileref_t's, and won't let them out. + * + * Theoretically, according to the porting guide, this function should + * prompt the user for a new game file name, that being the next part of the + * game just (presumably) completed. + * + * However, the newname passed in is always the current game file name, as + * level9.c ensures this for us. If we search for, and find, and then inc- + * rement, the last digit in the filename passed in, we wind up with, in + * all likelihood, the right file path. This is icky. + * + * This function is likely to be a source of portability problems on + * platforms that don't implement a file path/name mechanism that matches + * the expectations of the Level9 base interpreter fairly closely. + */ +L9BOOL +os_get_game_file (char *newname, int size) +{ + char *basename; /* Base of newname. */ + int index; /* Char iterator. */ + int digit; /* Digit index. */ + int file_number; /* Filename's number. */ + FILE *stream; /* Test open stream. */ + assert (newname != NULL); + + /* Find the last element of the filename passed in. */ + basename = strrchr (newname, GLN_FILE_DELIM); + if (basename == NULL) + basename = newname; + else + basename++; + + /* Search for the last numeric character in the basename. */ + digit = -1; + for (index = strlen (basename) - 1; index >= 0; index--) + { + if (isdigit (basename[ index ])) + { + digit = index; + break; + } + } + if (digit == -1) + { + /* No numeric character, but still note watchdog tick. */ + gln_watchdog_tick (); + return FALSE; + } + + /* Convert the digit and increment it. */ + file_number = basename[ digit ] - '0' + 1; + + /* Refuse values outside of the range 1 to 9. */ + if (file_number < 1 || file_number > 9) + { + gln_watchdog_tick (); + return FALSE; + } + + /* Write the new number back into the file. */ + basename[ digit ] = file_number + '0'; + + /* Flush pending output, then display the filename generated. */ + gln_output_flush (); + gln_game_prompted (); + gln_standout_string ("\nNext load file: "); + gln_standout_string (basename); + gln_standout_string ("\n\n"); + + /* + * Try to confirm access to the file. Otherwise, if we return TRUE + * but the interpreter can't open the file, it stops the game, and + * we then lose any chance to save it before quitting. + */ + stream = fopen (newname, "rb"); + if (stream == NULL) + { + /* Restore newname to how it was, and return fail. */ + basename[ digit ] = file_number - 1 + '0'; + gln_watchdog_tick (); + return FALSE; + } + fclose (stream); + + /* Return success. */ + gln_watchdog_tick (); + return TRUE; +} + + +/* + * os_set_filenumber() + * + * This function returns the next file in a game series for a disk-based + * game (typically, gamedat1.dat, gamedat2.dat...). It finds a single digit + * in a filename, and resets it to the new value passed in. The implemen- + * tation here is based on the generic interface version, and with the same + * limitations, specifically being limited to file numbers in the range 0 + * to 9, since it works on only the last digit character in the filename + * buffer passed in. + * + * This function may also be a source of portability problems on platforms + * that don't use "traditional" file path schemes. + */ +void +os_set_filenumber (char *newname, int size, int file_number) +{ + char *basename; /* Base of newname. */ + int index; /* Char iterator. */ + int digit; /* Digit index. */ + assert (newname != NULL); + + /* Do nothing if the file number is beyond what we can handle. */ + if (file_number < 0 || file_number > 9) + { + gln_watchdog_tick (); + return; + } + + /* Find the last element of the new filename. */ + basename = strrchr (newname, GLN_FILE_DELIM); + if (basename == NULL) + basename = newname; + else + basename++; + + /* Search for the last numeric character in the basename. */ + digit = -1; + for (index = strlen (basename) - 1; index >= 0; index--) + { + if (isdigit (basename[ index ])) + { + digit = index; + break; + } + } + if (digit == -1) + { + /* No numeric character, but still note watchdog tick. */ + gln_watchdog_tick (); + return; + } + + /* Reset the digit in the file name. */ + basename[ digit ] = file_number + '0'; + + /* Flush pending output, then display the filename generated. */ + gln_output_flush (); + gln_game_prompted (); + gln_standout_string ("\nNext disk file: "); + gln_standout_string (basename); + gln_standout_string ("\n\n"); + + /* Note watchdog tick and return. */ + gln_watchdog_tick (); +} + + +/*---------------------------------------------------------------------*/ +/* Functions intercepted by link-time wrappers */ +/*---------------------------------------------------------------------*/ + +/* + * __wrap_toupper() + * __wrap_tolower() + * + * Wrapper functions around toupper() and tolower(). The Linux linker's + * --wrap option will convert calls to mumble() to __wrap_mumble() if we + * give it the right options. We'll use this feature to translate all + * toupper() and tolower() calls in the interpreter code into calls to + * Glk's versions of these functions. + * + * It's not critical that we do this. If a linker, say a non-Linux one, + * won't do --wrap, then just do without it. It's unlikely that there + * will be much noticeable difference. + */ +int +__wrap_toupper (int ch) +{ + unsigned char uch; + + uch = glk_char_to_upper ((unsigned char) ch); + return (int) uch; +} +int +__wrap_tolower (int ch) +{ + unsigned char lch; + + lch = glk_char_to_lower ((unsigned char) ch); + return (int) lch; +} + + +/*---------------------------------------------------------------------*/ +/* main() and options parsing */ +/*---------------------------------------------------------------------*/ + +/* + * Watchdog timeout -- we'll wait for five seconds of silence from the core + * interpreter before offering to stop the game forcibly, and we'll check + * it every 10,240 opcodes. + */ +static const int GLN_WATCHDOG_TIMEOUT = 5; +static const int GLN_WATCHDOG_FREQUENCY = 10240; + +/* + * The following values need to be passed between the startup_code and main + * functions. + */ +static char *gln_gamefile = NULL; /* Name of game file. */ +static char *gln_game_message = NULL; /* Error message. */ + + +/* + * gln_startup_code() + * gln_main() + * + * Together, these functions take the place of the original main(). + * The first one is called from glkunix_startup_code(), to parse and + * generally handle options. The second is called from glk_main(), and + * does the real work of running the game. + */ +static int +gln_startup_code (int argc, char *argv[]) +{ + int argv_index; /* Argument iterator. */ + + /* Handle command line arguments. */ + for (argv_index = 1; + argv_index < argc && argv[ argv_index ][0] == '-'; argv_index++) + { + if (strcmp (argv[ argv_index ], "-ni") == 0) + { + gln_intercept_enabled = FALSE; + continue; + } + if (strcmp (argv[ argv_index ], "-nc") == 0) + { + gln_commands_enabled = FALSE; + continue; + } + if (strcmp (argv[ argv_index ], "-na") == 0) + { + gln_abbreviations_enabled = FALSE; + continue; + } + if (strcmp (argv[ argv_index ], "-np") == 0) + { + gln_prompt_enabled = FALSE; + continue; + } + if (strcmp (argv[ argv_index ], "-nl") == 0) + { + gln_loopcheck_enabled = FALSE; + continue; + } + return FALSE; + } + + /* + * Get the name of the game file. Since we need this in our call + * from glk_main, we need to keep it in a module static variable. + * If the game file name is omitted, then here we'll set the pointer + * to NULL, and complain about it later in main. Passing the + * message string around like this is a nuisance... + */ + if (argv_index == argc - 1) + { + gln_gamefile = argv[ argv_index ]; + gln_game_message = NULL; + } + else + { + gln_gamefile = NULL; + if (argv_index < argc - 1) + gln_game_message = "More than one game file" + " was given on the command line."; + else + gln_game_message = "No game file was given" + " on the command line."; + } + + /* All startup options were handled successfully. */ + return TRUE; +} + +static void +gln_main (void) +{ + /* Ensure Level9 types have the right sizes. */ + if (sizeof (L9BYTE) != 1 + || sizeof (L9UINT16) != 2 + || sizeof (L9UINT32) != 4) + { + gln_fatal ( "GLK: Types sized incorrectly," + " recompilation is needed"); + glk_exit (); + } + + /* Create the Glk window, and set its stream as the current one. */ + gln_main_window = glk_window_open (0, 0, 0, wintype_TextBuffer, 0); + if (gln_main_window == NULL) + { + gln_fatal ("GLK: Can't open main window"); + glk_exit (); + } + glk_window_clear (gln_main_window); + glk_set_window (gln_main_window); + glk_set_style (style_Normal); + + /* If there's a problem with the game file, complain now. */ + if (gln_gamefile == NULL) + { + assert (gln_game_message != NULL); + gln_header_string ("Glk Level9 Error\n\n"); + gln_normal_string (gln_game_message); + gln_normal_char ('\n'); + glk_exit (); + } + + /* If Glk can't do timers, note that timeouts are not possible. */ + if (glk_gestalt (gestalt_Timer, 0)) + gln_timeouts_possible = TRUE; + else + gln_timeouts_possible = FALSE; + + /* Try to create a one-line status window. We can live without it. */ + gln_status_window = glk_window_open (gln_main_window, + winmethod_Above|winmethod_Fixed, + 1, wintype_TextGrid, 0); + gln_status_redraw (); + + /* + * The main interpreter uses rand(), but never seeds the random + * number generator. This can lead to predictability in games + * that might be better with a little less, so here, we'll seed + * the random number generator ourselves. + */ + srand (time (NULL)); + + /* Repeat this game until no more restarts requested. */ + do + { + int watchdog_counter; /* Watchdog freq counter. */ + + /* Clear the Glk screen. */ + glk_window_clear (gln_main_window); + + /* + * Load the game. At present, the Glk interface does no + * pictures, so the picture file argument is NULL. + */ + errno = 0; + if (!LoadGame (gln_gamefile, NULL)) + { + /* Report the error, including any details in errno. */ + if (gln_status_window != NULL) + glk_window_close (gln_status_window, NULL); + gln_header_string ("Glk Level9 Error\n\n"); + gln_normal_string ("Can't find, open," + " or load game file '"); + gln_normal_string (gln_gamefile); + gln_normal_char ('\''); + if (errno != 0) + { + gln_normal_string (": "); + gln_normal_string (strerror (errno)); + } + gln_normal_char ('\n'); + + /* Free interpreter allocated memory. */ + FreeMemory (); + + /* Nothing more to be done. */ + glk_exit (); + } + + /* Print out a short banner. */ + gln_header_string ("\nLevel 9 Interpreter, version 3.0\n"); + gln_banner_string ("Written by Glen Summers and David Kinder\n" + "Glk interface by Simon Baldwin\n\n"); + + /* + * Set the stop reason indicator to none. A game will then + * exit with a reason if we call StopGame(), or none if it + * exits of its own accord (or with the "#quit" command, say). + */ + gln_stop_reason = STOP_NONE; + + /* Start, or restart, watchdog checking. */ + gln_watchdog_start (GLN_WATCHDOG_TIMEOUT); + watchdog_counter = 0; + + /* Run the game until StopGame called. */ + while (TRUE) + { + /* Execute an opcode - returns FALSE on StopGame. */ + if (!RunGame ()) + break; + glk_tick (); + + /* Check for a possible game infinite loop. */ + watchdog_counter++; + if (watchdog_counter == GLN_WATCHDOG_FREQUENCY) + { + if (gln_watchdog_timeout ()) + { + gln_stop_reason = STOP_FORCE; + StopGame (); + break; + } + watchdog_counter = 0; + } + } + + /* + * Stop watchdog functions, and flush any pending buffered + * output. + */ + gln_watchdog_stop (); + gln_output_flush (); + + /* Free interpreter allocated memory. */ + FreeMemory (); + + /* + * Unset any "stuck" game 'cheating' flag. This can get + * stuck on if exit is forced from the #cheat mode in the + * Adrian Mole games, which otherwise loop infinitely. Un- + * setting the flag here permits restarts; without this, + * the core interpreter remains permanently in silent + * #cheat mode. + */ + Cheating = FALSE; + + /* + * If the stop reason is none, something in the game stopped + * itself, or the user entered "#quit". If the stop reason + * is force, the user terminated because of an apparent inf- + * inite loop. For both of these, offer the choice to restart, + * or not (equivalent to exit). + */ + if (gln_stop_reason == STOP_NONE + || gln_stop_reason == STOP_FORCE) + { + if (gln_stop_reason == STOP_NONE) + gln_standout_string ( + "\nThe game has exited.\n"); + else + gln_standout_string ( + "\nGame exit was forced. The current" + " game state is unrecoverable." + " Sorry.\n"); + if (gln_confirm + ("\nDo you want to restart? [Y or N] ")) + gln_stop_reason = STOP_RESTART; + else + gln_stop_reason = STOP_EXIT; + } + } + while (gln_stop_reason == STOP_RESTART); + + /* Close any open transcript, input log, and/or read log. */ + if (gln_transcript_stream != NULL) + glk_stream_close (gln_transcript_stream, NULL); + if (gln_inputlog_stream != NULL) + glk_stream_close (gln_inputlog_stream, NULL); + if (gln_readlog_stream != NULL) + glk_stream_close (gln_readlog_stream, NULL); +} + + +/*---------------------------------------------------------------------*/ +/* Linkage between Glk entry/exit calls and the real interpreter */ +/*---------------------------------------------------------------------*/ + +/* + * Safety flags, to ensure we always get startup before main, and that + * we only get a call to main once. + */ +static int gln_startup_called = FALSE; +static int gln_main_called = FALSE; + +#if TARGET_OS_MAC +/* Additional Mac variables. */ +static strid_t gln_mac_gamefile = NULL; +static short gln_savedVRefNum = 0; +static long gln_savedDirID = 0; + + +/* + * gln_mac_whenselected() + * gln_mac_whenbuiltin() + * macglk_startup_code() + * + * Startup entry points for Mac versions of Glk interpreter. Glk will + * call macglk_startup_code() for details on what to do when the app- + * lication is selected. On selection, an argv[] vector is built, and + * passed to the normal interpreter startup code, after which, Glk will + * call glk_main(). + */ +static Boolean +gln_mac_whenselected (FSSpec *file, OSType filetype) +{ + static char* argv[2]; + assert (!gln_startup_called); + gln_startup_called = TRUE; + + /* Set the WD to where the file is, so later fopens work. */ + if (HGetVol (0, &gln_savedVRefNum, &gln_savedDirID) != 0) + { + gln_fatal ("GLK: HGetVol failed"); + return FALSE; + } + if (HSetVol (0, file->vRefNum, file->parID) != 0) + { + gln_fatal ("GLK: HSetVol failed"); + return FALSE; + } + + /* Put a CString version of the PString name into argv[1]. */ + argv[1] = gln_malloc (file->name[0] + 1); + BlockMoveData (file->name + 1, argv[1], file->name[0]); + argv[1][file->name[0]] = '\0'; + argv[2] = NULL; + + return gln_startup_code (2, argv); +} + +static Boolean +gln_mac_whenbuiltin (void) +{ + /* Not implemented yet. */ + return TRUE; +} + +Boolean +macglk_startup_code (macglk_startup_t *data) +{ + static OSType gln_mac_gamefile_types[] = { 'LVL9' }; + + data->startup_model = macglk_model_ChooseOrBuiltIn; + data->app_creator = 'cAGL'; + data->gamefile_types = gln_mac_gamefile_types; + data->num_gamefile_types = sizeof (gln_mac_gamefile_types) + / sizeof (*gln_mac_gamefile_types); + data->savefile_type = 'BINA'; + data->datafile_type = 0x3F3F3F3F; + data->gamefile = &gln_mac_gamefile; + data->when_selected = gln_mac_whenselected; + data->when_builtin = gln_mac_whenbuiltin; + /* macglk_setprefs(); */ + return TRUE; +} + + +#else /* not TARGET_OS_MAC */ +/* + * glkunix_startup_code() + * + * Startup entry point for UNIX versions of Glk interpreter. Glk will + * call glkunix_startup_code() to pass in arguments. On startup, we call + * our function to parse arguments and generally set stuff up. + */ +int +glkunix_startup_code (glkunix_startup_t *data) +{ + assert (!gln_startup_called); + gln_startup_called = TRUE; + + return gln_startup_code (data->argc, data->argv); +} +#endif /* TARGET_OS_MAC */ + + +/* + * glk_main() + * + * Main entry point for Glk. Here, all startup is done, and we call our + * function to run the game. + */ +void +glk_main (void) +{ + assert (gln_startup_called && !gln_main_called); + gln_main_called = TRUE; + + /* Call the interpreter main function. */ + gln_main (); +} diff --git a/Glk/glk_readme.txt b/Glk/glk_readme.txt new file mode 100755 index 0000000..933d807 --- /dev/null +++ b/Glk/glk_readme.txt @@ -0,0 +1,310 @@ +Level 9 Interpreter, Glk Notes +------------------------------ + + +Introduction +------------ + +This is a port of the Level 9 interpreter to Glk. The complete interface +lives in the single file + + glk.c + +The main test and development system for the port is Linux, with Xglk as the +Glk library. + + +Acknowledgements +---------------- + +Thanks to Alan Staniforth for considerable help with getting the some of the +source for the base interpreter together, and for his hints on how to cover +the porting requirements, and to David Kinder for a complete reality check +on the port progress, and also for the v3.0 Level9 interpreter. + +Thanks also to Ben Hines for the Mac code, which, +if I have transcribed it correctly from earlier work with AGiliTy, should +permit this port to run on Mac platforms with Glk. + + +Running Games +------------- + +The interpreter understands game files that use the "Spectrum snapshot" +format. A file in this format usually has the extension ".sna" or ".SNA". + +Give the name of the game file to run at the system prompt. For example + + glklevel9 colossal.sna + +There are a few command line options that you can also add, to vary the way +that the game looks: + + -ni Don't intercept 'quit', 'restart', 'save', and 'restore' + -np Don't add an extra prompt on line input + -nl Don't attempt to detect infinite loops in games + -na Don't expand single-letter abbreviations + -nc Don't attempt to interpret selected commands locally + +See below for further information about what these options mean. + +Glk Level9 does not attempt to display any game graphics. + + +Compiling +--------- + +To compile Glk Level 9 for Linux, first unpack the source files. You might +need to use the -a argument to unzip in order to convert text files for your +system. + +Edit Makefile.glk so that it has the right path to the Glk library you wish +to build with. If you want to build the IFP plugin, also edit the parts of +Makefile.glk that have paths to IFP components. + +To build standalone binary version of Glk Level 9, use + + make -f Makefile.glk glklevel9 + +To build the IFP plugin, use + + make -f Makefile.glk level9-3.0.so + +To clean up and delete everything in the case of a build error, use + + make -f Makefile.glk clean + + +Intercepting Game Commands +-------------------------- + +Some Level 9 games are written for cassette tape based microprocessor +systems, and the way in which they save, restore, and restart games generally +reflects this. Additionally, there's often no straightforward way to quit +from a game. + +To try to make things a bit more convenient for a player, Glk Level9 traps +the following command words in its interface: + + quit, restart, save, restore, and load (synonym for restore) + +When it sees one of these entered on an input line, it tries to handle the +command itself. In the case of "quit" and "restart", it will confirm that +you want to end the current game. + +In the case of "save" and "restore", Glk Level9 will try to handle saving or +loading game state to or from system files using the Level 9 interpreter +internal routines, rather than the ones built into the game itself. This +bypasses the inconvenient "Lenslok" check built into some games, and also +works round a couple of possible Level 9 interpreter bugs. + +If you prefix a command with a single quote, Glk Level9 will not try to +handle the command itself, but will remove the quote, and pass the remaining +string to the game without change. + +You can turn off command interception with the command line option '-ni'. + + +Extra Prompting +--------------- + +Early Level 9 games generally output a prompt something like + + What now? + +However, it's not always printed when the game is expecting line input. +Simply leaning on the Return key will scroll the game with no prompting, and +the Adrian Mole games offer no prompt at all when not in menu modes. + +Some later Level 9 games begin with the "What now?" (or "What gnow?"), then +switch to issuing a "> " prompt later on. + +To try to make it a little clearer to see what's going on, the Glk Level9 +will add it's own "> " prompt when the game is expecting line input, but +only when it determines that the game has not already issued its own prompt. + +You can turn this off with the '-np' option. + + +Game Infinite Loop Checking +--------------------------- + +Some Level9 games can enter an infinite loop if they have nothing better to +do. For example, at then end of Adrian Mole, you have the option to play +the last part again. If you decline, the game will spin in a tight loop +which does nothing. + +Because it's hard to break into this loop without killing the interpreter +completely, Glk Level9 notes how much work the interpreter is doing over +time. If it notices that the interpreter has neither printed anything nor +asked for input for five seconds or so, it assumes that the game is in an +infinite loop, and offers you the chance to stop the game. + +In the unlikely event that this check triggers with a game that is not in an +infinite loop, you can turn off the option with "-nl". + + +Expanding Abbreviations +----------------------- + +Many IF games systems allow a player to use single character abbreviations +for selected common commands, for example, 'x' for 'examine', 'l' for look, +and so on. Not all Level 9 games offer this feature, however. + +To try to help the player, Glk Level 9 will automatically expand a selection +of single character commands, before passing the expanded string to the game +as input. It expands a command only if the first word of the command is a +single letter, and one of the following: + + 'c' -> "close" 'g' -> "again" 'i' -> "inventory" + 'k' -> "attack" 'l' -> "look" 'p' -> "open" + 'q' -> "quit" 'r' -> "drop" 't' -> "take" + 'x' -> "examine" 'y' -> "yes" 'z' -> "wait" + +If you want to suppress abbreviation expansion, you can prefix your input +with a single quote character (like putting literal strings into a spread- +sheet). If you do this, Glk Level9 will strip the quote, then pass the rest +of the string to the main interpreter without any more changes. So for +example, + + 'x something + +will pass the string "x something" back to the game, whereas + + x something + +will pass "examine something" back to the game. + +You can turn off abbreviation expansions with the command line option '-na'. + + +Interpreting Commands Locally +----------------------------- + +Glk Level9 will handle special commands if they are prefixed with the string +'glk'. It understands the following special commands: + + script on Starts recording the game text output sent to the + main game window + script off Turns off game text recording + inputlog on Starts recording input lines typed by the player + inputlog off Stops recording input lines + readlog on Reads an input log file as if it had been typed by + a player; reading stops automatically at the end of + the file + abbreviations on Turn abbreviation expansion on + abbreviations off Turn abbreviation expansion off + loopchecks on Turn game infinite loop checking on + loopchecks off Turn game infinite loop checking off + locals on Turn local interception of 'quit', 'restart', + 'save' and 'restore' on + locals off Turn local interception of 'quit', etc. off + prompts on Turn extra "> " prompting on + prompts off Turn extra "> " prompting off + version Prints the Glk library and Glk port version numbers + commands off Turn of Glk special commands; once off, there is no + way to turn them back on + +You can abbreviate these commands, as long as the abbreviation you use is +unambiguous. + +If for some reason you need to pass the string "glk" to the interpreter, you +can, as with abbreviations above, prefix it with a single quote character. + +You can turn off local command handling with the command line option '-nc'. + +If all of abbreviation expansion, local command handling, and game command +interceptions are turned off, there is no need to use single quotes to +suppress special interpreter features. + + +Multi-file Games +---------------- + +Some Level 9 games come in multiple files, for example + + gamedat1.sna + gamedat2.sna + ... + +and so on. Once a game part is finished, the Level 9 interpreter queries +the Glk interface for the next file. When this happens, both for disk and +for tape based games, the interface will try to generate the appropriate +file name for the next game part, by incrementing the last digit found in +the file's name. The interpreter will then look in the same directory for +the new file name. + +If this method fails, then you will need to run the separate game parts +manually. Alternatively, rename the files so that they match this pattern. + + +Keyboard Polling +---------------- + +Some Level 9 games use the standard home microprocessor system method of +waiting for a keypress, using a tight spin loop that samples the keyboard +until a key is detected. The Adrian Mole games use this keyboard sampling +both for scroll pausing, and for their main menu selections, with something +like: + + for each line in the output paragraph + print the line + while (os_readchar() shows a key is pressed) + ; + endwhile + endfor + selection = return value from os_readchar() + while (selection shows no key is pressed) + selection = return value from os_readchar() + handle selection... + +This means that os_readchar() _must_ be implemented according to the notes +in Level 9's porting.txt. That is, it must poll the keyboard for key +presses, and return the key code if a key is pressed, or zero if no key is +currently pressed. + +If os_readchar() waits for a key press on each call, Adrian Mole games will +wait for a key press after each output line, which is not what we want. On +the other hand, if it doesn't wait for a key press at the menu selection +point, the interpreter will consume all the available system CPU time, which +is also very undesirable on a multi-tasking operating system. + +To solve this, Glk Level9 uses two different techniques, and which it +chooses depends on whether the Glk library it is linked with has timers. + +If the Glk library has timers, then on each call to os_readchar(), Glk +Level9 starts a timeout for a short period, then waits for either a key +press event, or a timeout. If it receives a key press, it returns the code, +otherwise it returns zero. This lets games run correctly. Continually +cycling through the Glk timeout is fast enough to be acceptable to a player, +but the timeout provides good protection against spinning wildly in a tight +loop. + +If the Glk library does not have timers, then the interface will return zero +for some large number of consecutive calls, then wait until a character is +entered. Since the initial number is large, this allows the game to +progress, generally, to the point where it needs a key press to continue at +all. The only thing that might show slightly odd behaviour is the DEMO mode +of Adrian Mole games. + +Also, this type of character polling cannot be used with reading input logs. +So the Glk "readlog" command does not function with Adrian Mole games. + +To pretend that no key was pressed, when the interface is in fact waiting +for a key press, you can type Control-U. The Glk interface will return this +to the game as a zero. + +Games that rarely, or never, use line input, but continually poll for input +using os_readchar(), are difficult to stop. To help with this, Glk Level9 +offers Control-C as as way to quit the game, with a confirmation as for +"quit" above. Note that Control-C will only work when the game is using +os_readchar() (that is, most of the time for Adrian Mole games, and +occasionally, if ever, in other games). For normal line input, use "quit" +as described above. + +Control-C is turned off by the command line option "-nc". + + +-- + +Simon Baldwin, simon_baldwin@yahoo.com diff --git a/Glk/level9.hdr b/Glk/level9.hdr new file mode 100755 index 0000000..66fccb8 --- /dev/null +++ b/Glk/level9.hdr @@ -0,0 +1,53 @@ +# +# IFP header definitions for Level 9 +# +# The two main Level 9 formats are Spectrum snapshots (.sna), and Level 9 +# data files (.dat). Neither format has a magic identifier in them, so +# the following is based on observations only, and may be insufficient, or +# just plain wrong. +# +# For .sna snapshot files containing Level 9 games V2 and above, the same +# four bytes appear at offset 1 in the file : "58 27 9B 36". For what it's +# worth, these represent the values of the registers HL', and DE', I think. +# By the way, this pattern excludes the known V1 game, which the inter- +# preter cannot play. +# +# For .dat data files, the only commonality seems to be "00" at offset 13, +# and "00 00 00" at offset 19. This is not at all safe; for example, tar +# archives match this pattern too. So for now, this header file will not +# recognize .dat data files. You can, however, turn them into pseudo- +# snapshots with +# +# ( echo -e "\000\130\047\233\066\c"; cat somefile.dat ) >somefile.lev +# +# This doesn't make the file a .sna snapshot, but it does put enough of a +# marker on it that the Level 9 plugin will find it, and since the interp- +# reter searches in the file for the game data, it'll work. Because this +# is cryptic, I'll add a secondary header to look for - "LVL9" at the very +# start of the file. You can add this to a .dat data file with the marg- +# inally less confusing +# +# ( echo "LVL9"; cat somefile.dat ) >somefile.lev +# +# For now, this is, unfortunately, the best workround available. +# +IFP_ENGINE_TYPE="Level9" +IFP_ENGINE_NAME="Level9" +IFP_ENGINE_VERSION="3.0" + +IFP_ACCEPTOR_OFFSET=0 +IFP_ACCEPTOR_LENGTH=5 +IFP_ACCEPTOR_PATTERN="^(.. 58 27 9B 36|4C 56 4C 39 ..)$" + +IFP_AUTHOR_NAME="Glen Summers" + +IFP_BUILDER_NAME="Simon Baldwin" +IFP_BUILDER_EMAIL="simon_baldwin@yahoo.com" + +IFP_ENGINE_DESCRIPTION=\ +"This is an interpreter for Level 9 games in any format, including + Spectrum snapshots (.sna files), and is capable of playing V2, V3, + and V4 Level 9 games. Currently, the interpreter plugin recognizes + only snapshot files. Other files need to be specially marked with + 'LVL9' in the first four bytes.\n" +IFP_ENGINE_COPYRIGHT="Copyright (C) 2002, by Glen Summers and David Kinder.\n" diff --git a/MakeDist.bat b/MakeDist.bat new file mode 100755 index 0000000..f67e5ee --- /dev/null +++ b/MakeDist.bat @@ -0,0 +1,10 @@ +@echo off + +"\Program Files\Info-ZIP\Zip\zip" \Temp\Level9Src.zip COPYING *.c *.h *.bat *.txt -x todo.txt +"\Program Files\Info-ZIP\Zip\zip" \Temp\Level9Src.zip Amiga/* +"\Program Files\Info-ZIP\Zip\zip" \Temp\Level9Src.zip DOS/* -x DOS/*.exe +"\Program Files\Info-ZIP\Zip\zip" \Temp\Level9Src.zip DOS32/* -x DOS32/*.exe +"\Program Files\Info-ZIP\Zip\zip" \Temp\Level9Src.zip Glk/* +"\Program Files\Info-ZIP\Zip\zip" \Temp\Level9Src.zip Unix/* +"\Program Files\Info-ZIP\Zip\zip" \Temp\Level9Src.zip Win/* Win/Classlib/* + diff --git a/Unix/unix-curses.c b/Unix/unix-curses.c new file mode 100755 index 0000000..b459129 --- /dev/null +++ b/Unix/unix-curses.c @@ -0,0 +1,1073 @@ +/* + * ncurses-based Unix interface for Level-9 interpreter + * + * Copyright (c) Jim Cameron 1998 + */ + + +/* + * 9-Jun-1998 fixed bug in os_load_file + * + * A few notes on this port: + * + * I've modified level9.h to use the LITTLEENDIAN macro -- it assumes this if + * you're using DOS or Windows, otherwise it uses the byte sex macros. This + * shouldn't hurt a little-endian machine (Intel), but you must undefine + * LITTLEENDIAN if you've got a Motorola-type machine. + * + * If you don't specify a path, level9 Linux will look first in the directory + * $LEVEL9DIR, if defined, and then in the current directory. + * + * The line justify routine could do with a little work -- it looks a bit ugly + * on screen (the code looks a bit ugly as well, to be honest). How do you + * write text-mode justification? + * + * 21-Apr-2002, simonb@caldera.com - Updates for v3.0 interpreter: + * + * FILE_DELIM defined locally + * Curses halfdelay in os_readchar() + * Os_stoplist() + * NULL second arg on LoadGame + * Stub graphics functions + * Strnicmp definition + */ + +/* + * Here are the defines which you might want to play with + */ +#define JUSTIFY 1 +#define FILE_DELIM '/' + +/* + * Set this if you are compiling on a little-endian machine (ARM, Intel) + */ +#define LITTLEENDIAN 1 + +/* + * Define this as 1 to get the Emacs-type key bindings + * Ctrl-A (go to beginning of line) + * Ctrl-B (back one character) + * Ctrl-D (delete character at cursor) + * Ctrl-E (go to end of line) + * Ctrl-F (forward one character) + * Ctrl-K (delete to end of line) + */ +#define EMACS_TYPE_KEYS 1 + +/* + * This will make justified text look nicer in some circumstances, but is + * slower and a bit of a yucky kludge. Don't use it if not justifying + */ +#define REPRINT_FLUSHED_TEXT 1 + + +/* + * #includes + */ +#include +#include +#include +#include +#include +#include "level9.h" + +/* + * You might have to change this to ncurses.h if using ncurses + */ +#include + + +#define CTRL_A '\x01' +#define CTRL_B '\x02' +#define CTRL_D '\x04' +#define CTRL_E '\x05' +#define CTRL_F '\x06' +#define CTRL_H '\x08' +#define CTRL_K '\x0B' + + +int Line_width; +char *Line_buffer; +int Line_ptr = 0; +int Lines = 0; +int More_lines; + +/* + * This is used by the justification routine to hold the part of the input that + * wouldn't fit on the line + */ +char *Rump; + +/* + * See discussion with os_flush() + */ +#if !REPRINT_FLUSHED_TEXT +int Line_pos = 0; +#endif + + +/* + * Justification kludge (see end of printline() ) + */ +L9BOOL Suppress_newline = FALSE; + + +/* + * curses' screen window + */ +WINDOW *stdscr; + + +/* + * History! + */ +#define HISTSIZE 32 +#define MAX_COMMAND_LENGTH 128 + +char History_list [HISTSIZE] [MAX_COMMAND_LENGTH]; +int History_start; +int History_end; + +#define NEXT_HISTORY(h) { ++(h); if ((h) >= HISTSIZE) (h) = 0; } +#define PREV_HISTORY(h) { if ((h) == 0) (h) = HISTSIZE; --(h); } + + + +/* + * Local function prototypes + */ +void printline (void); +static L9BOOL input_i (char *ibuff, int size, L9BOOL suppress_history); + + +/* + * From porting.txt : + os_printchar() prints a character to the output. The interface + can either buffer this character or print it immediately, but + if buffering is used then the characters must all be sent to the + output when the interpreter calls os_flush(). A paragraph of + text is output as one long stream of characters, without line + breaks, so the interface must provide its own word wrapping, and + any other features that are desired, such as justification or a + [More] prompt. The carriage return character is always '\r', + rather than '\n'. + * Got that? Good + */ +void os_printchar(char c) +{ + + if (c == '\r') + { + + /* + * Newline + */ + if (Line_ptr != 0) + { + Suppress_newline = FALSE; + } + + os_flush (); + + /* + * If the last line of the previous paragraph exactly filled the screen, + * we now have a blank line and should not print a newline + */ + if (!Suppress_newline) + { + addch ('\n'); + refresh (); + ++Lines; + } + else + { + Suppress_newline = FALSE; + } + + Line_ptr = 0; + +# if !REPRINT_FLUSHED_TEXT + Line_pos = 0; +# endif + + } + else + { + Line_buffer [Line_ptr] = c; + + ++Line_ptr; + +# if REPRINT_FLUSHED_TEXT + if (Line_ptr >= Line_width) + { + printline (); + } +# else + ++Line_pos; + if (Line_pos >= Line_width) + { + Line_buffer [Line_ptr] = 0; + printline (); + } +# endif + + } + + if (Lines == More_lines) + { + printw ("[More]"); + refresh (); + getch (); + printw ("\r \r"); + refresh (); + Lines = 0; + } + +} + + +/* + * From porting.txt : + os_input() reads a line of text from the user, usually to accept + the next command to be sent to the game. The text input must be + stored in ibuff with a terminating zero, and be no longer than + size characters. Normally os_input() should return TRUE, but may + return FALSE to cause the entire input so far to be discarded. + The reason for doing so is discussed in the section at the end + on allowing the interpreter to load a new game without exiting. + * + * The command-line history behaviour mirrors that of bash(1); if you edit a + * line and then move to another point in the history the current line will + * be saved in that history slot. + * + * Command-line editing works the way you'd expect (subject to $TERM's being + * set correctly and your termcap file's being kosher, of course). The input + * cursor takes up all the buffer space which doesn't actually contain + * characters; characters to the right of the cursor are stored at the very + * end of the input buffer. left_pos is the next position where a character + * is about to be stored, and right_pos points at the character under the + * cursor. This makes insertion much easier. (I got this trick from + * disassembling an old word-processor on the BBC micro). + * + * No, I don't have tab completion. Feel free to add it in ... 8-) + */ +L9BOOL os_input (char *ibuff, int size) +{ + + return input_i (ibuff, size, FALSE); + +} + + +/* + * The 'i' stands for internal + * The suppress_history parameter is used by the save/load filename input + * routines to allow command-line editing without history + */ +L9BOOL input_i (char *ibuff, int size, L9BOOL suppress_history) +{ + + int left_pos = 0; + int right_pos = size - 1; + int got; + int x, y; + + int history_current = History_end; + + /* + * A macro for fetching a history list entry + */ +# define FETCH_HISTORY \ + getyx (stdscr, y, x); \ + move (y, x - left_pos); \ + clrtoeol (); \ + strcpy (ibuff, History_list [history_current]); \ + left_pos = strlen (ibuff); \ + right_pos = size - 1; \ + printw ("%s", ibuff) + + ibuff [right_pos] = '\0'; + + /* + * Player's got a chance to read all the text now + */ + Lines = 0; + + /* + * We are going to print a newline, but not through the normal channels. + * Make sure there's no dangling justification stuff + */ +# if REPRINT_FLUSHED_TEXT + Line_ptr = 0; +# else + Line_pos = 0; +# endif + + if (size > MAX_COMMAND_LENGTH) + { + size = MAX_COMMAND_LENGTH; + } + + /* + * Clear the current entry in the history list + */ + if (!suppress_history) + { + History_list [History_end] [0] = '\0'; + } + + while (1) + { + + got = getch (); + switch (got) + { + + case '\r': + case '\n': + case KEY_ENTER: + + strcpy (ibuff + left_pos, ibuff + right_pos); + if ((strlen (ibuff) > 0) && !suppress_history) + { + strcpy (History_list [History_end], ibuff); + NEXT_HISTORY (History_end); + if (History_end == History_start) + { + NEXT_HISTORY (History_start); + } + } + getyx (stdscr, y, x); + move (y, x - left_pos); + printw ("%s\n", ibuff); + refresh (); + return TRUE; + + case CTRL_H: + case KEY_BACKSPACE: + if (left_pos == 0) + { + beep (); + } + else + { + --left_pos; + /* + * CHANGEME This won't work when the cursor is at the beginning of a + * line + */ + getyx (stdscr, y, x); + mvdelch (y, x - 1); + } + + break; + + /* + * Delete character under cursor + */ +# if EMACS_TYPE_KEYS + case CTRL_D: +# endif + case KEY_DC: + if (ibuff [right_pos] == '\0') + { + beep (); + } + else + { + ++right_pos; + delch (); + } + break; + + case KEY_UP: + if ((history_current == History_start) || suppress_history) + { + beep (); + } + else + { + strcpy (ibuff + left_pos, ibuff + right_pos); + strcpy (History_list [history_current], ibuff); + PREV_HISTORY (history_current); + FETCH_HISTORY; + } + break; + + case KEY_DOWN: + if ((history_current == History_end) || suppress_history) + { + beep (); + } + else + { + strcpy (ibuff + left_pos, ibuff + right_pos); + strcpy (History_list [history_current], ibuff); + NEXT_HISTORY (history_current); + FETCH_HISTORY; + } + break; + +# if EMACS_TYPE_KEYS + case CTRL_B: +# endif + case KEY_LEFT: + if (left_pos == 0) + { + beep (); + } + else + { + addch ('\x08'); + --left_pos; + --right_pos; + ibuff [right_pos] = ibuff [left_pos]; + } + break; + +# if EMACS_TYPE_KEYS + case CTRL_F: +# endif + case KEY_RIGHT: + if (ibuff [right_pos] == '\0') + { + beep (); + } + else + { + ibuff [left_pos] = ibuff [right_pos]; + addch (ibuff [left_pos]); + ++right_pos; + ++left_pos; + } + break; + + /* + * Move to beginning of line + */ +# if EMACS_TYPE_KEYS + case CTRL_A: +# endif + case KEY_HOME: + case KEY_A1: + while (left_pos > 0) + { + addch ('\x08'); + --left_pos; + --right_pos; + ibuff [right_pos] = ibuff [left_pos]; + } + break; + + /* + * Move to end of line + */ +# if EMACS_TYPE_KEYS + case CTRL_E: +# endif + case KEY_END: + case KEY_C1: + while (ibuff [right_pos] != '\0') + { + ibuff [left_pos] = ibuff [right_pos]; + addch (ibuff [left_pos]); + ++right_pos; + ++left_pos; + } + break; + + /* + * Delete to the end of the line + */ +# if EMACS_TYPE_KEYS + case CTRL_K: +# endif + case KEY_EOL: + right_pos = size - 1; + clrtoeol (); + break; + + /* + * Misc. key + */ + default: + + if ((left_pos >= right_pos) || (!isprint (got))) + { +# if DEBUG + printw ("%d\n", got); +# endif + beep (); + } + else + { + ibuff [left_pos] = got; + ++left_pos; + insch (got); + addch (got); + } + + break; + + } + + refresh (); + + } + +# undef FETCH_HISTORY + + return FALSE; + + +} + +char os_readchar(int millis) +{ + + int c; + + /* Set curses delay in 1/10 secs, with a lower limit of 1/10 sec */ + halfdelay ((millis / 100) > 0 ? millis / 100 : 1); + c = getch (); + cbreak (); + + if (c == ERR) + { + return 0; + } + + /* + * This is a hack for multiple-choice games (Adrian Mole) ... if you've had + * time to hit a key you've had time to read what's on the screen, or if + * not, it's your lookout + */ + Lines = 0; + + return c; + +} + +L9BOOL os_stoplist(void) +{ + int c; + + nodelay (stdscr, TRUE); + c = getch (); + nodelay (stdscr, FALSE); + + return (c != ERR); +} + +/* + * From porting.txt : + If the calls to os_printchar() are being buffered by the + interface then the buffered text must be printed when os_flush() + is called. + * + * jim sez: + * Some games (Adrian Mole) have a habit of flushing in the middle of a line + * (I don't recommend this; you'll get a wet bum) which confuses the + * justification. I'm getting round this problem by: + * If REPRINT_FLUSHED_TEXT is defined as true, leaving the flushed characters + * in the buffer and printing a carriage return at the beginning of all + * lines. Thus printing of more text will cause the original text to be + * overwritten with the justified version. This will look nicer, but cause + * hackers to go Ur, Yuk. + * If REPRINT_FLUSHED_TEXT is undefined or false, keeping track of the + * current column, leaving printed text on the screen and justifying the + * rest of the line as best I can. This might cause strange effects if there + * is very little room left at the end of the line. + * If you don't use justification, don't reprint flushed text either. There's + * no point. + */ +void os_flush(void) +{ + +# if REPRINT_FLUSHED_TEXT + int x, y; + getyx (stdscr, y, x); + move (y, 0); +# else + Line_ptr = 0; +# endif + + Line_buffer [Line_ptr] = '\0'; + addstr (Line_buffer); + refresh (); + +} + + + +/* + * From porting.txt : + os_save_file() should prompt the user in some way (with either + text or a file requester) for a filename to save the area of + memory of size Bytes pointed to by Ptr. TRUE or FALSE should be + returned depending on whether or not the operation was successful. + */ +L9BOOL os_save_file(L9BYTE * Ptr, int Bytes) +{ + + char fname [256]; + FILE *outfile; + + os_flush(); + printw("\nSave file: "); + refresh (); + input_i (fname, 256, TRUE); + + outfile = fopen (fname, "w"); + if (outfile == NULL) + { + return FALSE; + } + + fwrite (Ptr, 1, Bytes, outfile); + fclose (outfile); + + return TRUE; + +} + + + +/* + * From porting.txt : + os_load_file() should prompt the user for the name of a file to + load. At most Max bytes should be loaded into the memory pointed + to by Ptr, and the number of bytes read should be placed into the + variable pointed to by Bytes. + */ +L9BOOL os_load_file(L9BYTE *Ptr,int *Bytes,int Max) +{ + + char fname [256]; + FILE *infile; + + os_flush(); + printw("\nLoad file: "); + refresh (); + input_i (fname, 256, TRUE); + + infile = fopen (fname, "r"); + if (infile == NULL) + { + return FALSE; + } + + *Bytes = fread (Ptr, 1, Max, infile); + fclose (infile); + return TRUE; + +} + + + + +/* + * From porting.txt : + os_get_game_file() should prompt the user for a new game file, to + be stored in NewName, which can take a maximum name of Size + characters. This is used in the Adrian Mole games (and possibly + others) which load in the next part of the game after the part + currently being played has completed. These games were originally + written for tape-based systems where the call was simply "load + the next game from the tape". + */ +L9BOOL os_get_game_file(char *NewName,int Size) +{ + + os_flush(); + addstr ("Load next game: "); + refresh (); + return input_i (NewName, Size, TRUE); + return TRUE; + +} + + + + +/* + * From porting.txt : + os_set_filename() is for multi-part games originally written for + disk-based systems, which used game filenames such as + + gamedat1.dat gamedat2.dat + + etc. The routine should take the full filename in NewName (of + maximum size Size) and modify it to reflect the number n, e.g. + os_set_filename("gamedat1.dat",2) should leave "gamedat2.dat" + in NewName. + */ +void os_set_filenumber(char *NewName,int Size,int n) +{ + + char *leafname; + int i; + + leafname = strrchr (NewName, FILE_DELIM); + if (leafname == NULL) + { + leafname = NewName; + } + + for (i = strlen (leafname) - 1; i >= 0; i--) + { + if (isdigit (leafname [i])) + { + leafname [i] = '0' + n; + break; + } + } + +} + + + +/* + * From porting.txt : + You must provide your own main() entry point for the program. + The simplest such main() is given in generic.c, which just calls + LoadGame() and then sits in a loop calling RunGame(). These + functions are discussed below. + */ +int main (int argc, char *argv []) +{ + + int i; + char gamename [256]; + char *envbuf; + L9BOOL gotgame; + + /* + * Check byte sex + */ +# if LITTLEENDIAN + L9UINT32 test = 0x12345678; + char *tcp = (char *) &test; +# else + L9UINT32 test = 0x78563412; + char *tcp = (char *) &test; +# endif + if ((tcp [0] != 0x78) || (tcp [1] != 0x56) || + (tcp [2] != 0x34) || (tcp [3] != 0x12)) + { + fprintf (stderr, "%s: compiled with the wrong byte sex!\n" + " Check the LITTLEENDIAN macro in os/unix-curses.c\n", + argv [0]); + exit (1); + } + + if (argc != 2) + { + fprintf (stderr, "Syntax: %s \n",argv[0]); + exit (1); + } + + printf ("Level 9 Interpreter v3.0 by Glen Summers and David Kinder\n\n" + "curses interface by jim" + " (jim@madeira.physiol.ucl.ac.uk)\n\n"); + + /* + * Initialise the curses library---these are the recommended options from + * ncurses (3X) on my Linux box + */ + stdscr = initscr (); + cbreak (); + noecho (); + nonl (); + intrflush (stdscr, FALSE); + keypad (stdscr, TRUE); + scrollok (stdscr, TRUE); + + /* + * Get the terminal characteristics for line width and More prompt + */ + Line_width = tgetnum ("co"); + if (Line_width == ERR) + { + fprintf (stderr, "Couldn't get terminal width---falling back on good old" + " 80 columns\n\n"); + Line_width = 80; + } + + More_lines = tgetnum ("li"); + if (More_lines == ERR) + { + fprintf (stderr, "Couldn't get terminal height---guessing\n\n"); + More_lines = 24; + } + More_lines--; + + Line_buffer = malloc (2 * (Line_width + 1)); + if (Line_buffer == NULL) + { + endwin (); + fprintf (stderr, "Couldn't allocate buffer for pretty-print\n\n"); + exit (1); + } + Rump = Line_buffer + Line_width + 1; + + + /* + * Make sure the line buffer is terminated + */ + Line_buffer [Line_width] = '\0'; + + gotgame = FALSE; + if (strchr (argv [1], '/') == NULL) + { + /* + * Look first in $LEVEL9DIR, if it is defined + */ + envbuf = getenv ("LEVEL9DIR"); + if (envbuf != NULL) + { + sprintf (gamename, "%s/%s", envbuf, argv [1]); + gotgame = LoadGame (gamename, NULL); + } + } + + if (!gotgame) + { + if (!LoadGame (argv [1], NULL)) + { + endwin (); + fprintf(stderr, "%s: couldn't open Level9 file %s\n", + argv [0], argv [1]); + exit (1); + } + } + + /* + * Main game loop! + */ + while (1) + { + if (!RunGame ()) + { + break; + } + } + + StopGame(); + FreeMemory(); + + free (Line_buffer); + + /* + * Stop ncursing! + */ + endwin (); + + printf ("Thanks for playing Level9 ...\n\n"); + + return 0; + +} + + + + +/* + * Print the text in the line buffer up to the word in progress, justifying it + * if wanted + */ +void printline (void) +{ + + char *spaceptr; + char *hyphenptr; + +# if JUSTIFY + int dead_space; + int num_spaces; + int scount = 0; + char *stringptr; +# endif + +# if REPRINT_FLUSHED_TEXT + int x, y; +# endif + + /* + * ... otherwise ANY full-length line in a paragraph will cause the end-of- + * paragraph newline to be suppressed (oops!) + */ + Suppress_newline = FALSE; + + ++Lines; + +# if REPRINT_FLUSHED_TEXT + getyx (stdscr, y, x); + move (y, 0); +# endif + + spaceptr = strrchr (Line_buffer, ' '); + hyphenptr = strrchr (Line_buffer, '-'); + if ((spaceptr == NULL) && (hyphenptr == NULL)) + { + + /* + * Buffer contains no spaces - just print the whole line + */ + printw ("%s\n", Line_buffer); + Line_ptr = 0; + refresh (); + return; + + } + + if (spaceptr > hyphenptr) + { + *spaceptr = '\0'; + strcpy (Rump, spaceptr + 1); + } + else + { + strcpy (Rump, hyphenptr + 1); + hyphenptr [1] = '\0'; + } + +# if JUSTIFY + + /* + * This isn't a very sophisticated justify routine. In particular, leading + * spaces at the beginning of lines will cause curious results + * It looks a little ugly. I should like to give priority when adding spaces + * to the positions following punctuation + * Count the spaces in the line + */ + dead_space = Line_width - strlen (Line_buffer); + num_spaces = -1; + spaceptr = Line_buffer; + while (spaceptr != NULL) + { + spaceptr = strchr (spaceptr + 1, ' '); + ++num_spaces; + } + + if (num_spaces > 0) + { + stringptr = Line_buffer; + while (*stringptr != '\0') + { + addch (*stringptr); + if (*stringptr == ' ') + { + scount += dead_space; + while (scount > num_spaces) + { + scount -= num_spaces; + addch (' '); + } + } + ++stringptr; + } + } + else + { + addstr (Line_buffer); + } + + addch ('\n'); + +# else + + printw ("%s\n", Line_buffer); + +# endif /* JUSTIFY */ + + strcpy (Line_buffer, Rump); + Line_ptr = strlen (Line_buffer); + +# if !REPRINT_FLUSHED_TEXT + Line_pos = Line_ptr; +# endif + + /* + * If the last line of a paragraph exactly fills the screen, we will get an + * extra newline when attempting to flush the blank `next' line. Suppress + * this + */ + if (Line_ptr == 0) + { + Suppress_newline = TRUE; + } + + refresh (); + +} + + + + +/* + * jim -- being Linux, we must provide stricmp + */ +int stricmp (const char *s1, const char *s2) +{ + + int diff; + + while ((*s1 != '\0') || (*s2 != '\0')) + { + diff = toupper (*s1) - toupper (*s2); + if (diff != 0) + { + return diff; + } + ++s1; + ++s2; + } + + return 0; + +} +int strnicmp (const char *s1, const char *s2, int n) +{ + + int diff, count = 0; + + while ((*s1 != '\0') || (*s2 != '\0') || count < n) + { + diff = toupper (*s1) - toupper (*s2); + if (diff != 0) + { + return diff; + } + ++s1; + ++s2; + ++count; + } + + return 0; + +} + +/* + * Stub graphics routines. + */ +void os_graphics(int mode) +{ +} + +void os_cleargraphics(void) +{ +} + +void os_setcolour(int colour, int index) +{ +} + +void os_drawline(int x1, int y1, int x2, int y2, int colour1, int colour2) +{ +} + +void os_fill(int x, int y, int colour1, int colour2) +{ +} + diff --git a/Win/Classlib/Classlib.sln b/Win/Classlib/Classlib.sln new file mode 100755 index 0000000..42ef883 --- /dev/null +++ b/Win/Classlib/Classlib.sln @@ -0,0 +1,21 @@ +Microsoft Visual Studio Solution File, Format Version 7.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Classlib", "Classlib.vcproj", "{91DD3373-2B04-43AE-97BF-70D4F3BB64F3}" +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + ConfigName.0 = Debug + ConfigName.1 = Release + EndGlobalSection + GlobalSection(ProjectDependencies) = postSolution + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {91DD3373-2B04-43AE-97BF-70D4F3BB64F3}.Debug.ActiveCfg = Debug|Win32 + {91DD3373-2B04-43AE-97BF-70D4F3BB64F3}.Debug.Build.0 = Debug|Win32 + {91DD3373-2B04-43AE-97BF-70D4F3BB64F3}.Release.ActiveCfg = Release|Win32 + {91DD3373-2B04-43AE-97BF-70D4F3BB64F3}.Release.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/Win/Classlib/Classlib.vcproj b/Win/Classlib/Classlib.vcproj new file mode 100755 index 0000000..66db7e0 --- /dev/null +++ b/Win/Classlib/Classlib.vcproj @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Win/Classlib/app.cpp b/Win/Classlib/app.cpp new file mode 100755 index 0000000..49cd5bb --- /dev/null +++ b/Win/Classlib/app.cpp @@ -0,0 +1,150 @@ +// App definitions ******************************* + +#include +#pragma hdrstop + +#include + +HINSTANCE App::hInstance = 0; +HINSTANCE App::hPrevInstance = 0; + +#ifdef WIN32 +HKEY App::Machine; +HKEY App::User; +#endif + +int App::nCmdShow = 0; +char *App::CmdLine = NULL; +char *App::Name = NULL; +char *App::IniFile = NULL; +Object *App::MainWindow = NULL; +BOOL App::Ctl3dEnabled=FALSE; +HINSTANCE App::hI3D; +TextWindow *App::TextWindow=NULL; +BOOL App::Active; + +LRESULT CALLBACK InitialisationProc( HWND , UINT , WPARAM , LPARAM ); + +App::App(char *aName,char *Ini) +{ + Name=strdup(aName); + if (Ini) + { + IniFile=strdup(Ini); + #ifdef WIN32 + OpenKeys(); + #endif + } + Active=TRUE; +} + +#ifdef WIN32 +void App::OpenKeys() +{ + // open reg keys + DWORD Disp; + if (RegCreateKeyEx(HKEY_LOCAL_MACHINE,IniFile,0,"",REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&Machine,&Disp)==ERROR_SUCCESS) + { + } + if (RegCreateKeyEx(HKEY_CURRENT_USER,IniFile,0,"",REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&User,&Disp)==ERROR_SUCCESS) + { + } +} +#endif + +typedef BOOL (WINAPI *CallProcProc)(HANDLE); + +void CallProc(HINSTANCE hi,LPSTR lp) +{ + CallProcProc fp=(CallProcProc) GetProcAddress(hi,lp); + if (fp) fp(App::hInstance); +} + +void App::EnableCtl3d() +{ + UINT OldError = SetErrorMode(SEM_NOOPENFILEERRORBOX); + #ifdef WIN32 + if ((hI3D=LoadLibrary("ctl3d32.dll"))!=NULL) { + #else + if ((hI3D=LoadLibrary("ctl3dv2.dll"))>HINSTANCE_ERROR) { + #endif + Ctl3dEnabled=TRUE; + + CallProc(hI3D,"Ctl3dRegister"); + CallProc(hI3D,"Ctl3dAutoSubclass"); + } + SetErrorMode(OldError); +} + +App::~App() +{ + delete[] Name; + if (IniFile) delete[] IniFile; + if (MainWindow) delete MainWindow; + if (Ctl3dEnabled) + { + CallProc(hI3D,"Ctl3dUnregister"); + FreeLibrary(hI3D); + } +#ifdef WIN32 + RegCloseKey(Machine); + RegCloseKey(User); +#endif +} + +BOOL App::PeekMessage() +{ + MSG Msg; + if (Active && ::PeekMessage(&Msg,NULL,0,0,PM_REMOVE)) + { + TranslateMessage( &Msg ); + DispatchMessage( &Msg ); + return TRUE; + } + return FALSE; +} + +void App::PeekLoop() +{ + while (PeekMessage()); +} + +void App::Quit() +{ + PostQuitMessage(0); + Active=FALSE; +} + +int App::MessageLoop( void ) +{ + MSG msg; + + while( GetMessage( &msg, NULL, 0, 0 ) ) + { + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } + return msg.wParam; +} + +int App::MainLoop() +{ + return MessageLoop(); +} + +int App::Run() +{ + InitMainWindow(); + if (!MainWindow) return 0; + if (!MakeWindow(MainWindow)) return 0; + + ShowWindow(MainWindow->hWnd, nCmdShow ); + FirstIn(); + return MainLoop(); +} + +BOOL App::MakeWindow(Object *AObject) +{ + return AObject->Create(); +} + diff --git a/Win/Classlib/app.h b/Win/Classlib/app.h new file mode 100755 index 0000000..3fb45cb --- /dev/null +++ b/Win/Classlib/app.h @@ -0,0 +1,35 @@ +// app.h + +class App +{ +public: + App(char *aName,char *Ini=NULL); + virtual ~App(); + static HINSTANCE hInstance; + static HINSTANCE hPrevInstance; +#ifdef WIN32 + static HKEY Machine; + static HKEY User; + static void OpenKeys(); +#endif + static char *CmdLine; + static int nCmdShow; + static Object *MainWindow; + static TextWindow *TextWindow; + static char *Name; + static char *IniFile; + static int MessageLoop( void ); + virtual int MainLoop(); + static void PeekLoop(); + static BOOL PeekMessage(); + void EnableCtl3d(); + int Run(); + virtual void FirstIn() {}; + static BOOL Active; + static void Quit(); +private: + static BOOL Ctl3dEnabled; + static HINSTANCE hI3D; + virtual void InitMainWindow() =0 ; + BOOL MakeWindow(Object *); +}; diff --git a/Win/Classlib/array.cpp b/Win/Classlib/array.cpp new file mode 100755 index 0000000..31a3710 --- /dev/null +++ b/Win/Classlib/array.cpp @@ -0,0 +1,49 @@ +// array stuff + +template + +class Array +{ +public: + Array() + { + Data=NULL; + Dead=Used=Size=0; + } + Array(int n) + { + Data=new T*[Size=n]; + for (int i=0;i + +class Array +{ +public: + Array(int n) + { + Data=new T*[Size=n]; + Used=0; + } + ~Array(); + void Clear(); + void Add(T *Item) + { + if (Used==Size) Resize(); + Data[Used++]=Item; + } + void Delete(int i) + { + delete Data[i]; + memcpy(Data+i,Data+i+1,(--Used-i)*sizeof(T)); + } + void Resize(); + + T **Data; + int Used,Size; + T* operator [](int i) + { + return Data[i]; + } +}; + +template +Array::~Array() +{ + Clear(); +} + +template +void Array::Clear() +{ + for (int i=0;i +void Array::Resize() +{ +// create new array + T **NewData=new T*[Size=(long)Size*(100+INC)/100]; + for (int i=0;i +#pragma hdrstop + +#include + +#include +#include + +class LoadFail{}; +class SaveFail{}; +class Incompatible{}; + +Bitmap::Bitmap(char*fName) +{ + BITMAPFILEHEADER bmfh; + BITMAPINFOHEADER bmih; + ifstream f(fName,ios::in | ios::binary); + if (f.fail()) throw LoadFail(); + + f.read((bhp) &bmfh,sizeof(bmfh)); + if (f.gcount()!=sizeof(bmfh)) throw LoadFail(); + f.read((bhp) &bmih,sizeof(bmih)); + if (f.gcount()!=sizeof(bmih)) throw LoadFail(); + Width=bmih.biWidth; + Height=bmih.biHeight; + Bits=bmih.biBitCount; + Pitch=(Bits*Width/8+3)&~3; // real width in bytes + + if (Bits<8 || bmih.biCompression!=BI_RGB) throw Incompatible(); + Image.Alloc((int32) Pitch*Height); + + if (Bits==8) + { + Pal.Alloc(256); + f.read((bhp) Pal.Data,256*sizeof(RGBQUAD)); + } + f.read(Image,(int32) Pitch*Height); + if (f.gcount()!=(int32) Pitch*Height) throw LoadFail(); +} + +void Bitmap::ConvertTo256(RGBQUAD *DestPal,int PalSize) +{ + Pointer Image2((int32) Width*Height); + bhp buf,buf2=Image2; + + if (Bits==8) + { + // generate pixel translation + BYTE PixTrans[256]; + int i; + for (i=0;i<256;i++) + PixTrans[i]=FindCol(Pal[i].rgbRed,Pal[i].rgbGreen,Pal[i].rgbBlue,DestPal,PalSize); + + // do pixel translation + for (i=0;i=0;i--) + f.write( Image + (int32) i*Pitch,Width); +} + +void Bitmap::ConvertTo24Bit() +{ + if (Bits==24) return; + if (Bits!=8) throw Incompatible(); + Pointer Image2((int32) 3*Width*Height); + bhp buf,buf2=Image2; + + for (int i=0;irgbBlue; + *buf2++=Col->rgbGreen; + *buf2++=Col->rgbRed; + } + } + Image=Image2; + Bits=24; + Pitch=Width*Bits/8; +} + +void Bitmap::ChangeSize(int w,int h) +{ + ConvertTo24Bit(); + Pointer Image2((int32) 3*w*h); + bhp buf,buf2=Image2; + for (int i=0;i + +class LoadFail; +class SaveFail; +class Incompatible; + +class Bitmap +{ +public: + Bitmap(char*); + ~Bitmap() {} + void ConvertTo256(RGBQUAD *DestPal,int PalSize); + void ConvertTo24Bit(); + void SaveRaw(char *fName); + void ChangeSize(int w,int h); + + Pointer Pal; + Pointer Image; + int Width,Height,Pitch,Bits; +}; diff --git a/Win/Classlib/button.cpp b/Win/Classlib/button.cpp new file mode 100755 index 0000000..5cddcba --- /dev/null +++ b/Win/Classlib/button.cpp @@ -0,0 +1,22 @@ +// button in window + +#include +#pragma hdrstop + +Button::Button(Object *Parent,char *Title,int X, int Y, int W, int H,int ID) + : Window(Parent,Title) +{ + x=X; y=Y; w=W; h=H; + Style= WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON; + Create(); +#ifndef WIN32 + SetWindowWord(hWnd,GWW_ID,ID); +#else + SetWindowLong(hWnd,GWL_ID,ID); +#endif +} + +char *Button::GetClassName() +{ + return "BUTTON"; +} diff --git a/Win/Classlib/button.h b/Win/Classlib/button.h new file mode 100755 index 0000000..fced075 --- /dev/null +++ b/Win/Classlib/button.h @@ -0,0 +1,7 @@ +// button.h + +class Button : public Window { +public: + Button(Object *Parent,char *Title,int X, int Y, int W, int H,int ID); + virtual char *GetClassName(); +}; diff --git a/Win/Classlib/cacheini.cpp b/Win/Classlib/cacheini.cpp new file mode 100755 index 0000000..8a27f9d --- /dev/null +++ b/Win/Classlib/cacheini.cpp @@ -0,0 +1,263 @@ +#include +#pragma hdrstop + +#include +#include + +#include + +#include "cacheini.h" + +CachedIni::CachedIni() +{ + LineList=Last=NULL; + TotalSize=0; +} + +CachedIni::CachedIni(bhp ptr,long size) +{ + LineList=Last=NULL; + TotalSize=0; + Parse P(ptr,size); + do + { + AddLine(P.GetLine()); + } while (!P.Eof()); +} + +CachedIni::~CachedIni() +{ + Line *L=LineList,*L2; + while (L) + { + L2=L->Next; + delete L; + L=L2; + } +} + +void CachedIni::AddLine(char *s) +{ + InsertLine(s,Last); +} + +void CachedIni::DeleteLine(Line *L) +{ + if (L->Prev) L->Prev->Next=L->Next; + else LineList=L->Next; + if (L->Next) L->Next->Prev=L->Prev; + else Last=L->Prev; + delete L; +} + +CachedIni::Line *CachedIni::InsertLine(char *s,Line *L) +{ + Line *newLine=new Line(s); + TotalSize+=newLine->Len+1; + + if (L) + { + newLine->Next=L->Next; + if (L->Next) L->Next->Prev=newLine; + else Last=newLine; + L->Next=newLine; + } + else + { + LineList=Last=newLine; + newLine->Next=NULL; + } + newLine->Prev=L; + return newLine; +} + +bhp CachedIni::Write() +{ + bhp Buf=new huge BYTE[TotalSize]; + bhp ptr=Buf; + Line *L=LineList; + while (L) + { + ptr=L->Write(ptr); + L=L->Next; + } + return Buf; +} + +void CachedIni::Write(char *fName) +{ + FILE *f=fopen(fName,"wt"); + Line *L=LineList; + while (L) + { + fprintf(f,"%s\n",L->Str); + L=L->Next; + } + fclose(f); +} + +BOOL CachedIni::Read(char*fname) +{ + FILE *f=fopen(fname,"rt"); + if (f==NULL) return FALSE; + char temp[256]; + while (fgets(temp,256,f)) + { + char *p=strchr(temp,'\n'); + if (p) *p=0; + AddLine(temp); + } + fclose(f); + return TRUE; +} + +CachedIni::Line *CachedIni::FindSection(char *Section) +{ + char *Sect=new char[strlen(Section)+3]; + sprintf(Sect,"[%s]",Section); + + Line *L=LineList; + while (L) + { + if (!stricmp(L->Str,Sect)) break; + L=L->Next; + } + delete[] Sect; + return L; +} + +CachedIni::Line *CachedIni::FindItem(char *Item,Line *L) +{ + int len=strlen(Item); + while (L && *L->Str!='[') + { + if (!strnicmp(L->Str,Item,len) && L->Str[len]=='=') + return L; + L=L->Next; + } + return NULL; +} + +// fails if multiple matches eg device= +CachedIni::Line *CachedIni::FindItem2(char *Item,char *Full,Line *L) +{ +// int len=strlen(Item); +// int nm=0; +// Line *Match=NULL; + while (L && *L->Str!='[') + { + if (Full && !stricmp(Full,L->Str)) return L; // exact match so exit +// if (!strnicmp(L->Str,Item,len) && L->Str[len]=='=') +// { +// nm++; +// Match=L; +// } + L=L->Next; + } +// return nm>1 ? NULL : Match; + return NULL; +} + + +void CachedIni::AddToSection(char *Section,char *Item,char *Value) +{ + Line *L=FindSection(Section); + char *Val=NULL; + if (Value && *Value) + { + Val=new char[strlen(Item)+strlen(Value)+2]; + sprintf(Val,"%s=%s",Item,Value); + } + if (L) + { + Line *I=FindItem2(Item,Val,L->Next); + if (I) + { + if (Val) I->SetString(Val); + else DeleteLine(I); + } + else if (Val) + { + // find end of section + Line *Prev; + do + { + Prev=L; + L=L->Next; + } while (L && *L->Str!='['); + while (*Prev->Str==0) Prev=Prev->Prev; // skip back blank lines + + InsertLine(Val,Prev); + } + } + else if (Val) // only section create if item not blank + { + char *Sect=new char[strlen(Section)+3]; + sprintf(Sect,"[%s]",Section); + AddLine(Sect); + AddLine(Val); + delete[] Sect; + } + if (Val) delete[] Val; +} + +BOOL CachedIni::GetItem(char *Section,char *Item,String &S) +{ + Line *L=FindSection(Section); + if (!L) return FALSE; + L=FindItem(Item,L->Next); + if (!L) return FALSE; + char *p=strchr(L->Str,'=')+1; + S=p; + return TRUE; +} + +BOOL CachedIni::GetItemInt(char *Section,char *Item,int &Int) +{ + String Temp; + if (!GetItem(Section,Item,Temp)) return FALSE; + sscanf(Temp,"%d",&Int); + return TRUE; +} + +BOOL CachedIni::GetItemInt(char *Section,char *Item,long &Int) +{ + String Temp; + if (!GetItem(Section,Item,Temp)) return FALSE; + sscanf(Temp,"%ld",&Int); + return TRUE; +} + +BOOL CachedIni::GetItemBool(char *Section,char *Item) +{ + String Temp; + if (!GetItem(Section,Item,Temp)) return FALSE; + Temp.ToLower(); + return Temp=="yes" || Temp=="true"; +} + +BOOL CachedIni::GetSection(char *Section,String &Str) +{ + Str=""; + Line *L=FindSection(Section); + if (L==NULL) return FALSE; + L=L->Next; + while (L && *L->Str!='[') + { + Str << L->Str << '\n'; + L=L->Next; + } + return TRUE; +} + +BOOL IniScan::GetNextItem() +{ + do + { + L=L->Next; + } while (L && (*L->Str==0 || *L->Str==';')); + + if (L && *L->Str=='[') L=NULL; + return L!=NULL; +} + diff --git a/Win/Classlib/cacheini.h b/Win/Classlib/cacheini.h new file mode 100755 index 0000000..20b307b --- /dev/null +++ b/Win/Classlib/cacheini.h @@ -0,0 +1,102 @@ +// cacheini.h + +#ifndef _cacheini_h +#define _cacheini_h + +#include +#include + +class CachedIni +{ +public: + class Line + { + public: + Line(char *s) + { + Str=strdup(s); + Len=strlen(Str); + } + ~Line() + { + delete[] Str; + } + void SetString(char *s) + { + delete[] Str; + Str=strdup(s); + Len=strlen(Str); + } + bhp Write(bhp Buf) + { + strcpy((char*)Buf,Str); + *(Buf+Len)='\n'; + return Buf+Len+1; + } + char *Str; + int Len; + Line *Next,*Prev; + }; + + CachedIni(); + CachedIni(bhp ptr,long size); + ~CachedIni(); + + void AddLine(char *s); + void AddToSection(char *Section,char *Item,char *Value); + BOOL GetItem(char *Section,char *Item,String &S); + BOOL GetItemInt(char *Section,char *Item,int &Int); + BOOL GetItemInt(char *Section,char *Item,long &Int); + BOOL GetItemBool(char *Section,char *Item); + + BOOL GetSection(char *Section,String &Str); + + bhp Write(); + void Write(char*); + BOOL Read(char*); + + Line *LineList,*Last; + static Line *TempLine; + long TotalSize; + + void DeleteLine(Line *L); + Line *InsertLine(char *s,Line *L); + Line *FindSection(char *Str); + Line *FindItem(char *Item,Line *L); + Line *FindItem2(char *Item,char *Full,Line *L); +}; + +class IniScan +{ +public: + CachedIni::Line *L; + IniScan(CachedIni &C,char *Sect) + { + Section(C,Sect); + } + void Section(CachedIni &C,char *Sect) + { + L=C.FindSection(Sect); + if (L) GetNextItem(); + } + BOOL GetNextItem(); + operator char*() + { + return L->Str; + } + char *Str() + { + return L->Str; + } + int Len() + { + return L->Len; + } + BOOL operator ()() { return L!=NULL; } + BOOL operator ++() { return GetNextItem(); } + BOOL operator ++(int) { return GetNextItem(); } +}; + + +#endif + diff --git a/Win/Classlib/colorbtn.cpp b/Win/Classlib/colorbtn.cpp new file mode 100755 index 0000000..98db565 --- /dev/null +++ b/Win/Classlib/colorbtn.cpp @@ -0,0 +1,53 @@ +// Colour button ****************************** + +#include +#pragma hdrstop + +ColorButton::ColorButton(Object* Parent, int resId,COLORREF Col) : Object(Parent) { +hWnd=GetDlgItem(ParentHWnd,resId); +Color=Col; +} + +BOOL ColorButton::WndProc(TMSG &Msg) +{ + + switch (Msg.Msg) + { + case WM_COMMAND: + if (::GetColor(ParentHWnd,Color)) InvalidateRect(hWnd,NULL,TRUE); + break; + + case WM_DRAWITEM: + { + LPDRAWITEMSTRUCT drawInfo = (LPDRAWITEMSTRUCT) Msg.lParam; /* item-drawing information */ + switch (drawInfo->itemAction) + { + case ODA_DRAWENTIRE: + { + HPEN DefPen=(HPEN) SelectObject(drawInfo->hDC,GetStockObject(BLACK_PEN)); + HBRUSH Brush=(HBRUSH)CreateSolidBrush(Color); + HBRUSH DefBrush=(HBRUSH) SelectObject(drawInfo->hDC,Brush); + Rectangle(drawInfo->hDC,drawInfo->rcItem.left,drawInfo->rcItem.top,drawInfo->rcItem.right,drawInfo->rcItem.bottom); + SelectObject(drawInfo->hDC,DefBrush); + SelectObject(drawInfo->hDC,DefPen); + DeleteObject(Brush); + return TRUE; + } + case ODA_SELECT: + return TRUE; + + case ODA_FOCUS: + return TRUE; + } + } + break; + + } + return FALSE; +} + +COLORREF ColorButton::GetColor() +{ + return Color; +} + diff --git a/Win/Classlib/colorbtn.h b/Win/Classlib/colorbtn.h new file mode 100755 index 0000000..1cd4840 --- /dev/null +++ b/Win/Classlib/colorbtn.h @@ -0,0 +1,11 @@ +// colorbtn.h + +class ColorButton : Object { +private: + COLORREF Color; +public: + ColorButton(Object* , int, COLORREF); + virtual BOOL WndProc(TMSG &); + BOOL Create() { return TRUE; } + COLORREF GetColor(); +}; diff --git a/Win/Classlib/ddewnd.cpp b/Win/Classlib/ddewnd.cpp new file mode 100755 index 0000000..454b7db --- /dev/null +++ b/Win/Classlib/ddewnd.cpp @@ -0,0 +1,140 @@ +// dde stuff +#include +#pragma hdrstop + +#include + +#include + +EV_START(DDEWindow) + EV_MESSAGE(WM_DDE_TERMINATE,WMDdeTerminate) + EV_MESSAGE(WM_DDE_ACK,WMDdeAck) + EV_MESSAGE(WM_DDE_DATA,WMDDEData) +EV_END + +//derived windiw should call this if overridden +void DDEWindow::Destroy() +{ + if (ServerWindow) DDETerminate(); +} + +BOOL DDEWindow::DDEInitialise(char *App,char *Topic) +{ + ServerWindow=NULL; + ATOM aApp=GlobalAddAtom(App); + ATOM aTopic=GlobalAddAtom(Topic); + SendMessage((HWND)-1, SentMessage=WM_DDE_INITIATE, (WPARAM) hWnd, MAKELONG(aApp, aTopic)); + GlobalDeleteAtom(aApp); + GlobalDeleteAtom(aTopic); + // Ack should have come already + SentMessage=0; + return (ServerWindow!=NULL); +} + +void DDEWindow::DDETerminate() +{ + HWND W = ServerWindow; + ServerWindow = NULL; + if ( IsWindow( W ) ) PostMessage( W, WM_DDE_TERMINATE, (WPARAM) hWnd, 0 ); +} + +BOOL DDEWindow::WMDdeTerminate(TMSG &Msg) +{ + // server is terminating? + if ( (HWND) Msg.wParam == ServerWindow ) DDETerminate(); + return TRUE; +} + +BOOL DDEWindow::WMDdeAck(TMSG &Msg) +{ + switch (SentMessage) + { + case WM_DDE_INITIATE: + { + if ( ServerWindow == NULL ) ServerWindow = (HWND) Msg.wParam; + else PostMessage( (HWND) Msg.wParam, WM_DDE_TERMINATE, (LPARAM) hWnd,0); // already got response + GlobalDeleteAtom( LOWORD(Msg.lParam) ); + GlobalDeleteAtom( HIWORD(Msg.lParam) ); + break; + } + case WM_DDE_EXECUTE: + { + GlobalFree( (HGLOBAL) HIWORD(Msg.lParam) ); + SentMessage = 0; + WORD Status=LOWORD(Msg.lParam); + AckFlag=((DDEACK*)&Status)->fAck; +// SetFocus( hWnd ); // ?? + break; + } + case WM_DDE_REQUEST: + GlobalDeleteAtom( HIWORD(Msg.lParam) ); + SentMessage = 0; + break; + } + return TRUE; +} + +void DDEWindow::Wait() +{ + DWORD Time=GetCurrentTime(); + MSG Msg; + while ( SentMessage && (GetCurrentTime() - Time < 3000) ) + { + if (PeekMessage( &Msg, hWnd, 0, 0, PM_REMOVE ) ) + { + TranslateMessage(&Msg); + DispatchMessage(&Msg); + } + } +} + +BOOL DDEWindow::DDESend(char *c) +{ + HGLOBAL HCommands = GlobalAlloc( GHND | GMEM_DDESHARE, strlen(c)); + if ( !HCommands ) return FALSE; + + LPSTR lpCommands = (LPSTR) GlobalLock( HCommands ); // lock while putting in data + strcpy(lpCommands,c); + GlobalUnlock( HCommands ); + if ( PostMessage( ServerWindow, WM_DDE_EXECUTE, (WPARAM) hWnd, MAKELONG(0,HCommands) ) ) + SentMessage = WM_DDE_EXECUTE; + else + { + GlobalFree( HCommands ); + return FALSE; + } + + AckFlag=FALSE; + Wait(); + return AckFlag; +} + +BOOL DDEWindow::DDDRequest(char **d,char *Item) +{ + DataPtr=d; + SentMessage=WM_DDE_REQUEST; + ATOM aItem=GlobalAddAtom(Item); + PostMessage(ServerWindow,WM_DDE_REQUEST,(WPARAM) hWnd,MAKELPARAM(CF_TEXT,aItem)); + AckFlag=FALSE; + Wait(); + return AckFlag; +} + +BOOL DDEWindow::WMDDEData(TMSG &Msg) +{ + SentMessage=0; + HGLOBAL hData=(HGLOBAL) LOWORD(Msg.lParam); + if (hData==NULL) return FALSE; + else + { + DDEDATA *DDEData=(DDEDATA *) GlobalLock(hData); + if (DDEData->fAckReq) PostMessage(ServerWindow,WM_DDE_ACK,(WPARAM) hWnd,MAKELPARAM(0x8000,HIWORD(Msg.lParam))); + else GlobalDeleteAtom(HIWORD(Msg.lParam)); + *DataPtr=strdup((char*)DDEData->Value); + BOOL rel=DDEData->fRelease; + GlobalUnlock(hData); + if (rel) GlobalFree(hData); + } + return AckFlag=TRUE; +} + diff --git a/Win/Classlib/ddewnd.h b/Win/Classlib/ddewnd.h new file mode 100755 index 0000000..62e82d4 --- /dev/null +++ b/Win/Classlib/ddewnd.h @@ -0,0 +1,24 @@ +// ddehnd.h + +class DDEWindow : public Window +{ +public: + DDEWindow(Object *Parent,char *Title) : Window(Parent,Title){} + BOOL DDEInitialise(char *App,char *Topic); + BOOL DDDRequest(char **d,char *Item); + BOOL DDESend(char *c); + void DDETerminate(); +private: + HWND ServerWindow; + UINT SentMessage; + char **DataPtr; + BOOL AckFlag; + + void Wait(); + BOOL WMDdeTerminate(TMSG &); + BOOL WMDdeAck(TMSG &); + BOOL WMDDEData(TMSG &); + virtual void Destroy(); + +EV_ENABLE(DDEWindow) +}; \ No newline at end of file diff --git a/Win/Classlib/dialog.cpp b/Win/Classlib/dialog.cpp new file mode 100755 index 0000000..ddc74e5 --- /dev/null +++ b/Win/Classlib/dialog.cpp @@ -0,0 +1,189 @@ +// Dialog definitions ***************************** + +#include +#pragma hdrstop + +#include +#include + +#include +#include + +Dialog *D; + +BOOL PASCAL DialogHandler (HWND hDlg, UINT uMessage, + WPARAM wParam, LPARAM lParam); + +Dialog::Dialog(Object *Parent ,int Id, char *Sect) : Object(Parent) +{ + lpProcDialog = (DLGPROC) MakeProcInstance( (FARPROC)DialogHandler, App::hInstance); + ID=Id; + hWnd=0; + Section=Sect; +} + +Dialog::~Dialog() +{ + if (hWnd) + { + if (IsModal) ::EndDialog(hWnd,IDCANCEL); + else DestroyWindow(hWnd); //destroy modeless dialog + } + FreeProcInstance( (FARPROC)lpProcDialog); +} + +int Dialog::Execute() +{ + IsModal=TRUE; + D=this; // temp storage to initialise + return DialogBox(App::hInstance, + MAKEINTRESOURCE(ID), ParentHWnd,lpProcDialog); +} + +BOOL Dialog::Create() +{ + IsModal=FALSE; + D=this; + // modeless dialog creation + hWnd=CreateDialog(App::hInstance, + MAKEINTRESOURCE(ID), ParentHWnd,lpProcDialog); + return hWnd!=NULL; +} + +BOOL Dialog::SetupWindow() { return TRUE; } + +void Dialog::EndDialog(int) {} + +// Call from EndDialog() +void Dialog::SaveDialogPos() +{ + RECT rc; + GetWindowRect(hWnd,&rc); + WriteIniString(Section,"Position",String()<< (int) rc.left << ',' << (int) rc.top); +} + +// call this from SetupWindow +void Dialog::GetDialogPos() +{ + RECT rc; + // if this puts dialog completely offscreen, then dont do it??? + String S; + ReadIniString(Section,"Position",S); + if (!S.Empty()) + { + Parse P(S); + P >> rc.left >> rc.top; + SetWindowPos(hWnd,0,rc.left,rc.top,0,0,SWP_NOSIZE | SWP_NOZORDER); + } +} + +void Dialog::CentreDlgOnParent() +{ + RECT rc; + GetWindowRect(ParentHWnd,&rc); + int x=(rc.left+rc.right)/2; + int y=(rc.top+rc.bottom)/2; + GetClientRect(hWnd,&rc); + SetWindowPos(hWnd,0,x-rc.right/2,y-rc.bottom/2,0,0,SWP_NOSIZE | SWP_NOZORDER); +} + +void Dialog::KillDialog(int Value) +{ + if (Value==IDOK) + { + if (!CanClose()) return; + if (Section) SaveDialogPos(); + } + + EndDialog(Value); + if (IsModal) ::EndDialog(hWnd, Value); + else + { + DestroyWindow(hWnd); + } +} + +void Dialog::Paint(HDC, BOOL, RECT&) {} + +BOOL Dialog::ChildIDMessage(UINT id ,TMSG &Msg) +{ + return ChildMessage(GetDlgItem(hWnd,id),Msg); +} + +BOOL Dialog::ChildMessage(HWND hw,TMSG &Msg) +{ + Object *Child=FirstChild(); + while (Child) + { + if (Child->hWnd==hw) return Child->WndProc(Msg); + Child=NextChild(Child); + } + return FALSE; +} + + +// if breakpoint is set in this code then dialogs lose their modalness ????? + +BOOL PASCAL DialogHandler (HWND hDlg, UINT uMessage, + WPARAM wParam, LPARAM lParam) + +{ +//allow user to grab any message + Dialog *Dlg; + if ( (Dlg=(Dialog*)WindowInfo::Find(hDlg) )==NULL) + { + if (D==NULL) return FALSE; + new WindowInfo((Object*) (Dlg=D),hDlg); + D=NULL; + } + TMSG Msg={uMessage,wParam,lParam,0}; + if (Dlg->EV_FIND(Msg)) return TRUE; + + switch (uMessage) + { + + case WM_INITDIALOG: + Dlg->hWnd=hDlg; + Dlg->SetupWindow(); + if (Dlg->Section) Dlg->GetDialogPos(); + return TRUE; + + case WM_DESTROY: //if main window? + if (Dlg==App::MainWindow) PostQuitMessage(0); + WindowInfo::Delete(hDlg); + Dlg->hWnd=NULL; + return TRUE; + + case WM_PAINT: + PAINTSTRUCT ps; + BeginPaint(Dlg->hWnd, (LPPAINTSTRUCT) &ps); + Dlg->Paint(ps.hdc,ps.fErase,ps.rcPaint); + EndPaint(Dlg->hWnd, (LPPAINTSTRUCT) &ps); + return TRUE; + + case WM_DRAWITEM: + return Dlg->ChildIDMessage((UINT) wParam,Msg); + #ifndef WIN32 + case WM_CTLCOLOR: + return Dlg->ChildMessage((HWND) LOWORD(lParam),Msg); + #endif + case WM_COMMAND: + switch (wParam) + { + case IDOK: + case IDCANCEL: + Dlg->KillDialog(wParam); + return (TRUE); + + default: +#ifndef WIN32 + return Dlg->ChildMessage((HWND) LOWORD(lParam),Msg); +#else + return Dlg->ChildMessage((HWND) lParam,Msg); +#endif + } // switch (wParam) + } // switch (uMessage) + + return FALSE; +} + diff --git a/Win/Classlib/dialog.h b/Win/Classlib/dialog.h new file mode 100755 index 0000000..2898cc7 --- /dev/null +++ b/Win/Classlib/dialog.h @@ -0,0 +1,23 @@ +// Dialog.h + +class Dialog : public Object { + DLGPROC lpProcDialog; + int ID; +public: + BOOL IsModal; + char *Section; + Dialog(Object *,int,char *Sect=NULL); + virtual ~Dialog(); + int Execute(); + virtual BOOL SetupWindow(); + virtual void Paint(HDC, BOOL, RECT &); + virtual void EndDialog(int Result); + BOOL Create(); + void KillDialog(int); + void SaveDialogPos(); + void GetDialogPos(); + void CentreDlgOnParent(); + + BOOL ChildIDMessage(UINT id ,TMSG &); + BOOL ChildMessage(HWND hw,TMSG &); +}; diff --git a/Win/Classlib/dlgwnd.cpp b/Win/Classlib/dlgwnd.cpp new file mode 100755 index 0000000..278da9b --- /dev/null +++ b/Win/Classlib/dlgwnd.cpp @@ -0,0 +1,78 @@ +// DlgWindow definitions ******************************* +// Duplicates TWindow somewhat + +#include +#pragma hdrstop + + +DlgWindow::DlgWindow(Object *Parent, int Id) : Dialog(Parent,Id) +{ + Menu = NULL; + OldStyle = 0; +} + +void DlgWindow::AssignMenu(LPSTR menu) +{ + HMENU OldMenu,hM; + if (HIWORD(Menu)) delete[] Menu; + + if (HIWORD(menu)) Menu = strdup(menu); + else Menu = menu; + + if (hWnd) { + OldMenu=GetMenu(hWnd); + hM=menu ? LoadMenu(App::hInstance,menu) : (HMENU) NULL; + SetMenu(hWnd,hM); + if (OldMenu && !Menu) Resize( -GetSystemMetrics(SM_CYMENU) ); + if (!OldMenu && Menu) Resize( GetSystemMetrics(SM_CYMENU) ); + + if (OldMenu) DestroyMenu(OldMenu); + } +} + +void DlgWindow::AssignMenu(int MenuId) +{ + AssignMenu((LPSTR)MAKEINTRESOURCE(MenuId)); +} + +BOOL DlgWindow::Create() +{ + BOOL ret=Dialog::Create(); + if (ret && Menu) + { + HMENU hM=LoadMenu(App::hInstance,Menu); + SetMenu(hWnd,hM); + // resize dialog + Resize(GetSystemMetrics(SM_CYMENU)); + } + return ret; +} + +void DlgWindow::ShowTitleBar() +{ + Style=GetWindowLong(hWnd,GWL_STYLE); // need this ? + Style|=WS_CAPTION | OldStyle; + SetWindowLong(hWnd,GWL_STYLE,Style); + // resize dialog + Resize( GetSystemMetrics(SM_CYCAPTION) ); +} + +void DlgWindow::HideTitleBar() +{ + Style=GetWindowLong(hWnd,GWL_STYLE); // need this ? + OldStyle=Style & ( WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); + Style&=~( (WS_CAPTION & ~WS_BORDER) | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); + SetWindowLong(hWnd,GWL_STYLE,Style); + // resize dialog + Resize( -GetSystemMetrics(SM_CYCAPTION) ); +} + +void DlgWindow::Resize(int h) +{ + RECT rc; + GetWindowRect(hWnd,&rc); + int height=rc.bottom-rc.top+h; + SetWindowPos(hWnd,NULL, rc.left, rc.top-h, rc.right-rc.left, height , SWP_NOZORDER | SWP_DRAWFRAME); + ShowWindow(hWnd,SW_SHOW); +} + diff --git a/Win/Classlib/dlgwnd.h b/Win/Classlib/dlgwnd.h new file mode 100755 index 0000000..f59068a --- /dev/null +++ b/Win/Classlib/dlgwnd.h @@ -0,0 +1,17 @@ +// dlgwnd.h + +// cannot devive from Window and Dialog as conficts arise +class DlgWindow : public Dialog { + long OldStyle; + void Resize(int); +public: + DlgWindow(Object *, int); + LPSTR Menu; + int x,y,w,h; + DWORD Style; + void AssignMenu(LPSTR m); + void AssignMenu(int m); + BOOL Create(); + void ShowTitleBar(); + void HideTitleBar(); +}; diff --git a/Win/Classlib/dll.h b/Win/Classlib/dll.h new file mode 100755 index 0000000..3de7809 --- /dev/null +++ b/Win/Classlib/dll.h @@ -0,0 +1,40 @@ +// dll class +#ifndef _DLL_H +#define _DLL_H + +class DLL +{ +public: + DLL(char*n) + { + Name=strdup(n); + Loaded=FALSE; + } + ~DLL() + { + Unload(); + delete[] Name; + } + BOOL Load(); + void Unload() + { + if (Loaded) FreeLibrary(Inst); + Loaded=FALSE; + } + HINSTANCE Inst; + BOOL Loaded; + char *Name; +}; + +inline BOOL DLL::Load() +{ + Inst=LoadLibrary(Name); +#ifdef WIN32 + if (!Inst) return FALSE; +#else + if (Inst +#pragma hdrstop + +#include + +Edit::Edit(Object *Parent,int Id,char *Init,int Len) : Object(Parent) +{ + ID=Id; + hWnd=GetDlgItem(ParentHWnd,ID); + if (Len>0) SendMessage(hWnd,EM_LIMITTEXT,(WPARAM) Len-1,0); + new WindowInfo(this,hWnd); + SubClass(); +// had to move this to end for WIN32 ('95) ?? + if (Init) SetString(Init); +} + +BOOL Edit::Create() +{ // Exists in dialog + return TRUE; +} + +Edit::~Edit() +{ + WindowInfo::Delete(hWnd); +} + +int Edit::GetLength() +{ + return SendMessage(hWnd,WM_GETTEXTLENGTH,0,0); +} + +char *Edit::GetString(char *s,int BufSize) +{ + if (s==NULL) + { + BufSize=GetLength()+1; + s=new char[BufSize]; + } + +// GetDlgItemText(ParentHWnd,ID,s,BufSize); + GetWindowText(hWnd,s,BufSize); + return s; +} + +void Edit::SetString(char *s) +{ + SetWindowText(hWnd,s); +// SetDlgItemText(ParentHWnd,ID,s); +} + +void Edit::AddString(char *s) +{ + int BufSize=GetLength()+strlen(s)+1; + char *temp=new char[BufSize]; + GetString(temp,BufSize); + strcat(temp,s); + SetString(temp); + delete[] temp; +} + +LRESULT CALLBACK EditProc( HWND hWnd, UINT iMessage, WPARAM wParam, + LPARAM lParam ) +{ + TMSG Msg={iMessage,wParam,lParam,0}; + Edit *Obj=(Edit*) WindowInfo::Find(hWnd); + if (Obj) + { + Obj->WndProc(Msg); + return Msg.RetVal; + } + return 0; +} + +LRESULT Edit::DefProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam) +{ +#ifdef __BORLANDC__ + return CallWindowProc((FARPROC) OldProc, hWnd, Msg, wParam, lParam ); +#else + return CallWindowProc((WNDPROC) OldProc, hWnd, Msg, wParam, lParam ); +#endif +} + +void Edit::SubClass() +{ + OldProc=(WNDPROC) GetWindowLong(hWnd,GWL_WNDPROC); + SetWindowLong(hWnd,GWL_WNDPROC,(DWORD) EditProc); +} + diff --git a/Win/Classlib/edit.h b/Win/Classlib/edit.h new file mode 100755 index 0000000..a209e62 --- /dev/null +++ b/Win/Classlib/edit.h @@ -0,0 +1,19 @@ +// edit.h + +class Edit : public Object { + +public: + Edit(Object *,int,char *s=NULL,int Len=0); + virtual ~Edit(); + char *GetString(char *s=NULL,int BufSize=0); + void SetString(char *); + void AddString(char *); + int GetLength(); + LRESULT DefProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam); +private: + int ID; + BOOL Create(); + void SubClass(); + WNDPROC OldProc; +}; + diff --git a/Win/Classlib/eval.cpp b/Win/Classlib/eval.cpp new file mode 100755 index 0000000..c8f6749 --- /dev/null +++ b/Win/Classlib/eval.cpp @@ -0,0 +1,158 @@ +// evaluation routines +#include +#pragma hdrstop + +#include +#include + +#include "eval.h" + + +enum {TERM=-2,NONE=-1,LOR,LAND,REL,EQU,ADDI,MULT,UOP}; +enum {BRAK=-1,ADD,SUB,MUL,DIV,LE,LT,GE,GT,EQ,NE,SIN,COS,ATN,ACS,ASN,SQRT,AND,OR,UPL,UMN,MOD}; + +EvalParse::uOps EvalParse::UnaryOps[]= +{ + {"cos",COS,UOP}, + {"sin",SIN,UOP}, + {"atan",ATN,UOP}, + {"acos",ACS,UOP}, + {"asin",ASN,UOP}, + {"sqrt",SQRT,UOP}, + {"+",UPL,UOP}, + {"-",UMN,UOP}, + {"(",BRAK,NONE}, +}; + +EvalParse::bOps EvalParse::BinaryOps[]= +{ + {"+",1,ADD,ADDI}, + {"-",1,SUB,ADDI}, + {"*",1,MUL,MULT}, + {"/",1,DIV,MULT}, + {"=",1,EQ,EQU}, + {"<=",2,LE,REL}, // put before < + {"<>",2,NE,EQU}, // put before < + {"<",1,LT,REL}, + {">=",2,GE,REL}, // put before > + {">",1,GT,REL}, + {")",1,BRAK,NONE}, + {",",1,BRAK,TERM}, + {"and",3,AND,LAND}, + {"or",2,OR,LOR}, + {"mod",3,MOD,ADDI} +}; + +BOOL EvalParse::UnaryOp(int &Op,int &Prec) +{ + for (int i=0;iRightTerm);break; + case GE:LeftTerm=(LeftTerm>=RightTerm);break; + case AND:LeftTerm=((int) LeftTerm & (int) RightTerm);break; + case OR :LeftTerm=((int) LeftTerm | (int) RightTerm);break; + case UPL:LeftTerm=RightTerm;break; + case UMN:LeftTerm=-RightTerm;break; + case MOD:LeftTerm=((int) LeftTerm % (int) RightTerm);break; + default: + LeftTerm=RightTerm; + } + LeftOp=RightOp; + LeftPrec=RightPrec; +} + +int EvalParse::GetInt() +{ + Error=ERR_NONE; + return (int) Evaluate(); +} + +double EvalParse::GetDouble() +{ + Error=ERR_NONE; + return Evaluate(); +} + diff --git a/Win/Classlib/eval.h b/Win/Classlib/eval.h new file mode 100755 index 0000000..00fcd98 --- /dev/null +++ b/Win/Classlib/eval.h @@ -0,0 +1,43 @@ +// eval2.h + +#include + +enum {ERR_NONE,ERR_UNKNOWNID}; + +class EvalParse : public Parse +{ +public: + EvalParse(bhp Buf, long Size) : Parse(Buf,Size) {} + double Evaluate(); + int GetInt(); + double GetDouble(); + int Error; + +private: + double EvalTerm(); + void SubEval(int &LeftOp,int &LeftPrec,double &LeftTerm); + BOOL UnaryOp(int &Op,int &Prec); + void BinaryOp(int &Op,int &Prec); + + #define UOPS 9 + #define BOPS 15 + static struct uOps + { + char *Name; + int op; + int Prec; + } UnaryOps[UOPS]; + static struct bOps + { + char *Name; + int Len; + int op; + int Prec; + } BinaryOps[BOPS]; +}; + +inline double Evaluate(char *Str) +{ + return EvalParse((bhp)Str,strlen(Str)).Evaluate(); +} + diff --git a/Win/Classlib/event.cpp b/Win/Classlib/event.cpp new file mode 100755 index 0000000..4df6eb9 --- /dev/null +++ b/Win/Classlib/event.cpp @@ -0,0 +1,66 @@ +// event handler class +#include +#pragma hdrstop + +#include "event.h" + +List EventList; +extern DWORD Time; + +Event::Event(GENERICEV *This,GenProc *GP,long Delay) : BaseNode() +{ + // take a copy of function + GProc=*GP; + Owner=This; + EvTime=Time+Delay; + AddToList(); +} + +Event::Event(NormProc P,long Delay) : BaseNode() +{ + // take a copy of function + NProc=P; + Owner=NULL; + EvTime=Time+Delay; + AddToList(); +} + +void Event::AddToList() +{ + // put onto Eventlist in crono order + Event *List=EventList.GetFirst(),*Prev=NULL; + while (List && EvTime>List->EvTime) + { + Prev=List; + EventList.GetNext(List); + } + if (Prev) EventList.AddAfter(this,Prev); + else EventList.AddHead(this); +} + +void Event::Do() +{ + //call proc + // remove from eventlist in case evlist is affected by call + EventList.Remove(this); + if (Owner) (Owner->*GProc)(); + else NProc(); + delete this; +} + +void Event::Test() +{ + Event *Ev; + Ev=EventList.GetFirst(); + while (Ev && Time>Ev->EvTime) + { + Ev->Do(); + Ev=EventList.GetFirst(); + } +} + +void Event::Clear() +{ + EventList.DeleteAll(); +} + diff --git a/Win/Classlib/event.h b/Win/Classlib/event.h new file mode 100755 index 0000000..904e636 --- /dev/null +++ b/Win/Classlib/event.h @@ -0,0 +1,35 @@ +// event class +#include + +class GENERICEV {}; + +typedef void (GENERICEV::*GenProc)(); +typedef void (*NormProc)(); + +class Event : public BaseNode +{ +public: + Event(GENERICEV *,GenProc *,long); + Event(NormProc,long); + void Do(); + void AddToList(); + GENERICEV *Owner; + GenProc GProc; + NormProc NProc; + + DWORD EvTime; + + static void Test(); + static void Clear(); +}; + +#define CLASSEVENT(CLASS,PROC,TIME) {\ + void (CLASS::*_P)() =&CLASS::PROC;\ + new Event((GENERICEV *)this,(GenProc*)&_P,TIME);} + +#define CLASSEVENT2(CLASS,PTR,PROC,TIME) {\ + void (CLASS::*_P)() =&CLASS::PROC;\ + new Event((GENERICEV *)PTR,(GenProc*)&_P,TIME);} + +#define EVENT(PROC,TIME) { new Event(PROC,TIME); } + diff --git a/Win/Classlib/filedlg.cpp b/Win/Classlib/filedlg.cpp new file mode 100755 index 0000000..64b87e1 --- /dev/null +++ b/Win/Classlib/filedlg.cpp @@ -0,0 +1,147 @@ +// custom file dialog +#include +#pragma hdrstop + +#include + +#include +#include + +CustFileDlg::CustFileDlg(Object *Parent ,int Id, char *Buf,int BufS,const char *aTitle,const char *Filt,int *FiltInd,long flgs) + : Object(Parent) +{ + ID=Id; + Buffer=Buf; + BufSize=BufS; + Title=aTitle; + Filters=Filt; + FiltIndex=FiltInd; + hWnd=0; + TrapOK=FALSE; + Flags=flgs; +} + +BOOL CustFileDlg::SetupWindow() { return TRUE; } +void CustFileDlg::Paint(HDC, BOOL, RECT &) {} +BOOL CustFileDlg::Create() { return TRUE; } +void CustFileDlg::FileSelected(char*) {} +void CustFileDlg::EndDialog(int) {} + +CustFileDlg::~CustFileDlg() +{ +} + +#define IDC_FILE 1120 + +BOOL CustFileDlg::WndProc(TMSG &Msg) +{ + if (Msg.Msg==FokMsg && TrapOK) + { + FileSelected(O.lpstrFile); + TrapOK=FALSE; + return TRUE; + } + switch (Msg.Msg) + { + case WM_INITDIALOG: + SetupWindow(); +// if (Ini) GetDialogPos(); + SetFocus(GetDlgItem(hWnd,IDOK)); + break; + + case WM_DESTROY: + hWnd=NULL; + break; + + case WM_PAINT: + PAINTSTRUCT ps; + BeginPaint(hWnd, (LPPAINTSTRUCT) &ps); + Paint(ps.hdc,ps.fErase,ps.rcPaint); + EndPaint(hWnd, (LPPAINTSTRUCT) &ps); + break; + + case WM_COMMAND: + switch (LOWWPARAM(Msg.wParam)) + { + case IDOK: + case IDCANCEL: + EndDialog(LOWWPARAM(Msg.wParam)); + break; + case IDC_FILE: + // only trap for open file + if (!(Flags & OFN_OVERWRITEPROMPT) && HIWLPARAM(Msg)==LBN_SELCHANGE) + { + // bit of a hack + // pick up FILEOK message + TrapOK=TRUE; + PostMessage(hWnd,WM_COMMAND,IDOK,0); + } + break; + + } // switch (wParam) + } // switch (uMessage) + + return EV_FIND(Msg); +} + +CustFileDlg *CustFileDlg::OpenDialog; + +#ifdef __BORLANDC__ +UINT _export CALLBACK CustFileDlg::MyOpenDlgProc(HWND hDlg,UINT Msg,WPARAM wParam,LPARAM lParam) +#else +UINT CALLBACK CustFileDlg::MyOpenDlgProc(HWND hDlg,UINT Msg,WPARAM wParam,LPARAM lParam) +#endif +{ + if (Msg==WM_INITDIALOG) OpenDialog->hWnd=hDlg; + TMSG aMsg={Msg,wParam,lParam,0}; + return OpenDialog->WndProc(aMsg); +} + +// OFN_EXPLORER - new look commdlg +// OFN_LONGNAMES - force long names for 3.x modules + + +int CustFileDlg::Execute() +{ + OpenDialog=this; + FName F(Buffer); + String Name(BufSize); + Name=F.GetName(); + String Dir; + F.GetDir(Dir); + + O.lStructSize=sizeof(OPENFILENAME); + O.hwndOwner=ParentHWnd; + O.hInstance=App::hInstance; + O.lpstrFilter=Filters; + O.lpstrCustomFilter=NULL; + O.nFilterIndex=FiltIndex ? *FiltIndex : 0; + O.lpstrFile=Name; + O.nMaxFile=Name.Size(); + O.lpstrFileTitle=NULL; + O.lpstrInitialDir=Dir; + O.lpstrTitle=Title; + O.Flags=Flags | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_ENABLEHOOK; + + if (ID>=0) + { + O.lpTemplateName=MAKEINTRESOURCE(ID); + O.Flags |= OFN_ENABLETEMPLATE; + } + O.lpstrDefExt=NULL; + //O.lCustData; + + O.lpfnHook=MyOpenDlgProc; + + FokMsg=RegisterWindowMessage(FILEOKSTRING); + + int ret= (O.Flags & OFN_OVERWRITEPROMPT) ? + GetSaveFileName(&O) : GetOpenFileName(&O); + + if (ret) + { + strcpy(Buffer,Name); + if (FiltIndex) *FiltIndex=O.nFilterIndex; + } + return ret; +} diff --git a/Win/Classlib/filedlg.h b/Win/Classlib/filedlg.h new file mode 100755 index 0000000..991fbda --- /dev/null +++ b/Win/Classlib/filedlg.h @@ -0,0 +1,31 @@ +// filedlg.h + +#define OFN_EXPLORER 0x00080000 // new look commdlg +#define OFN_LONGNAMES 0x00200000 // force long names for 3.x modules + +class CustFileDlg : public Object +{ +public: + CustFileDlg(Object *Parent ,int Id, char *Buf,int BufS,const char *aTitle,const char *Filt,int *FiltInd,long flgs=OFN_EXPLORER); + ~CustFileDlg(); + BOOL WndProc(TMSG &); + int Execute(); + virtual void FileSelected(char*); +private: + UINT FokMsg; + BOOL TrapOK; + int ID; + long Flags; + char *Buffer; + int BufSize; + const char *Title,*Filters; + int *FiltIndex; + virtual BOOL SetupWindow(); + virtual void EndDialog(int); + virtual void Paint(HDC, BOOL, RECT &); + BOOL Create(); + + OPENFILENAME O; + static CustFileDlg *OpenDialog; + static UINT PASCAL MyOpenDlgProc(HWND hDlg,UINT Msg,WPARAM wParam,LPARAM lParam); +}; diff --git a/Win/Classlib/findcol.cpp b/Win/Classlib/findcol.cpp new file mode 100755 index 0000000..8fe07c7 --- /dev/null +++ b/Win/Classlib/findcol.cpp @@ -0,0 +1,28 @@ +#include +#pragma hdrstop + +#define FACTOR(x) ((int)((x)*256)) + +int FindCol(int Red,int Green,int Blue,RGBQUAD *Cols,int PalSize) +{ + // find closest match + long a,b,c; + int NearCol; + unsigned long MinDiff,Diff; + MinDiff=0xffffffffL; + + for (int x=0;x +#pragma hdrstop + +#include +#include +#include +#include + +#include + +FName::FName(char *f) : String(MAX_PATH) +{ + Assign(f); +} + +FName::FName(char *p,char *n) : String(MAX_PATH) +{ + NewPath(p); + NewName(n); +} + +FName::FName(FName &F) : String(MAX_PATH) +{ + Assign(F); +} + +void FName::NewExt(char *e) +{ + if (strchr(GetName(),'.')) Len(rPos('.')); + *this << '.' << e; +} + +void FName::NewPath(char *p) +{ + String n(GetName()); + Assign(p); + // force trailing "\\" + if (!Empty() && Last()!='\\') *this << '\\'; + *this << n; +} + +void FName::NewBaseName(char *n) +{ + String S(GetExt()); + NewName(n); + if (!S.Null()) *this << '.' << S; +} + +void FName::NewName(char *n) +{ + if (!strchr(n,'\\')) // has relative path? + { + int p=rPos('\\'); + if (p>=0) Len(p+1); + } + AddString(n); +} + +char *FName::GetName() +{ + return Str+rPos('\\')+1; // -1 if not in +} + +char *FName::GetExt() +{ + return strchr(GetName(),'.') ? Str+rPos('.')+1 : NULL; +} + +void FName::GetBaseName(String &S) +{ + S=GetName(); + int n=S.Pos('.'); + if (n>=0) S.Len(n); +} + +void FName::GetDir(String &S) +{ + S=Str; + int n=rPos('\\'); + if (n>=0) S.Len(n); // no trailing "\\" +} + +void FName::AddToPath(char *Add) +{ + String S(GetName()); + NewName(""); + AddString(Add); + if (Last()!='\\') *this << '\\'; + AddString(S); +} + +long FName::TimeStamp() +{ +#ifdef WIN32 + HANDLE f=CreateFile(Str,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); + if (f==INVALID_HANDLE_VALUE) return -1; + FILETIME ft; + GetFileTime(f,NULL,NULL,&ft); + WORD Date,Time; + FileTimeToDosDateTime(&ft,&Date,&Time); + CloseHandle(f); + return Time+(Date<<16); +#else + int hand; + union Time + { + ftime ft; + unsigned long dw; + } ft; + if ((hand=open(Str,O_RDONLY))<0) return -1; + getftime(hand,&ft.ft); + close(hand); + return ft.dw; +#endif +} + +void FName::SetFileTime(long t) +{ +#ifdef WIN32 + HANDLE f=CreateFile(Str,GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); + if (f==INVALID_HANDLE_VALUE) return; + FILETIME ft; + DosDateTimeToFileTime(HIWORD(t),LOWORD(t),&ft); + ::SetFileTime(f,NULL,NULL,&ft); + CloseHandle(f); +#else + int hand; + union Time { + ftime ft; + DWORD dw; + } ft; + ft.dw=t; + if ((hand=open(Str,O_RDONLY))<0) return; + setftime(hand,&ft.ft); + close(hand); +#endif +} + +long FName::GetFileSize() +{ + int hand=open(Str,O_RDONLY); + long len=filelength(hand); + close(hand); + return len; +} + +BOOL FName::Exist() +{ + if (Empty()) return FALSE; + FILE *f=fopen(Str,"rb"); + if (f) + { + fclose(f); + return TRUE; + } + return FALSE; +} + +// expand relative references +void FName::Expand(char *Dir) +{ +// qualified name + if (Empty() || Pos(':')>=0 || Str[0]=='\\') return; + + int n=strlen(Dir); + if (Dir[n-1]=='\\') n--; + + char *Ptr=Str; + while (!strncmp(Ptr,"..\\",3)) + { + Ptr+=3; + while (n>0 && Dir[--n]!='\\'); + } + + String S(Ptr); + Assign(Dir); + Len(n); + *this << '\\' << S; +} + diff --git a/Win/Classlib/fname.h b/Win/Classlib/fname.h new file mode 100755 index 0000000..73f1d7b --- /dev/null +++ b/Win/Classlib/fname.h @@ -0,0 +1,65 @@ +//#include + + +#ifndef _fname_h +#define _fname_h + +#include + +#ifndef MAX_PATH +#define MAX_PATH 256 +#endif + +class FName : public String +{ +public: + FName() : String(MAX_PATH) { } + FName(char *); + FName(char *p,char *n); + FName(FName &); + + void NewPath(char *e); + void NewName(char *e); + void NewBaseName(char *n); + void NewExt(char *e) ; + void AddToPath(char *Add); + char *GetName(); + void GetBaseName(String&); + void GetDir(String&); + char *GetExt(); + long TimeStamp(); + void SetFileTime(long t); + long GetFileSize(); + + BOOL Exist(); + void Expand(char *Dir); + +// inherit string operators... + +// FName &operator=(char*s) { *((String*) this)=s; return *this; } + FName &operator=(char*s) { String::operator=(s); return *this; } +}; + +class TempFile : public FName +{ +public: + TempFile(char* Pre) + { + #ifdef WIN32 + FName Path; + GetTempPath(MAX_PATH,Path); + GetTempFileName(Path,Pre,0,*this); + #else + // int Dr=GetTempDrive(); + GetTempFileName(0,Pre,0,*this); + #endif + } + ~TempFile() + { + OFSTRUCT of; + OpenFile(*this,&of,OF_DELETE); + } +}; + + +#endif diff --git a/Win/Classlib/font.cpp b/Win/Classlib/font.cpp new file mode 100755 index 0000000..416c3cb --- /dev/null +++ b/Win/Classlib/font.cpp @@ -0,0 +1,58 @@ +#include +#pragma hdrstop + +#include + +void ShadowText(HDC dc,int x,int y,char *font,int size,char *string,COLORREF Col) +{ + HFONT Font=CreateFont(size,0,0,0,FW_BOLD,0,0,0, + ANSI_CHARSET,OUT_TT_PRECIS,CLIP_TT_ALWAYS,PROOF_QUALITY, + VARIABLE_PITCH | 4 | FF_DONTCARE,font); + HFONT OldFont=(HFONT) SelectObject(dc,Font); + SIZE sz; + GetTextExtentPoint(dc, string,strlen(string), &sz); + int BkMode=GetBkMode(dc); + SetBkMode(dc,TRANSPARENT); + COLORREF OldCol=SetTextColor(dc,RGB(0,0,0)); + TextOut(dc,x+1-sz.cx/2,y+1-sz.cy/2,string,strlen(string)); + SetTextColor(dc,Col); + TextOut(dc,x-sz.cx/2,y-sz.cy/2,string,strlen(string)); + SetTextColor(dc,OldCol); + SetBkMode(dc,BkMode); + SelectObject(dc,OldFont); + DeleteObject(Font); +} + +void CentreText(HDC dc,int x,int y,char *font,int size,char *string,COLORREF Col) +{ + HFONT Font=CreateFont(size,0,0,0,FW_NORMAL,0,0,0, + ANSI_CHARSET,OUT_TT_PRECIS,CLIP_TT_ALWAYS,PROOF_QUALITY, + VARIABLE_PITCH | 4 | FF_DONTCARE,font); + HFONT OldFont=(HFONT) SelectObject(dc,Font); + SIZE sz; + GetTextExtentPoint(dc, string,strlen(string), &sz); + int BkMode=GetBkMode(dc); + SetBkMode(dc,TRANSPARENT); + COLORREF OldCol=SetTextColor(dc,Col); + TextOut(dc,x-sz.cx/2,y-sz.cy/2,string,strlen(string)); + SetTextColor(dc,OldCol); + SetBkMode(dc,BkMode); + SelectObject(dc,OldFont); + DeleteObject(Font); +} + +void AngleText(HDC dc,int x,int y,char *font,int size,int rot,char *string,COLORREF Col) +{ + HFONT Font=CreateFont(size,0,rot,0,FW_NORMAL,0,0,0, + ANSI_CHARSET,OUT_TT_PRECIS,CLIP_TT_ALWAYS,PROOF_QUALITY, + VARIABLE_PITCH | 4 | FF_DONTCARE,font); + HFONT OldFont=(HFONT) SelectObject(dc,Font); + int BkMode=SetBkMode(dc,TRANSPARENT); + COLORREF OldCol=SetTextColor(dc,Col); + TextOut(dc,x,y,string,strlen(string)); + SetTextColor(dc,OldCol); + SetBkMode(dc,BkMode); + SelectObject(dc,OldFont); + DeleteObject(Font); +} + diff --git a/Win/Classlib/font.h b/Win/Classlib/font.h new file mode 100755 index 0000000..645fcd8 --- /dev/null +++ b/Win/Classlib/font.h @@ -0,0 +1,5 @@ +// font.h + +void ShadowText(HDC dc,int x,int y,char *font,int size,char *string,COLORREF Col); +void CentreText(HDC dc,int x,int y,char *font,int size,char *string,COLORREF Col); +void AngleText(HDC dc,int x,int y,char *font,int size,int rot,char *string,COLORREF Col); diff --git a/Win/Classlib/hashwnd.cpp b/Win/Classlib/hashwnd.cpp new file mode 100755 index 0000000..60484f1 --- /dev/null +++ b/Win/Classlib/hashwnd.cpp @@ -0,0 +1,67 @@ +// hash window stuff + +#include +#pragma hdrstop + +// cannot make hash table in constructor as GetEvInfo still points to pure virtual function + +HashWindow::HashWindow(Object *Parent,char *Title,char *N,int HashSize) : Window(Parent,Title,N) +{ + // allocate hash table + HashTable=new int[HashSize]; + // initialise hash table + for (int i=0;iMsg) + { + Hash=(Ptr->Msg ^ (Ptr->Msg>>8)) & Mask; + // add msg onto Hash list + Ptr->Next=HashTable[Hash]; + HashTable[Hash]=(char) (Ptr-Start); + Ptr++; + } +} + +HashWindow::~HashWindow() +{ + // free hash table + delete[] HashTable; +} + +// define new search routine +BOOL HashWindow::EV_SEARCH(GEN_EV_INFO *EV_TABLE,TMSG &Msg,GENERIC *Owner) +{ + int First=HashTable[(Msg.Msg ^ (Msg.Msg>>8))&Mask]; + if (First<0) return FALSE; + GEN_EV_INFO *Ptr=EV_TABLE+First; + while (Ptr->Msg!=Msg.Msg || ( Ptr->Type==0 && Ptr->Id!=Msg.wParam ) ) + { + if ((First=Ptr->Next)<0) return FALSE; + Ptr=EV_TABLE+First; + } + switch (Ptr->Type) + { + case 0: + ( Owner->*Ptr->Proc)(); + break; + + case 1: + return (Owner->*( (Proc1) Ptr->Proc) ) (Msg); + } + + return TRUE; +} + diff --git a/Win/Classlib/hashwnd.h b/Win/Classlib/hashwnd.h new file mode 100755 index 0000000..e6071c7 --- /dev/null +++ b/Win/Classlib/hashwnd.h @@ -0,0 +1,14 @@ +// hashwnd.h + +// hash window + +class HashWindow : public Window { +public: + HashWindow::HashWindow(Object *Parent,char *Title,char *N=NULL,int HashSize=256); + ~HashWindow(); + BOOL Create(); + void MakeHashTable(); + int *HashTable,Mask; + BOOL EV_SEARCH(GEN_EV_INFO *,TMSG&,GENERIC *); + virtual GEN_EV_INFO *GetEvInfo()=0; +}; diff --git a/Win/Classlib/inistuff.cpp b/Win/Classlib/inistuff.cpp new file mode 100755 index 0000000..993a915 --- /dev/null +++ b/Win/Classlib/inistuff.cpp @@ -0,0 +1,282 @@ +// ini stuff +#include +#pragma hdrstop + +#include +#include +#include + +#include + +#ifdef WIN16 + +BOOL ReadIniString(const char *Section,const char *Item,String &S,BOOL) +{ + String Temp(256); + if (GetPrivateProfileString(Section,Item,"",Temp,Temp.Size(),App::IniFile)) + { + S=Temp; + return TRUE; + } + return FALSE;; +} + +void ReadIniBool(const char *Section,const char *Item,BOOL &Bool,BOOL) +{ + char temp[20]; + if (GetPrivateProfileString(Section,Item,"",temp,20,App::IniFile)) + Bool=(stricmp(temp,"Yes")==0); +} + +void WriteIniString(const char *Section,const char *Item,char *Val,BOOL) +{ + WritePrivateProfileString(Section,Item,Val,App::IniFile); +} + +void WriteIniBool(const char *Section,const char *Item,BOOL Bool,BOOL) +{ + WritePrivateProfileString(Section,Item,Bool ? "Yes":"No",App::IniFile); +} + +void ReadIniInt(const char *Section,const char *Item,int &Int,BOOL) +{ + Int=GetPrivateProfileInt(Section,Item,Int,App::IniFile); +} + +void WriteIniInt(const char *Section,const char *Item,int Int,BOOL) +{ + WritePrivateProfileString(Section,Item,String() << Int,App::IniFile); +} + +void ReadIniInt(const char *Section,const char *Item,long &Int,BOOL) +{ + String S(12); + GetPrivateProfileString(Section,Item,"",S,S.Size(),App::IniFile); + if (!S.Empty()) S >> Int; +} + +void WriteIniInt(const char *Section,const char *Item,long Int,BOOL) +{ + WritePrivateProfileString(Section,Item,String() << Int,App::IniFile); +} + +#else + +// Win32 registry version + +BOOL ReadIniString(const char *Section,const char *Item,String &S,BOOL Global) +{ + String Temp(256); + DWORD Size=Temp.Size(); + if (RegQueryValueEx(Global ? App::Machine : App::User,String() << (char*) Section << ':' << (char*) Item,NULL,NULL,(LPBYTE) Temp.Str,&Size)==ERROR_SUCCESS) + { + S=Temp; + return TRUE; + } + return FALSE; +} + +void WriteIniString(const char *Section,const char *Item,char *Val,BOOL Global) +{ + HKEY Key=Global ? App::Machine : App::User; + String Value; + Value << (char*) Section << ':' << (char*) Item; + + if (Val) RegSetValueEx(Key,Value,0,REG_SZ,(LPBYTE) Val,strlen(Val)+1); + else RegDeleteValue(Key,Value); +} + +void ReadIniBool(const char *Section,const char *Item,BOOL &Bool,BOOL Global) +{ + DWORD Size=sizeof(Bool); + RegQueryValueEx(Global ? App::Machine : App::User,String() << (char*) Section << ':' << (char*) Item,NULL,NULL,(LPBYTE) &Bool,&Size); +} + +void WriteIniBool(const char *Section,const char *Item,BOOL Bool,BOOL Global) +{ + RegSetValueEx(Global ? App::Machine : App::User,String() << (char*) Section << ':' << (char*) Item,0,REG_DWORD,(LPBYTE) &Bool,sizeof(Bool)); +} + +void ReadIniInt(const char *Section,const char *Item,int &Int,BOOL Global) +{ + DWORD Size=sizeof(Int); + RegQueryValueEx(Global ? App::Machine : App::User,String() << (char*) Section << ':' << (char*) Item,NULL,NULL,(LPBYTE) &Int,&Size); +} + +void WriteIniInt(const char *Section,const char *Item,int Int,BOOL Global) +{ + RegSetValueEx(Global ? App::Machine : App::User,String() << (char*) Section << ':' << (char*) Item,0,REG_DWORD,(LPBYTE) &Int,sizeof(Int)); +} + +// long duplicates int but keeps 16 bit interface + +void ReadIniInt(const char *Section,const char *Item,long &Int,BOOL Global) +{ + DWORD Size=sizeof(Int); + RegQueryValueEx(Global ? App::Machine : App::User,String() << (char*) Section << ':' << (char*) Item,NULL,NULL,(LPBYTE) &Int,&Size); +} + +void WriteIniInt(const char *Section,const char *Item,long Int,BOOL Global) +{ + RegSetValueEx(Global ? App::Machine : App::User,String() << (char*) Section << ':' << (char*) Item,0,REG_DWORD,(LPBYTE) &Int,sizeof(Int)); +} +#endif + +int MruNum,BeforeID; +HWND MruHw; +#define MRU_BREAKLEN 40 +SimpleList MruList; +SimpleList MruList2; + +BOOL DeleteBlock(HMENU hm,int id) +{ + int id2=id; + while (DeleteMenu(hm,id2,MF_BYCOMMAND)) id2++; + return id2>id; +} + +void AddBlock(HMENU hm,SimpleList &sList,int &Pos,int &Num,int ID) +{ + slIterator it(sList); + while (it()) + { + // make short version of name, double up &'s + String S=it.Get(); + + if (S.Len()>MRU_BREAKLEN) + { + // keep first 2 and last 2 idenifiers + int n=2,i=0,j; + do + { + i=1+S.Pos('\\',i); + if (S[i]=='\\') i++; + else if (--n==0) break; + } while (i>0); + + n=2,j=S.Len(); + do + { + j=S.rPos('\\',j); + if (--n==0) break; + } while (j>=0); + // remove from i to j + if (i>0 && j>i) + { + S.Remove(i,j-i); + S.Insert("...",i); + } + } + + int i=0; + while ((i=1+S.Pos('&',i))>0) + { + S.Insert('&',i++); + } + + String T; + T << '&' << Num << ' ' << S; + + InsertMenu(hm,Pos++,MF_BYPOSITION,ID+Num++,T); + it++; + } +} + + +void SetMru(HWND hw,UINT before) +{ + BeforeID=before; + MruHw=hw; + HMENU hMenu=GetMenu(hw); + hMenu=GetSubMenu(hMenu,0); + if (hMenu==NULL) return; + int Pos; + BOOL Del=DeleteBlock(hMenu,MRU_ID+1); + BOOL Del2=DeleteBlock(hMenu,MRU2_ID+1); + + // get pos of before id + int n=GetMenuItemCount(hMenu); + for (Pos=0;Pos1) InsertMenu(hMenu,Pos,MF_BYPOSITION | MF_SEPARATOR,0,NULL); + Pos++; + + int Num2=Num; + AddBlock(hMenu,MruList2,Pos,Num2,MRU2_ID+1-Num); + if (!Del2 && Num2>Num) InsertMenu(hMenu,Pos,MF_BYPOSITION | MF_SEPARATOR,0,NULL); +} + +void ReadMru(int Num) +{ + MruNum=Num; + String S; + int i=1; + while (ReadIniString("MRU",String()< &sList,char *Add) +{ + slIterator it(sList); + while (it()) + { + if (it.Get()==Add) + { + // remove it + it.Remove(); + break; + } + it++; + } + sList.AddHead(String(Add)); + if (sList.Count()>MruNum) sList.RemoveTail(); + // rebuild mru list + SetMru(MruHw,BeforeID); +} + +void AddToMru(char*Add) +{ + AddToList(MruList,Add); +} + +void AddToMru2(char*Add) +{ + AddToList(MruList2,Add); +} + +void WriteMru() +{ + int i=1; + slIterator it(MruList); + while (it()) + { + WriteIniString("MRU",String()< it2(MruList2); + while (it2()) + { + WriteIniString("MRU2",String()< + +BOOL ReadIniString(const char *Section,const char *Item,String &S,BOOL Global=FALSE); +void WriteIniString(const char *Section,const char *Item,char *Val,BOOL Global=FALSE); +void ReadIniBool(const char *Section,const char *Item,BOOL &Bool,BOOL Global=FALSE); +void WriteIniBool(const char *Section,const char *Item,BOOL Bool,BOOL Global=FALSE); +void ReadIniInt(const char *Section,const char *Item,int &Int,BOOL Global=FALSE); +void WriteIniInt(const char *Section,const char *Item,int Int,BOOL Global=FALSE); +void ReadIniInt(const char *Section,const char *Item,long &Int,BOOL Global=FALSE); +void WriteIniInt(const char *Section,const char *Item,long Int,BOOL Global=FALSE); + +void SetMru(HWND,UINT); +void ReadMru(int); +void AddToMru(char*); +void AddToMru2(char*); +void WriteMru(); +extern SimpleList MruList; +extern SimpleList MruList2; +#define MRU_ID 300 +#define MRU2_ID 350 +#define MRU_MAX 9 diff --git a/Win/Classlib/keypress.cpp b/Win/Classlib/keypress.cpp new file mode 100755 index 0000000..d2a8b59 --- /dev/null +++ b/Win/Classlib/keypress.cpp @@ -0,0 +1,138 @@ +// keypress +#include +#pragma hdrstop +#include + +#include + +// Key Press Dialog ************************************** + +EV_START(KeyPressDialog) + EV_MESSAGE(WM_SYSKEYDOWN, WMSysKeyDown) + EV_MESSAGE(WM_KEYDOWN, WMKeyDown) + EV_MESSAGE(WM_KILLFOCUS, WMKillFocus) + EV_MESSAGE(WM_LBUTTONDOWN, WMLButtonDown) + EV_MESSAGE(WM_MBUTTONDOWN, WMMButtonDown) + EV_MESSAGE(WM_RBUTTONDOWN, WMRButtonDown) + EV_MESSAGE(WM_TIMER, WMTimer) +EV_END + +KeyPressDialog *Dlg; + +KeyPressDialog::KeyPressDialog(Object *Parent,int ID,int *K,int JID) : Window(Parent,"Press a Key/Button") +{ + Jid=JID; + Key=K; + //Scan=0; + Id=ID; + Style=WS_OVERLAPPED | WS_VISIBLE; + HWND hw=GetDlgItem(Parent->hWnd,ID); + RECT rc; + GetWindowRect(hw,&rc); + x=(rc.right+rc.left)/2-100; + y=(rc.top+rc.bottom)/2-20; + w=200; + h=40; +} + +BOOL KeyPressDialog::SetupWindow() +{ + SetCapture(hWnd); + if (Jid>=0) SetTimer(hWnd,0,100,NULL); + return TRUE; +} + +/* +void SetKeyScanText(HWND hWnd,int ID,long Scan) +{ + char temp[20]; + GetKeyNameText(Scan,temp,20); + SetDlgItemText(hWnd,ID,temp); +} +*/ + +void KeyPressDialog::Destroy() +{ + SetKeyText(Parent->hWnd,Id,*Key); + //*Key=MapVirtualKey(Scan>>16,1); + ReleaseCapture(); + if (Jid>=0) KillTimer(hWnd,0); + delete Dlg; +} + +BOOL KeyPressDialog::WMKillFocus(TMSG&) +{ + DestroyWindow(); + return TRUE; +} + +BOOL KeyPressDialog::WMSysKeyDown(TMSG &Msg) +{ // gets alt and F10 + //Scan=lParam; + *Key=Msg.wParam; + DestroyWindow(); + return TRUE; +} + + +BOOL KeyPressDialog::WMKeyDown(TMSG &Msg) +{ + //Scan=lParam; + *Key=Msg.wParam; + DestroyWindow(); + return TRUE; +} + +BOOL KeyPressDialog::WMLButtonDown(TMSG&) +{ + *Key=VK_LBUTTON; + DestroyWindow(); + return TRUE; +} + +BOOL KeyPressDialog::WMMButtonDown(TMSG&) +{ + *Key=VK_MBUTTON; + DestroyWindow(); + return TRUE; +} + +BOOL KeyPressDialog::WMRButtonDown(TMSG&) +{ + *Key=VK_RBUTTON; + DestroyWindow(); + return TRUE; +} + +BOOL KeyPressDialog::WMTimer(TMSG&) +{ + JOYINFO ji; + joyGetPos(Jid,&ji); + // avoid multiple bits + if (ji.wButtons & JOY_BUTTON1) *Key=-JOY_BUTTON1; + else if (ji.wButtons & JOY_BUTTON2) *Key=-JOY_BUTTON2; + else if (ji.wButtons & JOY_BUTTON3) *Key=-JOY_BUTTON3; + else if (ji.wButtons & JOY_BUTTON4) *Key=-JOY_BUTTON4; + if (ji.wButtons) DestroyWindow(); + return TRUE; +} + +void SetKeyText(HWND hWnd,int ID,int Key) +{ + switch (Key) { + case VK_LBUTTON:SetDlgItemText(hWnd,ID,"L Button");break; + case VK_MBUTTON:SetDlgItemText(hWnd,ID,"M Button");break; + case VK_RBUTTON:SetDlgItemText(hWnd,ID,"R Button");break; + case -JOY_BUTTON1:SetDlgItemText(hWnd,ID,"JStick1");break; + case -JOY_BUTTON2:SetDlgItemText(hWnd,ID,"JStick2");break; + case -JOY_BUTTON3:SetDlgItemText(hWnd,ID,"JStick3");break; + case -JOY_BUTTON4:SetDlgItemText(hWnd,ID,"JStick4");break; + default: + char temp[20]; + Key=MapVirtualKey(Key,0); + GetKeyNameText((long) Key<<16,temp,20); + SetDlgItemText(hWnd,ID,temp); + break; + } +} + diff --git a/Win/Classlib/keypress.h b/Win/Classlib/keypress.h new file mode 100755 index 0000000..5d1c0c8 --- /dev/null +++ b/Win/Classlib/keypress.h @@ -0,0 +1,23 @@ +// keypress.h + +class KeyPressDialog : public Window { +public: +// long Scan; + int *Key; + int Id,Jid; + KeyPressDialog(Object *Parent,int ID,int *Key,int JID=-1); + BOOL SetupWindow(); + void Destroy(); + + BOOL WMSysKeyDown(TMSG&); + BOOL WMKeyDown(TMSG&); + BOOL WMKillFocus(TMSG&); + BOOL WMLButtonDown(TMSG&); + BOOL WMMButtonDown(TMSG&); + BOOL WMRButtonDown(TMSG&); + BOOL WMTimer(TMSG&); + +EV_ENABLE(KeyPressDialog) +} ; + +void SetKeyText(HWND hWnd,int ID,int Key); diff --git a/Win/Classlib/listc.h b/Win/Classlib/listc.h new file mode 100755 index 0000000..99722bc --- /dev/null +++ b/Win/Classlib/listc.h @@ -0,0 +1,327 @@ +#ifndef _list_h +#define _list_h + +template class List +{ +public: + T *Anchor; + + List() + { + Anchor=NULL; + } + virtual ~List() + { + DeleteAll(); + } + void DeleteAll(); + void AddTail(T *NewNode) + { + if (Anchor) + { + Anchor->Prev->Next=NewNode; + NewNode->Prev=Anchor->Prev; + } + else Anchor=NewNode; + NewNode->Next=Anchor; + Anchor->Prev=NewNode; + NewNode->BaseList=this; + } + void AddHead(T *NewNode) + { + if (Anchor) + { + NewNode->Next=Anchor; + NewNode->Prev=Anchor->Prev; + Anchor->Prev->Next=NewNode; + Anchor->Prev=NewNode; + } + else + { + NewNode->Prev=NewNode; + NewNode->Next=NewNode; + } + Anchor=NewNode; + NewNode->BaseList=this; + } + void AddAfter(T *NewNode,T *After) + { + After->Next->Prev=NewNode; + NewNode->Next=After->Next; + After->Next=NewNode; + NewNode->Prev=After; + NewNode->BaseList=this; + } + void AddBefore(T *NewNode,T *Before) + { + Before->Prev->Next=NewNode; + NewNode->Prev=Before->Prev; + Before->Prev=NewNode; + NewNode->Next=Before; + NewNode->BaseList=this; + } + void Remove(T *Node) + { + // get next anchor before removing from chain + if (Node==Anchor) if ((Anchor=Anchor->Next)==Node) Anchor=NULL; + Node->Prev->Next=Node->Next; + Node->Next->Prev=Node->Prev; + Node->BaseList=NULL; + } + void RemoveHead() + { + Remove(GetFirst()); + } + void RemoveTail() + { + Remove(GetLast()); + } + T *GetFirst() + { + return Anchor; + } + BOOL IsEmpty() + { + return Anchor==NULL; + } + T *GetLast() + { + return Anchor ? Anchor->Prev : NULL; + } + void GetNext(T *&Node) + { + if ((Node=Node->Next)==Anchor) Node=NULL; + } + void GetPrev(T *&Node) + { + Node=(Node==Anchor) ? NULL : Node->Prev; + } + void Sort(); + void SubSort(T *Item,T *Item2); + void qSort(); + void Swap(T *&,T *&); + T* operator [](int Num); + int Count(); +}; + +template class BaseNode +{ +public: + BaseNode() + { + BaseList=NULL; + } + BaseNode(List *aList) + { + BaseList=aList; + aList->AddTail((T*) this); + } + virtual ~BaseNode() + { + if (BaseList) BaseList->Remove((T*) this); + } + virtual int operator < (BaseNode &) { return 0; }; + T *GetNext() + { + T *Node=(T*) this; + BaseList->GetNext(Node); + return Node; + } + List *BaseList; + T *Next,*Prev; +}; + +template void List::DeleteAll() +{ + while (Anchor) delete Anchor; +} + +template T* List::operator [](int Num) +{ + T *Node=Anchor; + while (Node!=NULL && Num-->0) GetNext(Node); + return Node; +} + +template int List::Count() +{ + int n=0; + T *Node=GetFirst(); + while (Node) + { + n++; + GetNext(Node); + } + return n; +} + +template void List::Swap(T *&Item1,T *&Item2) +{ + T *i1p=Item1->Prev; + T *i2p=Item2->Prev; + T *A=Anchor; + + if (i1p!=Item2) + { + Remove(Item2); + AddAfter(Item2,i1p); + } + + if (i2p!=Item1) + { + Remove(Item1); + AddAfter(Item1,i2p); + } + + if (Item1==A) Anchor=Item2; + else if (Item2==A) Anchor=Item1; + + T *Temp=Item1; + Item1=Item2; + Item2=Temp; +} + +template void List::Sort() +{ + T *Item=GetFirst(),*Item2; + while (Item) + { + Item2=Item; + GetNext(Item2); + while (Item2) + { + if ((*Item2)<(*Item)) Swap(Item,Item2); + GetNext(Item2); + } + GetNext(Item); + } +} + +template void List::SubSort(T *Item,T *Item2) +{ + if (Item==Item2) return; + T *First,*Last; + if (*Item2<*Item) + { + First=Item2; + Last=Item; + } + else + { + First=Item; + Last=Item2; + } + + do + { + if (*Item2<*Item) Swap(Item,Item2); + + if (Item->Next==Item2) break; + Item=Item->Next; + Item2=Item2->Prev; + } while (Item!=Item2); + + Item2=Item2->Next; // make sure Item2 is not corrupted + SubSort(First,Item); + SubSort(Item2->Prev,Last); +} + +template void List::qSort() +{ + SubSort(GetFirst(),GetLast()); +} + +/* +Usage: + +List ObjectList; // Create a List of Objects + +class Object : BaseNode // Define the Object +{ + Object(...) : BaseNode(ObjectList) // for single list, simple constructor + {} + Object(List *ObjList,...) : BaseNode(ObjList) // multiple lists, more complex constructor + {} +}; + +new Object(...); // Create and add to Objectlist +new Object(OtherList,...); // Create and add to any ObjectList + +delete O; // deletes object O and removes from its parent list + +// For simpler object: + +class Object : BaseNode +{ + Object(...) + {] +}; + +O=new Object(...); +if (O->Valid) ObjectList.AddTail(O); else delete O; + +*/ + +// Simple List *********************************** + +// e.g. +// SimpleList IntList; +// Item should have default constructor and copy constructor + +template class SimpleNode : public BaseNode< SimpleNode > +{ +public: + SimpleNode(T &Data) { Item=Data; } + T Item; + int operator < (SimpleNode&Comp) { return Item class SimpleList : public List< SimpleNode > +{ +public: + void AddTailRef(T &Data) { List< SimpleNode >::AddTail(new SimpleNode(Data)); } + void AddTail(T Data) { List< SimpleNode >::AddTail(new SimpleNode(Data)); } + void AddHeadRef(T &Data) { List< SimpleNode >::AddHead(new SimpleNode(Data)); } + void AddHead(T Data) { List< SimpleNode >::AddHead(new SimpleNode(Data)); } + T operator [](int Num) { return (List >::operator[](Num))->Item; } +}; + +template class slIterator +{ +public: + SimpleList *List; + SimpleNode *Node; + slIterator(SimpleList &ListId) + { + List=&ListId; + Node=List->GetFirst(); + } + int operator()() { return Node!=NULL; } + operator T() { return Node->Item; } + operator T*() { return &Node->Item; } + void operator++() { List->GetNext(Node); } + void operator++(int) { List->GetNext(Node); } + void operator--() { List->GetPrev(Node); } + void operator--(int) { List->GetPrev(Node); } + void First() { Node=List->GetFirst(); } + void Last() { Node=List->GetLast(); } + T Get() { return Node->Item; } + void Remove() + { + SimpleNode *Node2=Node; + operator++(); + List->Remove(Node2); + } +}; + +/* +Iterator I(ListIdendifier); +while (I()) +{ + (T) I; + ++I; +} +*/ + +#endif + diff --git a/Win/Classlib/main.cpp b/Win/Classlib/main.cpp new file mode 100755 index 0000000..98a39e1 --- /dev/null +++ b/Win/Classlib/main.cpp @@ -0,0 +1,84 @@ +// WinMain and object init +#include +#pragma hdrstop + +#include + +// Window::CreationWindow should be set +LRESULT CALLBACK InitialisationProc( HWND hWnd, UINT iMessage, WPARAM wParam, + LPARAM lParam ) +{ + Window *CW=Window::CreationWindow; + if ( iMessage == WM_CREATE ) + { + // Store a pointer to this object in the window's extra bytes; + // this will enable us to access this object (and its member + // functions) in WndProc where we are + // given only a handle to identify the window. + Window::CreationWindow=NULL; + SetPointer( hWnd, CW ); + // make sure hWnd is set of correct handling of WM_CREATE + CW->hWnd=hWnd; + // Now let the object perform whatever + // initialization it needs for WM_CREATE in its own + // WndProc. + SetWindowLong(hWnd,GWL_WNDPROC,(long) MainWndProc); + + TMSG Msg={iMessage,wParam,lParam,0}; + if (CW->WndProc(Msg)) return Msg.RetVal; + } + // The messages that + // precede WM_CREATE must be processed without using pWindow so we + // pass them to DefProc. + return CW->DefProc( hWnd, iMessage, wParam, lParam ); +} + +LRESULT CALLBACK MainWndProc( HWND hWnd, UINT iMessage, WPARAM wParam, + LPARAM lParam ) +{ + // Pointer to the (C++ object that is the) window. + Window *pWindow = GetPointer( hWnd ); + TMSG Msg={iMessage,wParam,lParam,0}; + if (pWindow->WndProc(Msg)) return Msg.RetVal; + return pWindow->DefProc( hWnd, iMessage, wParam, lParam ); +} + +// this may call the wrong proc (MDI frame or child, but so what??) +LRESULT CALLBACK NullProc( HWND hWnd, UINT iMessage, WPARAM wParam, + LPARAM lParam ) +{ + return DefWindowProc( hWnd, iMessage, wParam, lParam ); +} + +// WinMain ************************************* + +int Main(); + +int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, + int nCmdShow ) +{ + App::hInstance = hInstance; + App::hPrevInstance = hPrevInstance; + App::CmdLine = lpszCmdLine; + App::nCmdShow = nCmdShow; + + int ret=Main(); + + // TextWindow open + if (App::TextWindow && (App::TextWindow->StayOpen)) + { + App::TextWindow->Parent=NULL; + App::MainWindow=(Object *) App::TextWindow; + String S(GetWindowTextLength(App::MainWindow->hWnd)+1); + GetWindowText(App::MainWindow->hWnd,S,S.Size()); + S.Update(); + S << " (finished)"; + SetWindowText(App::MainWindow->hWnd,S); + ShowWindow(App::MainWindow->hWnd, SW_SHOW ); + // wait until closed + return App::MessageLoop(); + // delete App::TextWindow; ?? + } + return ret; +} + diff --git a/Win/Classlib/main.h b/Win/Classlib/main.h new file mode 100755 index 0000000..c568a47 --- /dev/null +++ b/Win/Classlib/main.h @@ -0,0 +1,6 @@ +LRESULT CALLBACK NullProc( HWND hWnd, UINT iMessage, WPARAM wParam, + LPARAM lParam ); +LRESULT CALLBACK MainWndProc( HWND hWnd, UINT iMessage, WPARAM wParam, + LPARAM lParam ); +LRESULT CALLBACK InitialisationProc( HWND hWnd, UINT iMessage, WPARAM wParam, + LPARAM lParam ); diff --git a/Win/Classlib/matrix.cpp b/Win/Classlib/matrix.cpp new file mode 100755 index 0000000..351b168 --- /dev/null +++ b/Win/Classlib/matrix.cpp @@ -0,0 +1,161 @@ +#include + +#include + +Matrix::Matrix() +{ + n=3; + Data=new double[n*n]; +} + +Matrix::Matrix(int m) +{ + n=m; + Data=new double[n*n]; +} + +Matrix::~Matrix() +{ + delete[] Data; +} + +void Matrix::ReSize(int m) +{ + if (m!=n) + { + delete[] Data; + n=m; + Data=new double[n*n]; + } +} + +Matrix &Matrix::operator=(int v) +{ + for(int i=0;i + +class Vector +{ +//friend Matrix; +public: + double *Data; + int n; + static Vector Temp; + + + Vector(); + Vector(int); + Vector(double x,double y,double z); + ~Vector(); + void ReSize(int m); + double Length(); + Vector &operator=(double); + Vector &operator=(Vector &); + Vector &operator+(Vector &); + Vector &operator*(double); + double &operator[](int i) + { + return Data[i]; + } + +}; + +class Matrix +{ +private: + double *Data; + int n; + static Matrix Temp; + +public: + Matrix(); + Matrix(int); + ~Matrix(); + void ReSize(int m); + Matrix &operator=(Matrix&M) + { + memcpy(Data,M.Data,n*n*sizeof(double)); + return *this; + } + Matrix &operator=(int); + Matrix &operator*(Matrix &); + Matrix &operator*(double); + Matrix &operator+(Matrix &); + Vector &operator*(Vector &V); + + double *operator[](int j) + { + return Data+j*n; + } + +}; + diff --git a/Win/Classlib/mdi.cpp b/Win/Classlib/mdi.cpp new file mode 100755 index 0000000..b205445 --- /dev/null +++ b/Win/Classlib/mdi.cpp @@ -0,0 +1,158 @@ +// MDIClient +#include +#pragma hdrstop + +#include + +// Client **************************************************** + +MDIClient::MDIClient(Object *P) : Object(P) +{ + Style= WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP | WS_CLIPCHILDREN; + x = CW_USEDEFAULT; + y = 0; + w = CW_USEDEFAULT; + h = 0; +} + +BOOL MDIClient::Create() +{ + if ( x == CW_USEDEFAULT || w == CW_USEDEFAULT ) + { + RECT rc; + GetClientRect(ParentHWnd,&rc); + x = rc.left; + y = rc.top; + w = rc.right - rc.left; + h = rc.bottom - rc.top; + } + + CLIENTCREATESTRUCT ccs={NULL,CM_FIRSTCHILD}; + hWnd=CreateWindow("MDICLIENT","",Style,x,y,w,h,ParentHWnd, + NULL,App::hInstance, &ccs ); + return hWnd!=NULL; +} + +// Child **************************************************** + +/* +EV_START(MDIClient) + EV_MESSAGE(WM_KEYUP,WMKeyUp) + EV_MESSAGE(WM_KEYDOWN,WMKeyDown) +EV_END +*/ + +MDIChild::MDIChild(Object *P,char *T,char *N) : Window (P,T,N) +{ + Style=WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS; +} + +MDIChild::~MDIChild() +{ +} + +LRESULT MDIChild::DefProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam) +{ + return DefMDIChildProc(hWnd,Msg,wParam,lParam); +} + +char *MDIChild::GetClassName() +{ + return "Child"; +} + +BOOL MDIChild::Create() +{ + if (!Register()) return FALSE; + + MDICREATESTRUCT mcs= + { + GetClassName(),Title,App::hInstance,x,y,w,h,Style,NULL + }; + + if (Style & (WS_HSCROLL | WS_VSCROLL)) Flags|=W_SCROLLBARS; + + CreationWindow=this; + +#ifdef WIN32 + hWnd = (HWND) SendMessage( ((MDIFrame*)Parent)->hClient, WM_MDICREATE, 0, (LPARAM)(&mcs)); +#else + hWnd = LOWORD(SendMessage( ((MDIFrame*)Parent)->hClient, WM_MDICREATE, 0, (LPARAM)(&mcs))); +#endif + if (!hWnd) return FALSE; + + // user may not want child to save its position?? + if (Name) GetWindowState(W_SAVESTATE | W_SAVEMIN); + return SetupWindow(); +} + +void MDIChild::DestroyWindow() +{ + SendMessage( ((MDIFrame*)Parent)->hClient, WM_MDIDESTROY, (WPARAM) hWnd, 0); +} + +// pass keypresses to StatBar if present +/* +LRESULT MDIClient::WMKeyUp(TMSG &Msg) +{ + if (((MDIFrame*)Parent)->StatBar) ((MDIFrame*)Parent)->StatBar->WMKeyUp(Msg); +} + +LRESULT MDIClient::WMKeyDown(TMSG &Msg) +{ + if (((MDIFrame*)Parent)->StatBar) ((MDIFrame*)Parent)->StatBar->WMKeyDown(Msg); +} +*/ + +// Frame ********************************************************** + +EV_START(MDIFrame) + EV_COMMAND(CM_MDITILE,MDITile) + EV_COMMAND(CM_MDICASCADE,MDICascade) + EV_COMMAND(CM_MDIARRANGEICONS,MDIArrangeIcons) + + EV_MESSAGE(WM_SIZE,WMSize) +EV_END + +MDIFrame::MDIFrame(Object *P,char *T,char *N) : Window(P,T,N) +{ +} + +LRESULT MDIFrame::DefProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam) +{ + return DefFrameProc(hWnd,hClient,Msg,wParam,lParam); +} + +// needs to be called from derived SetupWindow +BOOL MDIFrame::SetupWindow() +{ + MDI=new MDIClient(this); + if (!MDI || !MDI->Create()) return FALSE; + hClient=MDI->hWnd; + return TRUE; +} + +// pick up WM_SIZE in case of StatBar +BOOL MDIFrame::WMSize(TMSG &Msg) +{ + int h=HIWORD(Msg.lParam); + if (StatBar) h-=StatBar->dyStatbar; + MoveWindow(hClient, 0,0, LOWORD(Msg.lParam), h, TRUE); + return TRUE; +} + +void MDIFrame::SetWindowMenu(int Pos) +{ + HMENU hm1=GetMenu(hWnd); + HMENU hm2=GetSubMenu(hm1,Pos); + #ifdef WIN32 + SendMessage(hClient,WM_MDISETMENU,(WPARAM) NULL,(LPARAM) hm2); + #else + SendMessage(hClient,WM_MDISETMENU,FALSE,MAKELPARAM(hm1,hm2)); + #endif + DrawMenuBar(hWnd); +} + +//Change Menu on child activation +//key press in child for statbar + diff --git a/Win/Classlib/mdi.h b/Win/Classlib/mdi.h new file mode 100755 index 0000000..710ce57 --- /dev/null +++ b/Win/Classlib/mdi.h @@ -0,0 +1,51 @@ +// mdi.h + +class MDIClient : public Object +{ +private: + DWORD Style; + int x,y,w,h; + +public: + MDIClient(Object*); + BOOL Create(); +}; + +class MDIChild : public Window +{ +public: + MDIChild(Object*,char*,char *Name=NULL); + ~MDIChild(); + virtual char *GetClassName(); + LRESULT DefProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam); + BOOL Create(); + void DestroyWindow(); +}; + +// LRESULT WMKeyUp(TMSG &); +// LRESULT WMKeyDown(TMSG &); + +// EV_ENABLE(MDIChild) + + +class MDIFrame : public Window +{ +private: + MDIClient *MDI; +public: + HWND hClient; + MDIFrame(Object *,char *,char *N=NULL); + LRESULT DefProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam); + + BOOL SetupWindow(); + void SetWindowMenu(int Pos); + + void MDITile() { SendMessage(hClient,WM_MDITILE,0,0); } + void MDICascade() { SendMessage(hClient,WM_MDICASCADE,0,0); } + void MDIArrangeIcons() { SendMessage(hClient,WM_MDIICONARRANGE,0,0); } + + + BOOL WMSize(TMSG &); + + EV_ENABLE(MDIFrame) +}; diff --git a/Win/Classlib/misc.cpp b/Win/Classlib/misc.cpp new file mode 100755 index 0000000..fbf1fe7 --- /dev/null +++ b/Win/Classlib/misc.cpp @@ -0,0 +1,224 @@ +// Misc useful routines ********************************** + +#include +#pragma hdrstop + +#include + +#include +#include +#include + +#ifdef WIN32 +#include + +#else +#include +#endif + +char *strdup(char *s) +{ + if (s==NULL) return NULL; + char *t=new char[strlen(s)+1]; + if (t) strcpy(t,s); + return t; +} + +void EnableDlgButton(HWND hDlg,int ID, BOOL Enable) { +HWND hw=GetDlgItem(hDlg,ID); +EnableWindow(hw,Enable); +} + +void DrawBitmap(HDC hDC, HBITMAP hBitmap) { +// need grey brush to remove white edges when using Ctrl3d +BITMAP bm; +HDC hMemDC = CreateCompatibleDC(hDC); +GetObject(hBitmap, sizeof(BITMAP), &bm); +SelectObject(hMemDC, hBitmap); +BitBlt(hDC, 0,0, bm.bmWidth, bm.bmHeight, hMemDC, 0, 0, SRCCOPY); +DeleteDC(hMemDC); +} + +void StretchBitmap(HDC hDC, int dw, int dh,HBITMAP hBitmap) { +BITMAP bm; +HDC hMemDC = CreateCompatibleDC(hDC); +GetObject(hBitmap, sizeof(BITMAP), &bm); +SelectObject(hMemDC, hBitmap); +StretchBlt(hDC, 0,0,dw,dh, hMemDC, 0,0, bm.bmWidth, bm.bmHeight, SRCCOPY); +DeleteDC(hMemDC); +} + +// Establishes a set of custom colors +COLORREF CustCols[]={ + RGB(255,0,0),RGB(0,255,0),RGB(255,255,0), RGB(0,0,255), + RGB(255,0,255),RGB(0,255,255),RGB(255,255,255), RGB(0,0,0), + RGB(127,0,0),RGB(0,127,0),RGB(127,127,0), RGB(0,0,127), + RGB(127,0,127),RGB(0,127,127),RGB(127,127,127), RGB(0,0,0) }; + +BOOL GetColor(HWND HWnd, COLORREF &Col) { +CHOOSECOLOR CC; +CC.lStructSize = sizeof(CHOOSECOLOR); +CC.hwndOwner=HWnd; +CC.rgbResult=Col; +CC.lpCustColors=CustCols; +CC.Flags=CC_RGBINIT; +if (ChooseColor(&CC)) { + Col=CC.rgbResult; + return TRUE; + } +return FALSE; +} + +BOOL OK2Save(char *s,char * Title) { + char temp[255]; + FILE *f=fopen(s,"r"); + if (f!=NULL) { + fclose(f); + sprintf(temp,"File \"%s\" exists. OK to overwrite?",s); + if (MessageBox(0,temp,Title,MB_YESNOCANCEL | MB_ICONQUESTION)!=IDYES) return FALSE; + } + // what if read only file ? + f=fopen(s,"w"); + if (f) fclose(f); + else { + sprintf(temp,"Unable to write to file :%s",s); + MessageBox(0,temp,Title,MB_OK | MB_ICONEXCLAMATION); + return FALSE; + } + return TRUE; +} +/* +BOOL GetSaveFName(HWND hWnd,char *Buffer,int BufSize,const char *Filters,int &Fi,char *Title,DWORD Flags) +{ + if (GetFile(hWnd,Buffer,BufSize,Filters,Fi,Title,Flags)) + { + if (OK2Save(Buffer,"")) return TRUE; + } + return FALSE; +} +*/ +void DeleteFile(char *fname) +{ + OFSTRUCT of; + OpenFile(fname,&of,OF_DELETE); +} + +BOOL DirExist(char *path) +{ +#ifdef WIN32 + DWORD dw=GetFileAttributes(path); + return (dw!=DWORD(-1) && (dw & FILE_ATTRIBUTE_DIRECTORY)); +#else + if (strlen(path)<=3) return TRUE; // for "c:\\" + char temp[MAX_PATH]; + strcpy(temp,path); + if (temp[strlen(temp)-1]=='\\') temp[strlen(temp)-1]=0; + ffblk ff; + int h=findfirst(temp,&ff,FA_DIREC); + return h==0; +#endif +} + +BOOL CheckPath(HWND hw,char *Path,char *Title,BOOL Query) +{ + //check path ok + FName P(Path,""),temp; + if (DirExist(P)) return TRUE; + if (Query && MessageBox(hw,String()<<"Path \""<=0); + return TRUE; +} + +void GetTempFName(char *Pre,FName &F) +{ + #ifdef WIN32 + FName Path; + GetTempPath(MAX_PATH,Path); + GetTempFileName(Path,Pre,0,F); + #else +// int Dr=GetTempDrive(); + GetTempFileName(0,Pre,0,F); + #endif +} + +long filelength(FILE *f) +{ + long pos=ftell(f); + fseek(f,0,SEEK_END); + long size=ftell(f); + fseek(f,pos,SEEK_SET); + return size; +} + +#ifndef WIN32 + +#define MAXREAD 0xff00L + +long fread(bhp ptr,int size,long num,FILE *f) +{ + long bytes=num*size,read; + do + { + read=fread((void*) ptr,(int) 1,(int) min(bytes,MAXREAD),f); + ptr+=read; + bytes-=read; + } while (bytes || read==0); + return num-bytes/size; +} +#endif + + +#ifdef WIN32 + +int CALLBACK SetSelProc(HWND hWnd,UINT uMsg,LPARAM,LPARAM lpData) +{ + if (uMsg==BFFM_INITIALIZED) SendMessage(hWnd,BFFM_SETSELECTION,TRUE,lpData); + return 0; +} + +BOOL SelectDirectory(HWND hw,FName &Dir,char *Title) +{ + LPMALLOC pMalloc; + BOOL ret=FALSE; + if (SUCCEEDED(SHGetMalloc(&pMalloc))) + { + BROWSEINFO bi; + bi.hwndOwner=hw; + bi.pidlRoot=NULL; + bi.pszDisplayName=Dir; + bi.lpszTitle=Title; + bi.ulFlags=BIF_RETURNONLYFSDIRS; + bi.lpfn=SetSelProc; + bi.lParam=(LPARAM) (char*) Dir; + bi.iImage=0; + + ITEMIDLIST *iList=SHBrowseForFolder(&bi); + if (iList) + { + ret=SHGetPathFromIDList(iList,Dir); + pMalloc->Free(iList); + } + pMalloc->Release(); + } + + return ret; +} + +#endif diff --git a/Win/Classlib/misc.h b/Win/Classlib/misc.h new file mode 100755 index 0000000..5c27619 --- /dev/null +++ b/Win/Classlib/misc.h @@ -0,0 +1,23 @@ +// misc.h +#include +#include + +void EnableDlgButton(HWND hDlg,int ID, BOOL Enable); +inline void swap(int &A,int &B) {int temp=A; A=B; B=temp;} // use macro ?? +inline char *YesNo(BOOL yn) { return yn ? "Yes" : "No"; } +BOOL OK2Save(char *s,char * Title); +BOOL GetSaveFName(HWND hWnd,char *Buffer,int BufSize,const char *Filters,char *Title,DWORD Flags); +void DeleteFile(char *fname); +BOOL CheckPath(HWND hw,char *Path,char *Title,BOOL Query); +char *strdup(char *s); +BOOL GetColor(HWND HWnd, COLORREF &Col); +void DrawBitmap(HDC hDC, HBITMAP hBitmap); +void GetTempFName(char *Pre,FName &F); + +long filelength(FILE *f); +#ifdef WIN32 +BOOL SelectDirectory(HWND hw,FName &Dir,char *Title); +#else +long fread(bhp ptr,int size,long num,FILE *f); +#endif + diff --git a/Win/Classlib/mywin.h b/Win/Classlib/mywin.h new file mode 100755 index 0000000..d651559 --- /dev/null +++ b/Win/Classlib/mywin.h @@ -0,0 +1,140 @@ +// MyWin.h +#define WIN32_LEAN_AND_MEAN + +#include +#include + +// Fixed menu responses +#define IDM_SYSMENU 300 +#define IDS_POPUP1 301 + +#define CM_MDITILE 400 +#define CM_MDICASCADE 401 +#define CM_MDIARRANGEICONS 402 + +#define CM_FIRSTCHILD 500 + +#ifndef __BORLANDC__ +// msvc hacks +// needs to be here to prevent knackeration +#include + +#define MAXINT INT_MAX +#define strnicmp _strnicmp +#define asm __asm +#define random(x) ((long)x*rand()/RAND_MAX) +#endif + +#ifdef TRACEFILE +#include +#define TRACE(s) { ofstream(TRACEFILE,ios::app) << (char*) (s) << '\n'; } +#else +#define TRACE(s) +#endif + +#if defined(__WIN32__) || defined (_WIN32) +#define LOWWPARAM(x) LOWORD(x) +#define HIWLPARAM(x) HIWORD(x.wParam) +#define LLHW(x) HIWORD(x.wParam) +#define HILP32(x) x.lParam +typedef int int32; +typedef unsigned int uint32; +typedef BYTE *bhp; +#define huge +#define export +#ifndef WIN32 +#define WIN32 +#endif +#else +#define LOWWPARAM(x) (x) +#define HIWLPARAM(x) HIWORD(x.lParam) +#define LLHW(x) LOWORD(x.lParam) +#define HILP32(x) HIWORD(x.lParam) +typedef long int32; +typedef unsigned long uint32; +typedef BYTE huge *bhp; +#define WIN16 +#endif + +struct TMSG +{ + UINT Msg; + WPARAM wParam; + LPARAM lParam; + LRESULT RetVal; +}; + +template class EV_INFO +{ +public: + unsigned int Type,Msg,Id; + int Next; + void (T::*Proc)(); +}; + +//Generic Class for casts +class GENERIC {}; +typedef EV_INFO GEN_EV_INFO; + +#define HASH_EV_ENABLE(Type)\ + EV_ENABLE(Type)\ + GEN_EV_INFO *GetEvInfo() { return (GEN_EV_INFO*) EV_TABLE; }\ + +#define EV_ENABLE(Type)\ + static EV_INFO EV_TABLE[];\ +public:\ + virtual BOOL EV_FIND(TMSG &);\ + typedef Type MyType;\ + typedef void (Type::*MyProc)(); + +#define EV_START(Type)\ + BOOL Type::EV_FIND(TMSG &Msg)\ + {\ + return EV_SEARCH( (GEN_EV_INFO *) Type::EV_TABLE, Msg, (GENERIC *)this);\ + }\ + EV_INFO Type::EV_TABLE[]={ + +#define EV_START2(Type,Base)\ + BOOL Type::EV_FIND(TMSG &Msg)\ + {\ + if (EV_SEARCH( (GEN_EV_INFO *) Type::EV_TABLE, Msg, (GENERIC *)this)) return TRUE;\ + return Base::EV_FIND(Msg);\ + }\ + EV_INFO Type::EV_TABLE[]={ + + +#define EV_COMMAND(MES,PROC) {0,WM_COMMAND,MES,0,(MyProc) MyType::PROC}, +#define EV_COMMANDNOTIFY(MES,PROC) {2,WM_COMMAND,MES,0,(MyProc) MyType::PROC}, +#define EV_MESSAGE(MES,PROC) {1,MES,0,0,(MyProc) MyType::PROC}, +#define EV_END {0,0,0,0,NULL} }; + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + diff --git a/Win/Classlib/numedit.cpp b/Win/Classlib/numedit.cpp new file mode 100755 index 0000000..bcf8b9b --- /dev/null +++ b/Win/Classlib/numedit.cpp @@ -0,0 +1,85 @@ +// NumEdit *********************************************** + +#include +#pragma hdrstop + +#include +#include +#ifdef __BORLANDC__ +#include +#else +#include +#endif + +NumEdit::NumEdit(Object *Parent,int Id,long Value,long Flags,long Min,long Max,int Div) : Edit(Parent,Id) +{ + FLAGS=Flags; + MIN=Min; + MAX=Max; + DivBy=Div; + SetValue(Value); +} + +void NumEdit::SetRange(long Min,long Max) +{ + MIN=Min; + MAX=Max; +} + +void NumEdit::SetValue(long Value) +{ + char temp[11]; + sprintf(temp,FLAGS & NE_HEX ? "%lX" : "%ld",Value); + SetString(temp); +} + +BOOL Pow2(unsigned long x) +{ + if (x!=0) while ( (x&1)==0) x>>=1; + return x==1; +} + +BOOL NumEdit::CanClose() +{ + char temp[128]=""; + int err=0; + long Value=GetValue(&err); + if (err) strcpy(temp,"Please enter a valid number"); + else if ((Value= %ld",MIN); + else if ((Value>MAX && (FLAGS & NE_MAX) && !(FLAGS & NE_MIN))) + sprintf(temp,"Please enter a number <= %ld",MAX); + else if ((( ValueMAX ) && ((FLAGS & NE_MINMAX)==NE_MINMAX))) + sprintf(temp,"Please enter a number %ld <= x <= %ld",MIN,MAX); + else if (Value<0 && (FLAGS & NE_POS)) + strcpy(temp,"Please enter a positive number"); + else if (Value>0 && (FLAGS & NE_NEG)) + strcpy(temp,"Please enter a negative number"); + else if (Value==0 && (FLAGS & NE_NONZERO)) + strcpy(temp,"Please enter a non-zero number"); + else if ((FLAGS & NE_POWER2) && !Pow2(Value)) + strcpy(temp,"Please enter a power of 2"); + else if ( (FLAGS & NE_DIVBY) && (Value % DivBy!=0) ) { + sprintf(temp,"Please enter a number divisible by %d",DivBy); + } + if (*temp) + { + // handle needs to be that of window not control + MessageBox(ParentHWnd,temp,"Error",MB_OK | MB_ICONEXCLAMATION); + SendMessage(hWnd, EM_SETSEL,0,MAXINT); + SetFocus(hWnd); + return FALSE; + } + return TRUE; +} + +long NumEdit::GetValue(BOOL *err) +{ + long Value; + char *temp=GetString(); + if (err) *err=0; + if (sscanf(temp,FLAGS & NE_HEX ? "%lX" : "%ld",&Value)<=0 && err) *err=1; + delete[] temp; + return Value; +} + diff --git a/Win/Classlib/numedit.h b/Win/Classlib/numedit.h new file mode 100755 index 0000000..caac984 --- /dev/null +++ b/Win/Classlib/numedit.h @@ -0,0 +1,24 @@ +// NumEdit.h + +//NumEdit Flags +#define NE_MIN 1 +#define NE_MAX 2 +#define NE_MINMAX 3 +#define NE_POS 4 +#define NE_NEG 8 +#define NE_NONZERO 16 +#define NE_POWER2 32 +#define NE_DIVBY 64 +#define NE_HEX 128 + +class NumEdit : Edit { +public: + NumEdit::NumEdit(Object *Parent,int Id,long Value,long Flags=0,long Min=0,long Max=0,int Div=1); + long GetValue(BOOL *err=NULL); + void SetValue(long Value); + void SetRange(long Min,long Max); + virtual BOOL CanClose(); +private: + int DivBy; + long FLAGS,MIN,MAX; +}; diff --git a/Win/Classlib/object.cpp b/Win/Classlib/object.cpp new file mode 100755 index 0000000..1fcba0e --- /dev/null +++ b/Win/Classlib/object.cpp @@ -0,0 +1,105 @@ +// Object definitiions *********************************** + +#include +#pragma hdrstop + +Object::Object(Object *AParent) +{ + Parent=AParent; + //Add onto parents childlist + if (Parent) { + Next=Parent->ChildList; + Parent->ChildList=this; + if (Next) Next->Prev=this; + ParentHWnd=Parent->hWnd; + } + else { // no parent + Next=NULL; + ParentHWnd=0; + } + + ChildList=Prev=NULL; +} + +Object::~Object() +{ + // delete all children + Object *List=ChildList,*Nxt; + while (List) { + Nxt=List->Next; + delete List; + List=Nxt; + } + // remove this object from parents Childlist + if (Next) Next->Prev=Prev; + if (Prev) Prev->Next=Next; + // only child? + if (Parent && Parent->ChildList==this) Parent->ChildList=Next; +} + +BOOL Object::CanClose() +{ + Object *Child=FirstChild(); + while (Child) + { + if (!Child->CanClose()) return FALSE; + Child=NextChild(Child); + } + return TRUE; +} + +BOOL Object::EV_FIND(TMSG &) +{ + return FALSE; +} + +BOOL Object::Create() { return FALSE; } + +// returns TRUE if message is processed +BOOL Object::EV_SEARCH(GEN_EV_INFO * EV_TABLE, TMSG &Msg, GENERIC *Owner) +{ + while ((EV_TABLE)->Msg) + { + if (EV_TABLE->Msg==Msg.Msg) + { + switch (EV_TABLE->Type) + { + case 0: + if (EV_TABLE->Id==LOWORD(Msg.wParam)) + { + ( Owner->*EV_TABLE->Proc)(); + return TRUE; + } + break; + + case 2: + if (EV_TABLE->Id==LOWORD(Msg.wParam)) + { +#ifndef WIN32 + ( Owner->*((NotifyProc) EV_TABLE->Proc) ) (HIWORD(Msg.lParam)); +#else + ( Owner->*((NotifyProc) EV_TABLE->Proc) ) (HIWORD(Msg.wParam)); +#endif + return TRUE; + } + break; + + case 1: + return (Owner->*( (Proc1) EV_TABLE->Proc) ) (Msg); + } + } + EV_TABLE++; + } + return FALSE; +} + +BOOL Object::WndProc(TMSG &Msg) +{ + return (Msg.RetVal=DefProc(hWnd,Msg.Msg,Msg.wParam,Msg.lParam))==0; +} + +LRESULT Object::DefProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam) +{ + return DefWindowProc(hWnd,Msg,wParam,lParam); +} + diff --git a/Win/Classlib/object.h b/Win/Classlib/object.h new file mode 100755 index 0000000..82d07bb --- /dev/null +++ b/Win/Classlib/object.h @@ -0,0 +1,22 @@ +// Object.h + +typedef BOOL (GENERIC::*Proc1)(TMSG&); +typedef void (GENERIC::*NotifyProc)(UINT); + +class Object { + Object *ChildList,*Next,*Prev; +public : + HWND hWnd,ParentHWnd; + Object *Parent; + Object(Object *); + virtual ~Object(); + virtual BOOL EV_FIND(TMSG&); + virtual BOOL EV_SEARCH(GEN_EV_INFO *,TMSG&,GENERIC *); + virtual BOOL CanClose(); + Object *FirstChild() { return ChildList; } + Object *NextChild(Object *Child) { return Child->Next; } + virtual BOOL Create(); + virtual BOOL WndProc(TMSG &); + virtual LRESULT DefProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam); +}; + diff --git a/Win/Classlib/owndrwb.cpp b/Win/Classlib/owndrwb.cpp new file mode 100755 index 0000000..3c083bf --- /dev/null +++ b/Win/Classlib/owndrwb.cpp @@ -0,0 +1,78 @@ +// Owner draw button ****************************** + +#include +#pragma hdrstop + +OwnDrwButton::OwnDrwButton(Object* Parent, int resId, + int drw, int sel, int foc, int dis) : Object(Parent) { +bm_draw=LoadBitmap(App::hInstance,MAKEINTRESOURCE(drw)); +bm_select=LoadBitmap(App::hInstance,MAKEINTRESOURCE(sel)); +bm_focus=LoadBitmap(App::hInstance,MAKEINTRESOURCE(foc)); +bm_disabled=LoadBitmap(App::hInstance,MAKEINTRESOURCE(dis)); +// get size of button? +hWnd=GetDlgItem(ParentHWnd,resId); +RECT rc; +GetWindowRect(hWnd,&rc); +Width=rc.right-rc.left; +Height=rc.bottom-rc.top; +} + +OwnDrwButton::~OwnDrwButton() { +DeleteObject(bm_draw); +DeleteObject(bm_select); +DeleteObject(bm_focus); +DeleteObject(bm_disabled); +} + +BOOL OwnDrwButton::WndProc(TMSG &Msg) +{ + switch (Msg.Msg) + { + #ifndef WIN32 + case WM_CTLCOLOR: + switch ( (int) HIWORD(Msg.lParam)) // type of control + { + case CTLCOLOR_DLG: + case CTLCOLOR_EDIT: + case CTLCOLOR_LISTBOX: + break; + case CTLCOLOR_MSGBOX: // this one works ? + // Prevents white out with CTL3D + Msg.RetVal=GetStockObject(NULL_BRUSH); + return TRUE; + case CTLCOLOR_SCROLLBAR: + case CTLCOLOR_STATIC: + case CTLCOLOR_BTN: + break; + } + break; + #endif + case WM_DRAWITEM: + { + LPDRAWITEMSTRUCT drawInfo = (LPDRAWITEMSTRUCT) Msg.lParam; /* item-drawing information */ + switch (drawInfo->itemAction) + { + case ODA_DRAWENTIRE: + if (drawInfo->itemState & ODS_DISABLED) DrawBitmap(drawInfo->hDC,bm_disabled); + else if (drawInfo->itemState & ODS_SELECTED) DrawBitmap(drawInfo->hDC,bm_select); + else if (drawInfo->itemState & ODS_FOCUS) DrawBitmap(drawInfo->hDC,bm_focus); + else DrawBitmap(drawInfo->hDC,bm_draw); + return TRUE; + + case ODA_SELECT: + if (drawInfo->itemState & ODS_SELECTED) DrawBitmap(drawInfo->hDC,bm_select); + else if (drawInfo->itemState & ODS_FOCUS) DrawBitmap(drawInfo->hDC,bm_focus); + else DrawBitmap(drawInfo->hDC,bm_draw); + return TRUE; + + case ODA_FOCUS: + if (drawInfo->itemState & ODS_FOCUS) DrawBitmap(drawInfo->hDC,bm_focus); + else DrawBitmap(drawInfo->hDC,bm_draw); + return TRUE; + } + break; + } + } + return FALSE; +} + diff --git a/Win/Classlib/owndrwb.h b/Win/Classlib/owndrwb.h new file mode 100755 index 0000000..d6b6f5b --- /dev/null +++ b/Win/Classlib/owndrwb.h @@ -0,0 +1,11 @@ +// owndrwb.h + +class OwnDrwButton : Object { +public: + HBITMAP bm_draw,bm_select,bm_focus,bm_disabled; + int Width,Height; + OwnDrwButton(Object* parent, int resId, int dr, int s, int f, int dis); + virtual ~OwnDrwButton(); + virtual BOOL WndProc(TMSG &); + BOOL Create() { return TRUE; } +}; diff --git a/Win/Classlib/parse.cpp b/Win/Classlib/parse.cpp new file mode 100755 index 0000000..4cd4345 --- /dev/null +++ b/Win/Classlib/parse.cpp @@ -0,0 +1,293 @@ +// handy parsing stuff +#include +#pragma hdrstop + +#include + +#include + +Parse::Parse(bhp Buf,long Size) : Com(256) +{ + Buffer=Ptr=Buf; + BufLen=Size; + c=0; + Eol=FALSE; +} + +Parse::Parse(char* Buf) : Com(256) +{ + Buffer=Ptr=(bhp) Buf; + BufLen=strlen(Buf); + c=0; + Eol=FALSE; +} + +void Parse::SkipLine() +{ + Eol=FALSE; + while (GetC()!='\n' && c>=0); + if (GetC()!='\r') PutBackC(); +} + +char *Parse::GetCom() +{ + Com=""; + if (!Eol) + { + SkipSpace(); + if (strchr(".0123456789",c)) // pure numeric? + while (strchr("0123456789.",GetC()) && !Eof()) Com+=c; // exponent? + else + while (!strchr(" +-*/,<>=;()\r\n\t",GetC()) && !Eof()) Com+=c; + if (c==';' || c=='\n' || c=='\r') + { + PutBackC(); // only put back line terminator + Eol=TRUE; + } + } + return Com; +} + +char *Parse::GetTerm() +{ + Com=""; + if (!Eol) + { + SkipSpace(); + while (!strchr(" +-*/,<>=;()\r\n\t",GetC()) && !Eof()) Com+=c; + if (c==';' || c=='\n' || c=='\r') + { + PutBackC(); // only put back line terminator + Eol=TRUE; + } + } + return Com; +} + + +char *Parse::GetQStr() +{ + Com=""; + if (!Eol) + { + SkipSpace(); + if (GetC()=='\"') + { + bhp oPtr=Ptr; + if (!SkipTo("\"")) return NULL; + int n=Ptr-oPtr-1; + if (n>255) return NULL; + memcpy(Com,oPtr,n); + Com[n]=0; + if (strchr(Com,'\n')) return NULL; + // skip to delimiter + SkipSpace(); + if (!strchr("+-*/,<>=()\t",GetC())) PutBackC(); + } + else + { + PutBackC(); + GetCom(); + } + } + return Com; +} + +char *Parse::RestOfLine() +{ + SkipSpace(); + Com=""; + if (c!=';' && c>=' ') + { + while (GetC()>=' ') Com+=c; + PutBackC(); + } + Eol=TRUE; + return Com; +} + +char *Parse::GetLine() +{ + Com=""; + while (GetC()!='\n' && c!='\r' && !Eof()) Com+=c; + PutBackC(); + // skip to newline + if (c>=0) while (GetC()!='\n' && c>=0); + Eol=FALSE; + return Com; +} + +void Parse::SkipSpace() +{ + if (!Eol) + { + while (GetC()==' ' || c=='\t'); + PutBackC(); + } +} + +BOOL Parse::SkipTo(char *skip) +{ + Eol=FALSE; + int n=strlen(skip); + do + { + int i=0; + while (GetC()==skip[i] && ++i=0); + return FALSE; +} + +BOOL Parse::GoTo(char *g) +{ + Ptr=Buffer; + return SkipTo(g); +} + +// ********************************* + +enum {ID_TEXT,ID_INT,ID_FLOAT}; + +ID *ID::IDList=NULL; + +ID::ID(char *N,int V) +{ + Name=strdup(N); + Value=V; + StrValue=NULL; + // add to IDList + Next=IDList; + IDList=this; + Type=ID_INT; + RefCount=0; +} + +ID::ID(char *N,char *V) +{ + Name=strdup(N); + Value=0; + StrValue=strdup(V); + // add to IDList + Next=IDList; + IDList=this; + Type=ID_TEXT; + RefCount=0; +} + +ID::ID(char *N,double V) +{ + Name=strdup(N); + Value=0; + StrValue=NULL; + DblValue=V; + // add to IDList + Next=IDList; + IDList=this; + Type=ID_FLOAT; + RefCount=0; +} + +ID::~ID() +{ + delete[] Name; + if (StrValue) delete[] StrValue; +} + +void ID::FreeIDs() +{ + ID *I=IDList,*I2; + while (I) + { + I2=I->Next; + delete I; + I=I2; + } + IDList=NULL; +} + +BOOL ID::EvalInt(char *Id,int &i) +{ + ID *I=Find(Id,ID_INT); + if (I) + { + i=I->Value; + I->RefCount++; + } + return I!=NULL; +} + +BOOL ID::EvalStr(char *Id,char *&s) +{ + ID *I=Find(Id,ID_TEXT); + if (I) + { + s=I->StrValue; + I->RefCount++; + } + return I!=NULL; +} + +BOOL ID::EvalDouble(char *Id,double &d) +{ + ID *I=Find(Id,ID_FLOAT); + if (I) + { + d=I->DblValue; + I->RefCount++; + } + return I!=NULL; +} + +ID *ID::Find(char *Id) +{ + ID *I=IDList; + while (I && stricmp(Id,I->Name)) I=I->Next; + return I; +} + +ID *ID::Find(char *Id,int Type) +{ + ID *I=IDList; + while (I && (I->Type!=Type || stricmp(Id,I->Name))) I=I->Next; + if (I) I->RefCount++; + return I; +} + +void ID::Assign(char *V) +{ + if (StrValue) delete[] StrValue; + StrValue=strdup(V); +} + +void ID::Assign(int V) +{ + Value=V; +} + +void ID::Assign(double V) +{ + DblValue=V; +} + +void ID::Set(char *Id,char *V) +{ + ID *I=Find(Id); + if (I==NULL) new ID(Id,V); + else I->Assign(V); +} + +void ID::Set(char *Id,int V) +{ + ID *I=Find(Id); + if (I==NULL) new ID(Id,V); + else I->Assign(V); +} + +void ID::Set(char *Id,double V) +{ + ID *I=Find(Id); + if (I==NULL) new ID(Id,V); + else I->Assign(V); +} + diff --git a/Win/Classlib/parse.h b/Win/Classlib/parse.h new file mode 100755 index 0000000..69240f2 --- /dev/null +++ b/Win/Classlib/parse.h @@ -0,0 +1,85 @@ +#ifndef _parse_h +#define _parse_h + +#include + +class Parse +{ +public: + Parse(bhp,long); + Parse(char*); + + String Com; + char *GetCom(); + char *GetTerm(); + char *GetQStr(); + char *RestOfLine(); + char *GetLine(); + void SkipLine(); + BOOL Eof(); + BOOL SkipTo(char *skip); + BOOL GoTo(char *g); + bhp Buffer; + bhp Ptr; + BOOL Eol; + long BufLen; + char c; + char GetC(); + void PutBackC(); + void SkipSpace(); + + Parse& operator >> (int &i) { i=atoi(GetCom()); return *this; } + Parse& operator >> (long &i) { i=atol(GetCom()); return *this; } +}; + +inline void Parse::PutBackC() +{ + if (!Eol && !Eof()) Ptr--; +} + +inline char Parse::GetC() +{ + return c= ((Ptr +class Pointer +{ +public: + Pointer() + { + Data=NULL; + } + Pointer(long Size) + { + Data=new huge T[Size]; + } + Pointer(T huge *D) + { + Data=D; + } + ~Pointer() + { + Free(); + } + void Free() + { + if (Data) delete[] Data; + Data=NULL; + } + void Alloc(long Size) + { + Free(); + Data=new huge T[Size]; + } + void Copy(T huge *D,int n); + void MemCopy(T huge *D,int n) { memcpy(Data,D,n*sizeof(T)); } + void CopyTo(T huge *D,int n); + + T huge *Data; + operator T huge *() { return Data; } +//if T isnt char + +// operator unsigned char *() { return Data; } + Pointer &operator =(T huge *NewData) + { + Free(); + Data=NewData; + return *this; + } + Pointer &operator =(Pointer &P) + { + Free(); + Data=P.Data; + P.Data=NULL; + return *this; + } + T &operator[](long i) { return Data[i]; }; + T &operator[](int i) { return Data[i]; }; +}; + +template void Pointer::Copy(T huge *D,int n) +{ + Alloc(n); + T *Ptr=Data; + for (int i=0;i void Pointer::CopyTo(T huge *D,int n) +{ + T *Ptr=Data; + for (int i=0;i +#pragma hdrstop + +#include +#include + +Printer::Printer() +{ + memset(&pd, 0, sizeof(PRINTDLG)); + pd.lStructSize = sizeof(PRINTDLG); + GetDefault(); +} + +void Printer::GetDefault() +{ + DeleteGlobals(); + pd.Flags = PD_RETURNDEFAULT; + PrintDlg(&pd); + // pd now has hDevMode and hDevNames set +} + +void Printer::DeleteGlobals() +{ + if (pd.hDevMode != NULL) + { + GlobalFree(pd.hDevMode); + pd.hDevMode=NULL; + } + if (pd.hDevNames != NULL) + { + GlobalFree(pd.hDevNames); + pd.hDevNames=NULL; + } +} + +Printer::~Printer() +{ + DeleteGlobals(); +} + +BOOL Printer::PrintDialog(Window *Obj,UINT Flags) +{ + pd.hwndOwner = Obj->hWnd; + pd.Flags = Flags; + + while (PrintDlg(&pd)==0) + { + switch (CommDlgExtendedError()) + { + case PDERR_DEFAULTDIFFERENT: + { + DEVNAMES *dn=(DEVNAMES*) GlobalLock(pd.hDevNames); + dn->wDefault&=~DN_DEFAULTPRN; + GlobalUnlock(pd.hDevNames); + break; + } + case PDERR_PRINTERNOTFOUND: + MessageBox(Obj->hWnd,"Printer not Found\r\nReverting to default printer","Error",MB_OK); + GetDefault(); + pd.Flags = Flags; // reset flags as GetDefault changes them + break; + default: + return FALSE; + } + } + return TRUE; +} + +void Printer::Setup(Window *Obj) +{ + PrintDialog(Obj,PD_PRINTSETUP); + // pd now has updated hDevMode and hDevNames +} + +void Printer::Print(Window *Obj) +{ + if (PrintDialog(Obj,PD_RETURNDC)) + { + RECT Client; + GetClientRect(Obj->hWnd,&Client); + + SetMapMode(pd.hDC, MM_ANISOTROPIC); + SetWindowExtEx(pd.hDC, Client.right, Client.bottom,NULL); + SetViewportExtEx(pd.hDC, GetDeviceCaps(pd.hDC,HORZRES),GetDeviceCaps(pd.hDC,VERTRES),NULL); + + DOCINFO dInfo={sizeof(DOCINFO),"Test-Doc",NULL}; + StartDoc(pd.hDC,&dInfo); + StartPage(pd.hDC); + + Obj->Paint(pd.hDC,FALSE,Client); + + EndPage(pd.hDC); + EndDoc(pd.hDC); + + DeleteDC(pd.hDC); + } +} + diff --git a/Win/Classlib/printer.h b/Win/Classlib/printer.h new file mode 100755 index 0000000..06a89a0 --- /dev/null +++ b/Win/Classlib/printer.h @@ -0,0 +1,15 @@ +// printer class + +class Printer +{ +public: + Printer(); + ~Printer(); + void Setup(Window *Obj); + void Print(Window *Obj); + void DeleteGlobals(); + void GetDefault(); + BOOL PrintDialog(Window *Obj,UINT Flags); + + PRINTDLG pd; +}; diff --git a/Win/Classlib/radgrp.cpp b/Win/Classlib/radgrp.cpp new file mode 100755 index 0000000..14d730b --- /dev/null +++ b/Win/Classlib/radgrp.cpp @@ -0,0 +1,29 @@ +// Radio Group **************************************** + +#include +#pragma hdrstop + +RadGrp::RadGrp(Object *Parent,int ID_First,int ID_Last,int Value) : Object (Parent) +{ + ID_FIRST=ID_First; + ID_LAST=ID_Last; + SetValue(Value); +} + +void RadGrp::SetValue(int Value) +{ + CheckRadioButton(ParentHWnd,ID_FIRST,ID_LAST,ID_FIRST+Value); +} + +BOOL RadGrp::Create() +{ // Multiple hWnds ??? + return TRUE; +} + + +int RadGrp::GetValue() { +int i; +for (i=ID_FIRST;i<=ID_LAST;i++) if (IsDlgButtonChecked(ParentHWnd,i)) + break; +return i-ID_FIRST; +} diff --git a/Win/Classlib/radgrp.h b/Win/Classlib/radgrp.h new file mode 100755 index 0000000..dab3293 --- /dev/null +++ b/Win/Classlib/radgrp.h @@ -0,0 +1,11 @@ +// radgrp.h + +class RadGrp : Object { + int ID_FIRST; + int ID_LAST; +public: + RadGrp(Object *,int,int,int); + int GetValue(); + void SetValue(int Value); + BOOL Create(); +}; diff --git a/Win/Classlib/realedit.cpp b/Win/Classlib/realedit.cpp new file mode 100755 index 0000000..2a6cd1a --- /dev/null +++ b/Win/Classlib/realedit.cpp @@ -0,0 +1,61 @@ +// RealEdit *********************************************** + +#include +#pragma hdrstop + +#include +#include +#ifdef __BORLANDC__ +#include +#else +#include +#endif + +RealEdit::RealEdit(Object *Parent,int Id,double Value,int Decs,long Flags,double Min,double Max) : Edit(Parent,Id) +{ + FLAGS=Flags; + MIN=Min; + MAX=Max; + char temp[20]; + sprintf(temp,"%.*g",Decs,Value); + SetString(temp); +} + +BOOL RealEdit::CanClose() +{ + char temp[128]=""; + int err=0; + double Value=GetValue(&err); + if (err) strcpy(temp,"Please enter a valid number"); + else if ((ValueMAX && (FLAGS & NE_MAX) && !(FLAGS & NE_MIN))) + sprintf(temp,"Please enter a number below %lf",MAX); + else if ((( ValueMAX ) && (FLAGS & NE_MINMAX))) + sprintf(temp,"Please enter a number between %lf and %lf",MIN,MAX); + else if (Value<0 && (FLAGS & NE_POS)) + strcpy(temp,"Please enter a positive number"); + else if (Value>0 && (FLAGS & NE_NEG)) + strcpy(temp,"Please enter a negative number"); + else if (Value==0 && (FLAGS & NE_NONZERO)) + strcpy(temp,"Please enter a non-zero number"); + if (*temp) + { + // handle needs to be that of window not control + MessageBox(ParentHWnd,temp,"Error",MB_OK | MB_ICONEXCLAMATION); + SendMessage(hWnd, EM_SETSEL,0,MAXINT); + SetFocus(hWnd); + return FALSE; + } + return TRUE; +} + +double RealEdit::GetValue(BOOL *err) +{ + double Value; + char *temp=GetString(); + if (sscanf(temp,"%lf",&Value)<=0 && err) *err=1; + delete[] temp; + return Value; +} + diff --git a/Win/Classlib/realedit.h b/Win/Classlib/realedit.h new file mode 100755 index 0000000..e7a9d62 --- /dev/null +++ b/Win/Classlib/realedit.h @@ -0,0 +1,11 @@ +// realedit.h + +class RealEdit : public Edit { +public: + RealEdit::RealEdit(Object *Parent,int Id,double Value,int Decs=3,long Flags=0,double Min=0,double Max=0); + double GetValue(BOOL *err=NULL); + virtual BOOL CanClose(); +private: + long FLAGS; + double MIN,MAX; +}; diff --git a/Win/Classlib/statbar.cpp b/Win/Classlib/statbar.cpp new file mode 100755 index 0000000..1d76151 --- /dev/null +++ b/Win/Classlib/statbar.cpp @@ -0,0 +1,398 @@ +// status bar class +// drawn in parent window rather than independant window +#include +#pragma hdrstop + +#include + +// status box ********************************************** + +StatusBox::StatusBox(char *T,int X,int W) +{ + Text=strdup(T); + x=X; w=W; +} + +StatusBox::~StatusBox() +{ + if (Text) delete[] Text; +} + +void StatusBox::SetText(char *T) +{ + if (Text) delete[] Text; + Text=strdup(T); +} + +// status bar ********************************************** + +// void Paint(HDC dc); +EV_START(StatusBar) + EV_MESSAGE(WM_KEYUP,WMKeyUp) + EV_MESSAGE(WM_KEYDOWN,WMKeyDown) + EV_MESSAGE(WM_SETFOCUS,WMFocus) + EV_MESSAGE(WM_SIZE,WMSize) + EV_MESSAGE(WM_GETMINMAXINFO,WMGetMinMaxInfo) + EV_MESSAGE(WM_MENUSELECT,WMMenuSelect) +EV_END + +StatusBox *StatusBar::AddBox(char *T,int w) +{ + StatusBox *S=new StatusBox(T,dyBorderx8 + Pos,w*CharWidth); + BoxList.AddTail(S); + Pos+=w*CharWidth+dyBorderx8; + return S; +} + +void StatusBar::InsertBox(int After,char *T,int w) +{ + StatusBox *Box=BoxList[After]; + + StatusBox *S=new StatusBox(T,Box->x+Box->w+dyBorderx8,w*CharWidth); + S->SetText(T); + BoxList.AddAfter(S,Box); + BoxList.GetNext(S); + while (S) + { + S->x+=w*CharWidth+dyBorderx8; + BoxList.GetNext(S); + } + DrawText(Box); + Invalidate(); +} + + +StatusBar::StatusBar(Object *P,int w,BOOL NumBox,BOOL CapsBox) : Object(P) +{ + dyBorder = GetSystemMetrics(SM_CYBORDER); + dyBorderx2 = dyBorder * 2; + dyBorderx3 = dyBorder * 3; + dyBorderx8 = dyBorder * 8; + dyBorderx9 = dyBorder * 9; + + HDC dc = GetDC(NULL); + int Fntheight = MulDiv(-10, GetDeviceCaps(dc, LOGPIXELSY), 72); + Font = CreateFont(Fntheight, 0, 0, 0, 400, 0, 0, 0, + ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, + DEFAULT_QUALITY, VARIABLE_PITCH | FF_SWISS, "Helv"); + SelectObject(dc, Font); + + TEXTMETRIC tm; + GetTextMetrics(dc, &tm); + ReleaseDC(NULL, dc); + + dyStatbar = tm.tmHeight + tm.tmExternalLeading + (7*dyBorder); + CharWidth = tm.tmMaxCharWidth; + Pos=0; + TextBox=AddBox("",w); + NumLock = NumBox ? AddBox("",3) : NULL; + CapsLock = CapsBox ? AddBox("",3) :NULL; + nMenu=0; + OldText=NULL; +} + +StatusBar::~StatusBar() +{ + if (OldText) delete[] OldText; + DeleteObject(Font); +} + +BOOL StatusBar::SetupWindow() +{ + nMenu=0; + ParsePopup(GetMenu(ParentHWnd)); + return TRUE; +} + +void StatusBar::SetText(char *T) +{ + TextBox->SetText(T); + DrawText(TextBox); +} + +void StatusBar::SetText(int Box,char *T) +{ + StatusBox *S=BoxList[Box]; + S->SetText(T); + DrawText(S); +} + +void StatusBar::DrawText(StatusBox *S) +{ + if (!S->Text) return; + + RECT rc,rcTemp; + HDC dc = GetDC(ParentHWnd); + GetClientRect(ParentHWnd, &rc); + rc.top = rc.bottom - dyStatbar; + + + rcTemp.top = rc.top + dyBorder*4; + rcTemp.bottom = rc.bottom - dyBorderx3; + rcTemp.left = dyBorder + S->x; + rcTemp.right = rcTemp.left + S->w- dyBorder; + + SelectObject(dc, Font); + SetTextColor(dc, GetSysColor(COLOR_BTNTEXT)); + SetBkColor(dc, GetSysColor(COLOR_BTNFACE)); + + ExtTextOut(dc, rcTemp.left + dyBorderx2, rcTemp.top, + ETO_OPAQUE | ETO_CLIPPED, &rcTemp, S->Text, + strlen(S->Text), NULL); + ReleaseDC(ParentHWnd, dc); + +} + +void StatusBar::Paint(HDC dc) +{ + RECT rc,rcTemp; + GetClientRect(ParentHWnd, &rc); + rc.top = rc.bottom - dyStatbar; + +/* Border color */ + HBRUSH Brush = CreateSolidBrush(GetSysColor(COLOR_BTNFACE)); + rcTemp = rc; + rcTemp.top += dyBorderx2; + FillRect(dc, &rcTemp, Brush); + DeleteObject(Brush); + +/* Shadow color */ + Brush = CreateSolidBrush(GetSysColor(COLOR_BTNSHADOW)); + /* Top and left of boxes */ + StatusBox *Ptr=BoxList.GetFirst(); + while (Ptr) + { + rcTemp.top = rc.top + dyBorderx3; + rcTemp.bottom = rcTemp.top + dyBorder; + rcTemp.left = Ptr->x; + rcTemp.right = rcTemp.left + Ptr->w; + FillRect(dc, &rcTemp, Brush); + rcTemp.bottom = rc.bottom - dyBorderx2; + rcTemp.right = rcTemp.left + dyBorder; + FillRect(dc, &rcTemp, Brush); + BoxList.GetNext(Ptr); + } + DeleteObject(Brush); + + Brush = CreateSolidBrush(GetSysColor(COLOR_BTNHIGHLIGHT)); + /* Bottom and right of boxes */ + Ptr=BoxList.GetFirst(); + while (Ptr) + { + rcTemp.top = rc.bottom - dyBorderx3; + rcTemp.bottom = rcTemp.top + dyBorder; + rcTemp.left = Ptr->x; + rcTemp.right = rcTemp.left + Ptr->w; + FillRect(dc, &rcTemp, Brush); + + rcTemp.top = rc.top + dyBorderx3; + rcTemp.left = Ptr->x+Ptr->w; + rcTemp.right = rcTemp.left + dyBorder; + FillRect(dc, &rcTemp, Brush); + + BoxList.GetNext(Ptr); + } + + /* Across the top */ + rcTemp = rc; + rcTemp.top += dyBorder; + rcTemp.bottom = rcTemp.top + dyBorder; + FillRect(dc, &rcTemp, Brush); + DeleteObject(Brush); + + /* solid black line across top */ + + Brush = CreateSolidBrush(GetSysColor(COLOR_WINDOWTEXT)); + rcTemp = rc; + rcTemp.bottom = rcTemp.top; + rcTemp.bottom += dyBorder; + FillRect(dc, &rcTemp, Brush); + DeleteObject(Brush); + + /* now the text, with the button face background */ + + SelectObject(dc, Font); + SetTextColor(dc, GetSysColor(COLOR_BTNTEXT)); + SetBkColor(dc, GetSysColor(COLOR_BTNFACE)); + + /* Text */ + Ptr=BoxList.GetFirst(); + while (Ptr) + { + if (Ptr->Text) + { + rcTemp.top = rc.top + dyBorder*4; + rcTemp.bottom = rc.bottom - dyBorderx3; + rcTemp.left = dyBorder + Ptr->x; + rcTemp.right = rcTemp.left + Ptr->w - dyBorder; + ExtTextOut(dc, rcTemp.left + dyBorderx2, rcTemp.top, + ETO_OPAQUE | ETO_CLIPPED, &rcTemp, Ptr->Text, + strlen(Ptr->Text), NULL); + } + BoxList.GetNext(Ptr); + } +} + +BOOL StatusBar::bInKeyDown=FALSE; + +BOOL StatusBar::WMKeyUp(TMSG &) +{ + bInKeyDown = FALSE; + return TRUE; +} + +BOOL StatusBar::WMKeyDown(TMSG &Msg) +{ + if ( !bInKeyDown ) + { + if ( Msg.wParam == VK_CAPITAL ) SetCapsLockText(); + else if ( Msg.wParam == VK_NUMLOCK ) SetNumLockText(); + bInKeyDown = TRUE; + } + return TRUE; +} + +BOOL StatusBar::WMFocus(TMSG &) +{ + SetCapsLockText(); + SetNumLockText(); + return TRUE; +} + +void StatusBar::SetNumLockText() +{ + /* Num Lock Text */ + if (NumLock) + { + int State=GetKeyState(VK_NUMLOCK) & 0x0001; + NumLock->SetText(State ? "NUM" : ""); + DrawText(NumLock); + } +} + +void StatusBar::SetCapsLockText() +{ + /* Caps Lock Text */ + if (CapsLock) + { + int State=GetKeyState(VK_CAPITAL) & 0x0001; + CapsLock->SetText(State ? "CAPS" : ""); + DrawText(CapsLock); + } +} + +void StatusBar::Invalidate() +{ + RECT rc; + GetClientRect(ParentHWnd, &rc); + rc.top = rc.bottom - dyStatbar; + InvalidateRect(ParentHWnd,&rc,TRUE); +} + +BOOL StatusBar::WMSize(TMSG &) +{ + RECT rc; + GetClientRect(ParentHWnd, &rc); + rc.top = rc.bottom - dyStatbar; + if (rc.top < StatRecSizeAdv.top) + { + /* Window SHRANK, need to invalidate current status rect */ + InvalidateRect(ParentHWnd, &rc,FALSE); + } + else + { + /* Window GREW, need to invalidate prev status rect */ + InvalidateRect(ParentHWnd, &StatRecSizeAdv,TRUE); + } + return TRUE; +} + +BOOL StatusBar::WMGetMinMaxInfo(TMSG &) +{ + GetClientRect(ParentHWnd, &StatRecSizeAdv); + StatRecSizeAdv.top = StatRecSizeAdv.bottom - dyStatbar; + return TRUE; +} + +#define NOMENU 0 +// WM_MENUSELECT +BOOL StatusBar::WMMenuSelect(TMSG &Msg) +{ + int nMenuID=NOMENU; + WORD flags=LLHW(Msg); + WORD item=LOWWPARAM(Msg.wParam); + + if (HILP32(Msg) == NULL && flags == 0xffff) + { + // Exiting menu mode. hMenu && hmenuPopup are NULL. + //nMenuID = NOMENU; + + if (OldText) + { + SetText(OldText); + delete[] OldText; + OldText=NULL; + } + + return TRUE; + } + else if (flags & MF_SYSMENU) + { + // System menu is up + if (!(flags & MF_POPUP)) nMenuID = item; + // System menu item is selected + // System menu: item contains SC_* code. + else nMenuID = IDM_SYSMENU; + // System menu and no item is selected + } + else + { + if ((flags & MF_POPUP)) + { + // Pulling down a popup submenu: hmenuPopup contains popup handle. + #ifdef WIN32 + HMENU hm=GetSubMenu((HMENU) Msg.lParam,item); + for (int i=0;iText ? TextBox->Text : ""); // null is reserved to indicate not in menu + SetText(temp); + return TRUE; +} + +void StatusBar::ParsePopup(HMENU hm) +{ + if (hm==NULL) return; + HMENU hm2; + int n=GetMenuItemCount(hm); + for (int i=0;i + +class StatusBox : public BaseNode +{ +public: + StatusBox(char *T,int X,int W); + ~StatusBox(); + void SetText(char *T); + + int x,w; + char *Text; +}; + +class StatusBar : Object +{ +public: + StatusBar(Object *P,int w,BOOL NumBox,BOOL CapsBox); + ~StatusBar(); + BOOL SetupWindow(); + void SetText(char *); + void SetText(int,char *); + void Paint(HDC dc); + void Invalidate(); + void InsertBox(int After,char *T,int w); + + BOOL WMKeyUp(TMSG &); + BOOL WMKeyDown(TMSG &); + BOOL WMFocus(TMSG &); + BOOL WMSize(TMSG &); + BOOL WMGetMinMaxInfo(TMSG &); + BOOL WMMenuSelect(TMSG &Msg); + int dyStatbar; +private: + StatusBox *AddBox(char *T,int w); + void DrawText(StatusBox *S); + char *OldText; + StatusBox *TextBox,*NumLock,*CapsLock; + List BoxList; + int dyBorder,dyBorderx2,dyBorderx3,dyBorderx8,dyBorderx9; + int Pos,CharWidth; + int nMenu; + HMENU hMenu[10]; + HFONT Font; + static BOOL bInKeyDown; + RECT StatRecSizeAdv; + + void SetNumLockText(); + void SetCapsLockText(); + void ParsePopup(HMENU hm); + + EV_ENABLE(StatusBar) +}; + diff --git a/Win/Classlib/stringc.cpp b/Win/Classlib/stringc.cpp new file mode 100755 index 0000000..6ba6938 --- /dev/null +++ b/Win/Classlib/stringc.cpp @@ -0,0 +1,120 @@ +#include +#pragma hdrstop + +#include + +#include + +int String::Decimals=2; + +void String::Set(char*S) +{ + if (S==NULL) + { + Str=NULL; + Length=Max=0; + } + else + { + Str=strdup(S); + Length=strlen(Str); + Max=Length+1; + } +} + +void String::Assign(char*S) +{ + if (S==NULL) + { + if (Str) Str[0]=0; + Length=0; + } + else + { + Length=strlen(S); + if (Length>=Max) + { + if (Str) delete[] Str; + Str=strdup(S); + Max=Length+1; + } + else strcpy(Str,S); + } +} + +// asummes pMax) ReAlloc(Length+la+1); + memmove(Str+p+la,Str+p,Length-p+1); + strncpy(Str+p,s,la); + Length+=la; +} + +void String::Insert(char c,int p) +{ + if (Length+2>Max) ReAlloc(Length+2); + memmove(Str+p+1,Str+p,Length-p+1); + Str[p]=c; + Length+=1; +} + +void String::AddString(char *Add) +{ + int la=strlen(Add); + if (Length+la+1>Max) ReAlloc(Length+la+1); + strcat(Str,Add); + Length+=la; +} + +void String::AddChar(char Add) +{ + if (Length+1>=Max) ReAlloc(Length+2); + Str[Length++]=Add; + Str[Length]=0; +} + + +void String::ReAlloc(int NewLen) +{ + char *NewStr=new char[Max=NewLen]; + if (Str) + { + strcpy(NewStr,Str); + delete[] Str; + } + else *NewStr=0; + Str=NewStr; +} + +String & String::operator << (int i) +{ + char temp[11]; + AddString(itoa(i,temp,10)); + return *this; +} + +String & String::operator << (long i) +{ + char temp[11]; + AddString(ltoa(i,temp,10)); + return *this; +} + +String & String::operator << (double d) +{ + char Buf[32]; + sprintf(Buf,"%.*lf",Decimals,d); + AddString(Buf); + return *this; +} + +void String::Pad(int Where,char Pad,int n) +{ + if (Length+n>=Max) ReAlloc(Length+n+1); // +1 for null + memmove(Str+Where+n,Str+Where,Length-Where+1); // +1 for null + for (int i=0;i +#include + +#ifndef _stringc_h +#define _stringc_h + +class String +{ +public: + char *Str; + int Length,Max; + String() + { + Str=NULL; + Max=Length=0; + } + String(char *S) { Set(S); } + String(String &S) { Set(S); } + void Set(char*S); + String(char *S,int n) + { + Str=new char[Max=n+1]; + Length=0; + Assign(S); + } + String(int n) + { + Str=new char[Max=n]; + *Str=Length=0; + } + ~String() + { + if (Str) delete[] Str; + } + int Len() { return Length; } + void Len(int NewLen) + { + Str[NewLen]=0; + Length=NewLen; + } + void Update() + { + Length=strlen(Str); + } + int Size() { return Max; } + void AddString(char *Add); + void ReAlloc(int NewLen); + void Assign(char*S); + void AddChar(char Add); + int Pos(char c) + { + char *p=strchr(Str,c); + return p ? p-Str : -1; + } + int Pos(char c,int pos) + { + char *p=strchr(Str+pos,c); + return p ? p-Str : -1; + } + int rPos(char c) + { + char *p=strrchr(Str,c); + return p ? p-Str : -1; + } + int rPos(char c,int pos) + { + char temp=Str[pos]; + Str[pos]=0; + char *p=strrchr(Str,c); + Str[pos]=temp; + return p ? p-Str : -1; + } + void Insert(char *s,int p); + void Insert(char c,int p); + void Remove(int From,int n) + { + memmove(Str+From,Str+From+n,Length-From-n+1); + Update(); + } + void Pad(int Where,char Pad,int n); + char Last() { return Length ? Str[Length-1] : 0; } + + String & operator << (char *Add) { AddString(Add); return *this; } + String & operator += (char *Add) { return *this << Add; } + String & operator << (char Add) { AddChar(Add); return *this; } + String & operator += (char Add) { return *this << Add; } + String & operator << (String &Add) { AddString(Add); return *this; } + String & operator += (String &Add) { return *this << Add; } + + String & operator << (int i); + String & operator += (int i) { return *this << i; } + String & operator >> (int &i) { i=atoi(Str); return *this; } + + String & operator << (long i); + String & operator += (long i) { return *this << i; } + String & operator >> (long &i) { i=atol(Str); return *this; } + + String & operator << (double d); + String & operator >> (double &d) { d=atof(Str); return *this; } + + static int Decimals; + operator char*() { return Str; } + + String & operator =(String &S) { Assign(S); return *this; } + String & operator =(char *S) { Assign(S); return *this; } + int operator < (String &S) { return strcmp(Str,S)<0; } + int operator <= (String &S) { return strcmp(Str,S)<=0; } + int operator ==(String &S) { return strcmp(Str,S)==0; } + int operator !=(String &S) { return strcmp(Str,S)!=0; } + int operator > (String &S) { return strcmp(Str,S)>0; } + int operator >= (String &S) { return strcmp(Str,S)>=0; } + + int operator < (char *S) { return strcmp(Str,S)<0; } + int operator <= (char *S) { return strcmp(Str,S)<=0; } + int operator ==(char *S) { return strcmp(Str,S)==0; } + int operator !=(char *S) { return strcmp(Str,S)!=0; } + int operator > (char *S) { return strcmp(Str,S)>0; } + int operator >= (char *S) { return strcmp(Str,S)>=0; } + + char &operator [](int Pos) { return Str[Pos]; } + BOOL Empty() { return Str==NULL || *Str==0; } + BOOL Null() { return Str==NULL; } + void ToUpper() { strupr(Str); } + void ToLower() { strlwr(Str); } + + String & operator --() + { + if (Length) Str[--Length]=0; + return *this; + } +}; + +#endif diff --git a/Win/Classlib/textwnd.cpp b/Win/Classlib/textwnd.cpp new file mode 100755 index 0000000..b7d0ba7 --- /dev/null +++ b/Win/Classlib/textwnd.cpp @@ -0,0 +1,186 @@ +// simple Text window + +#include +#pragma hdrstop + +#include +#include + +#ifdef WIN32 +#define MaxLines 2000 +#else +#define MaxLines 1000 +#endif + +EV_START(TextWindow) + EV_MESSAGE(WM_CLOSE,WMClose) + EV_MESSAGE(WM_CREATE,WMCreate) +EV_END + +TextWindow::TextWindow(Object *Parent,char *title,BOOL Stay) : Window(Parent,title) +{ + App::TextWindow=this; + App::Active=TRUE; // hack + Style=WS_OVERLAPPEDWINDOW; + StayOpen=Stay; + First=NULL; + Last=NULL; + Lines=0; + MaxStr=0; + Flags|=W_OVERSCROLL; +} + +char *TextWindow::GetClassName() +{ + return "TextWindow"; +} + +TextWindow::~TextWindow() +{ + App::TextWindow=NULL; + DeleteObject(Font); + // clean up mem + ClearMem(); +} + +void TextWindow::ClearMem() +{ + TWString *temp=First,*temp2; + while (temp) + { + delete[] temp->String; + temp2=temp->Next; + delete temp; + temp=temp2; + } +} + +void TextWindow::Clear() +{ + ClearMem(); + First=NULL; + Last=NULL; + Lines=0; + MaxStr=0; + InvalidateRect(hWnd,NULL,TRUE); +} + +BOOL TextWindow::WMCreate(TMSG&) +{ + ShowWindow(hWnd, SW_HIDE ); + + TEXTMETRIC tm; + HDC dc=GetDC(hWnd); + + LOGFONT lf; + memset(&lf,0,sizeof(lf)); + lf.lfHeight=-12; + lf.lfWeight=FW_NORMAL; + lf.lfCharSet=ANSI_CHARSET; + lf.lfOutPrecision=OUT_TT_PRECIS; + lf.lfClipPrecision=CLIP_DEFAULT_PRECIS; + lf.lfQuality=PROOF_QUALITY; + lf.lfPitchAndFamily=4; //FIXED_PITCH | 4 | FF_MODERN; + strcpy(lf.lfFaceName,"Courier New"); + Font=CreateFontIndirect(&lf); + HFONT OldFont=(HFONT) SelectObject(dc,Font); + GetTextMetrics(dc, &tm); + SelectObject(dc,OldFont); + ReleaseDC(hWnd, dc); + + FontHeight = tm.tmHeight + tm.tmExternalLeading; + FontWidth = tm.tmAveCharWidth; + return FALSE; +} + +BOOL TextWindow::WMClose(TMSG&) +{ + if (Parent) + { + ShowWindow(hWnd, SW_HIDE ); + SetFocus(ParentHWnd); + } + else DestroyWindow(); + return TRUE; +} + +void TextWindow::AddString(char *S) +{ + TWString *temp; + if (Lines==MaxLines) + { + // delete last + temp=Last->Prev; + delete[] Last->String; + delete Last; + Last=temp; + Last->Next=NULL; + } + else Lines++; + + temp=new TWString; + temp->String=strdup(S); + temp->Next=First; + if (First) First->Prev=temp; + else Last=temp; + First=temp; + temp->Prev=NULL; + if ((int)strlen(S)>MaxStr) MaxStr=strlen(S); + + SetVirtualExtent(MaxStr*FontWidth,Lines*FontHeight,FontWidth,FontHeight,0,0); + ShowWindow(hWnd, SW_SHOW ); + + RECT rc; + GetClientRect(hWnd,&rc); + if (Lines*FontHeightString,strlen(temp->String)); + y+=FontHeight; + temp=temp->Prev; + } + SelectObject(dc,OldFont); + ::SetBkColor(dc,OldBk); +} + +void TextWindowInit(Object *Parent,char *Title,BOOL Stay) +{ + if (App::TextWindow==NULL) (new TextWindow(Parent,Title,Stay))->Create(); +} + +int printf(const char *fmt,...) +{ + char temp[256]; + va_list ap; + va_start(ap,fmt); + vsprintf(temp,fmt,ap); + va_end(ap); + if (App::TextWindow==NULL) + { + FName F; + GetModuleFileName(App::hInstance,F,MAX_PATH); + TextWindowInit(0,F.GetName(),TRUE); + } + App::TextWindow->AddString(temp); + // if message loop?? + return 0; +} diff --git a/Win/Classlib/textwnd.h b/Win/Classlib/textwnd.h new file mode 100755 index 0000000..b36ce99 --- /dev/null +++ b/Win/Classlib/textwnd.h @@ -0,0 +1,38 @@ +// textwnd.h + +#define TW_STAYOPEN 1 + +class TextWindow : public Window +{ +public: + TextWindow(Object *Parent,char *title,BOOL Stay=FALSE); + ~TextWindow(); + virtual char *GetClassName(); + void AddString(char *S); + void Clear(); + BOOL StayOpen; +protected: + int Lines,MaxStr,FontHeight,FontWidth; + HFONT Font; + + struct TWString + { + char *String; + TWString *Next,*Prev; + } *First,*Last; + + void ClearMem(); + void Paint(HDC, BOOL, RECT&); + BOOL WMClose(TMSG&); + BOOL WMCreate(TMSG&); + +EV_ENABLE(TextWindow) +}; + +void TextWindowInit(Object *Parent,char *Title,BOOL Stay=TRUE); +extern "C" +{ + int printf(const char *fmt,...); +} + + diff --git a/Win/Classlib/unitedit.cpp b/Win/Classlib/unitedit.cpp new file mode 100755 index 0000000..53f3222 --- /dev/null +++ b/Win/Classlib/unitedit.cpp @@ -0,0 +1,220 @@ +#include +#pragma hdrstop + +#include +#include +#include + +#ifdef __BORLANDC__ +#include +#else +#include +#endif + +Unit SciUnit[]={ + {"T", 1e12 }, + {"G", 1e9 }, + {"M", 1e6 }, + {"k", 1000 }, + {"", 1.00 }, + {"m", 1e-3 }, + {"u", 1e-6 }, + {"n", 1e-9 }, + {"p", 1e-12}, + {"f", 1e-15}, + {"a", 1e-18}, + {"", 0}}, + + TimeUnit[]={ + {"h", 3600 }, + {"hr", 3600 }, + {"m", 60 }, + {"min",60 }, + {"s", 1.00 }, + {"ms", 1e-3 }, + {"us", 1e-6 }, + {"", 0}}, + + MetricUnit[]={ + {"km", 1000 }, + {"m", 1.00 }, + {"cm", 1e-2 }, + {"mm", 1e-3 }, + {"um", 1e-6 }, + {"nm", 1e-9 }, + {"", 0}}; + +Unit *UTab[]={NULL,SciUnit,TimeUnit,MetricUnit}; + +UnitEdit::UnitEdit(Object *Parent,int Id,double Value,int iUnit,char *Unit,long Flags,double Min,double Max) + : Edit(Parent,Id) +{ + Units=strdup(Unit); + UnitID=iUnit; + UnitTable=UTab[UnitID]; + FLAGS=Flags; + MIN=Min; + MAX=Max; + Express(Value); +} + +UnitEdit::UnitEdit(Object *Parent,int Id,double Value,Unit *U,long Flags,double Min,double Max) + : Edit(Parent,Id) +{ + Units=strdup(""); + UnitID=U_CUSTOM; + UnitTable=U; + FLAGS=Flags; + MIN=Min; + MAX=Max; + Express(Value); +} + +BOOL UnitEdit::FindUnit(char *S, double &x) +{ + if (*S==0) return TRUE; + char temp[10]; + + for ( Unit *U=UnitTable ; U->Modifier!=0 ; U++) + { + // add U->UnitStr to Units + strcpy(temp,U->UnitStr); + strcat(temp,Units); + if (!strcmp(temp,S)) + { + x= U->Modifier>0 ? U->Modifier*x : -U->Modifier/x; + return TRUE; + } + } + return FALSE; +} + +// express Value with Given units +void UnitEdit::Express(double Value) +{ + // validate Value, so scroll bar doesnt go beyond range + if (ValueMAX && (FLAGS & NE_MAX)) Value=MAX; + // could do some more validation + + double tempr; + char Str[20],temp[10]; + if (UnitID==U_FIXED) + { + strcpy(temp,Units); + tempr=Value; + } + else + { + Unit *U,*U2; + for (U=UnitTable,U2=NULL;U->Modifier!=0;U++) + { + if (U->Modifier>0) // do not use reciprocal units + { + tempr=fabs(Value/U->Modifier); + if ((U->Modifier==1.0 && !U2) || (tempr>=0.1 && tempr<100)) U2=U; + } + } + if (U2==NULL) U2=UnitTable; // no 1.00 modifier and no match + + // in case reciprocal unit is first on list + tempr= U2->Modifier>0 ? Value/U2->Modifier : -1/(Value*U2->Modifier); + strcpy(temp,U2->UnitStr); + strcat(temp,Units); + } + + sprintf(Str,"%.4g",tempr); + + if (*temp) + { + strcat(Str," "); + strcat(Str,temp); + } + SetString(Str); +} + +UnitEdit::~UnitEdit() +{ + delete[] Units; +} + +enum {U_INVALIDNUMBER=1,U_WRONGUNITS}; + +BOOL UnitEdit::CanClose() +{ + char temp[128]=""; + int err; + double Value=GetValue(&err); + if (err==U_INVALIDNUMBER) strcpy(temp,"Please enter a valid number"); + else if (err==U_WRONGUNITS) strcpy(temp,"Unknown units"); + else if ((ValueMAX && (FLAGS & NE_MAX) && !(FLAGS & NE_MIN))) + sprintf(temp,"Please enter a number below %lf",MAX); + else if ((( ValueMAX ) && (FLAGS & NE_MINMAX))) + sprintf(temp,"Please enter a number between %lf and %lf",MIN,MAX); + else if (Value<0 && (FLAGS & NE_POS)) + strcpy(temp,"Please enter a positive number"); + else if (Value>0 && (FLAGS & NE_NEG)) + strcpy(temp,"Please enter a negative number"); + else if (Value==0 && (FLAGS & NE_NONZERO)) + strcpy(temp,"Please enter a non-zero number"); + if (*temp) + { + // handle needs to be that of window not control + MessageBox(ParentHWnd,temp,"Error",MB_OK | MB_ICONEXCLAMATION); + SendMessage(hWnd, EM_SETSEL,0,MAXINT); + SetFocus(hWnd); + return FALSE; + } + return TRUE; +} + +double UnitEdit::GetValue(BOOL *err) +{ + double Value; + char Extra[20]=""; + int error=0; + char *temp=GetString(); + if (sscanf(temp,"%lf%s",&Value,Extra)<=0) error=U_INVALIDNUMBER; + // search for Extra modifier + if (UnitID==U_FIXED) + { + if (*Extra && strcmp(Extra,Units)) error=U_WRONGUNITS; + } + else + { + if (!FindUnit(Extra,Value)) error=U_WRONGUNITS; + } + + if (err) *err=error; + delete[] temp; + return Value; +} + +BOOL UnitEdit::WndProc(TMSG &Msg) +{ + if (Msg.Msg==WM_VSCROLL) + { + switch (Msg.wParam) + { + case SB_LINEUP: + if (CanClose()) + { + double V=GetValue(); + Express(V+pow(floor(log10(V))-2,10)); + } + break; + case SB_LINEDOWN: + if (CanClose()) + { + double V=GetValue(); + Express(V-pow(floor(log10(V))-2,10)); + } + break; + } + } + + return ((Msg.RetVal=DefProc(hWnd,Msg.Msg,Msg.wParam,Msg.lParam))==0); +} + diff --git a/Win/Classlib/unitedit.h b/Win/Classlib/unitedit.h new file mode 100755 index 0000000..d16cd14 --- /dev/null +++ b/Win/Classlib/unitedit.h @@ -0,0 +1,27 @@ +// unit edit + +enum {U_FIXED,U_SCI,U_TIME,U_METRIC,U_CUSTOM}; + +struct Unit +{ + char *UnitStr; + double Modifier; +}; + +class UnitEdit : public Edit { +public: + UnitEdit(Object *Parent,int Id,double Value,int iUnit,char *Unit="",long Flags=0,double Min=0,double Max=0); + UnitEdit(Object *Parent,int Id,double Value,Unit *U,long Flags=0,double Min=0,double Max=0); + virtual ~UnitEdit(); + double GetValue(BOOL *err=NULL); + virtual BOOL WndProc(TMSG &); + + long FLAGS; + double MIN,MAX; + char *Units; + int UnitID; + Unit *UnitTable; + BOOL FindUnit(char *S, double &x); + void Express(double Value); + virtual BOOL CanClose(); +}; diff --git a/Win/Classlib/vectorc.h b/Win/Classlib/vectorc.h new file mode 100755 index 0000000..3ab9901 --- /dev/null +++ b/Win/Classlib/vectorc.h @@ -0,0 +1,43 @@ + +#include + +template + +class Vector +{ +public: + T x,y,z; + + Vector(T X,T Y,T Z) { x=X; y=Y; z=Z; } + Vector(Vector &v) { x=v.x; y=v.y; z=v.z; } + + T Length2() { return sqrt(x*x+y*y+z*z); } + T Length() { return sqrt(Length2()); } + + Vector& operator=(T v) { x=y=z=v; return *this; } + Vector &operator=(Vector &v) { x=v.x; y=v.y; z=v.z; return *this; } + + Vector operator+(Vector &v) { return Vector( x+v.x , y+v.y , z+v.z ); } + Vector operator-(Vector &v) { return Vector( x-v.x , y-v.y , z-v.z ); } + Vector operator*(Vector &v) { return Vector( y*v.z - z*v.y , z*v.x - x*v.z , x*v.y - y*v.x ); } + + Vector operator*(T t) { return Vector(x*t,y*t,z*t); } + Vector operator/(T t) { return Vector(x/t,y/t,z/t); } + + Vector &operator+=(Vector &v) { x+=v.x; y+=v.y; z+=v.z; return *this; } + Vector &operator-=(Vector &v) { x-=v.x; y-=v.y; z-=v.z; return *this; } + Vector &operator*=(Vector &v) + { + T xtemp= y*v.z - z*v.y; + T ytemp= z*v.x - x*v.z; + z= x*v.y - y*v.x; + x=xtemp; + y=ytemp; + return *this; + } + + Vector &operator*=(T t) { x*=t; y*=t; z*=t; return *this; } + Vector operator/=(T t) { x/=t; y/=t; z/=t; return *this; } + +}; + diff --git a/Win/Classlib/windinfo.cpp b/Win/Classlib/windinfo.cpp new file mode 100755 index 0000000..7189933 --- /dev/null +++ b/Win/Classlib/windinfo.cpp @@ -0,0 +1,48 @@ +// list of predefined window classes stored as objects +#include +#pragma hdrstop + +WindowInfo *WindowInfo::WinObjList=NULL; + +WindowInfo::WindowInfo(Object *O,HWND hw) +{ + Next=WinObjList; + WinObjList=this; + if (Next) Next->Prev=this; + Prev=NULL; + hWnd=hw; + Obj=O; +} + +WindowInfo::~WindowInfo() +{ + if (Prev) Prev->Next=Next; + else WinObjList=Next; + if (Next) Next->Prev=Prev; +} + +Object *WindowInfo::Find(HWND hw) +{ + WindowInfo *Ptr=WinObjList; + while (Ptr) + { + if (Ptr->hWnd==hw) return Ptr->Obj; + Ptr=Ptr->Next; + } + return NULL; +} + +void WindowInfo::Delete(HWND hw) +{ + WindowInfo *Ptr=WinObjList; + while (Ptr) + { + if (Ptr->hWnd==hw) + { + delete Ptr; + return; + } + Ptr=Ptr->Next; + } +} + diff --git a/Win/Classlib/windinfo.h b/Win/Classlib/windinfo.h new file mode 100755 index 0000000..0e22dc6 --- /dev/null +++ b/Win/Classlib/windinfo.h @@ -0,0 +1,15 @@ +// window object list + +class WindowInfo +{ +private: + WindowInfo *Next,*Prev; + HWND hWnd; + Object *Obj; + static WindowInfo *WinObjList; +public: + WindowInfo(Object *O,HWND hw); + ~WindowInfo(); + static Object *Find(HWND hw); + static void Delete(HWND hw); +}; diff --git a/Win/Classlib/window.cpp b/Win/Classlib/window.cpp new file mode 100755 index 0000000..0ca79a5 --- /dev/null +++ b/Win/Classlib/window.cpp @@ -0,0 +1,654 @@ +// Window definitions ************************** + +#include +#pragma hdrstop + +#include +#include +#include + +Window::Window(Object *Parent,char *aTitle,char *aName) : Object(Parent) +{ + //grab title + Title=strdup(aTitle); + Flags=0; + Name=aName ? strdup(aName) : NULL; + hWnd=0; + //set defaults + Style=WS_OVERLAPPEDWINDOW; + x = CW_USEDEFAULT; + y = 0; + w = CW_USEDEFAULT; + h = 0; + + Menu = NULL; + OldStyle = 0; + Icon=-1; + // scroll bar stuff + // sensible defalults ? + + xMax=GetSystemMetrics(SM_CXFULLSCREEN); + yMax=GetSystemMetrics(SM_CYFULLSCREEN); + xStep=yStep=1; + xPos=yPos=0; + StatBar=NULL; +} + +char *Window::GetClassName() +{ + return App::Name; +} + +/* The Object can be deleted in two ways +1) Windows sends a WM_CLOSE and then a WM_DESTROY message which does 'delete this' +2) The application does 'delete Object' at any time +*/ + +Window::~Window() +{ + //free memory + if (Title) delete[] Title; + if (Name) delete[] Name; + if (HIWORD(Menu)) delete[] Menu; + + // no more messages, not even destroy + SetWindowLong(hWnd,GWL_WNDPROC,(long) NullProc); + SetPointer( hWnd, NULL ); + + // only destroy if option 2) above + if (!(Flags & W_DESTROYED)) DestroyWindow(); +} + +// MDI will change this +void Window::DestroyWindow() +{ + ::DestroyWindow(hWnd); +} + +BOOL Window::Register() +{ + WNDCLASS wc; + + if ( !GetClassInfo( 0, GetClassName(), &wc) && + !GetClassInfo(App::hInstance, GetClassName(),&wc) ) + { + wc.style = CS_DBLCLKS ; //CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = InitialisationProc; + wc.cbClsExtra = 0; + // Reserve extra bytes for each instance of the window; + // we will use these bytes to store a pointer to the C++ + // (MainWindow) object corresponding to the window. + // the size of a 'this' pointer depends on the memory model. + wc.cbWndExtra = sizeof( Window * ); + + wc.hInstance = App::hInstance; + wc.hIcon = Icon>=0 ? LoadIcon( App::hInstance, MAKEINTRESOURCE(Icon)) : NULL; + wc.hCursor = LoadCursor( NULL, IDC_ARROW ); + wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + + wc.lpszMenuName = NULL; + wc.lpszClassName = GetClassName(); + + return RegisterClass( &wc ); + } + return TRUE; +} + + +Window *Window::CreationWindow=NULL; + +BOOL Window::Create() +{ + if (!Register()) return FALSE; + + HMENU hM=0; + if (Menu) hM=LoadMenu(App::hInstance,Menu); + if (Style & (WS_HSCROLL | WS_VSCROLL)) Flags|=W_SCROLLBARS; + CreationWindow=this; + hWnd=CreateWindow(GetClassName(),Title,Style,x,y,w,h,ParentHWnd, + hM,App::hInstance,(LPSTR) this ); + if (!hWnd) return FALSE; + + if (!SetupWindow()) + { + DestroyWindow(); + return FALSE; + } + else + { + if (StatBar) StatBar->SetupWindow(); + // autocreate?? + // read in x,y,w,h if used setdef ?? + } + return TRUE; +} + +void Window::Destroy() {} + +// could use Window event table for this stuff +BOOL Window::WndProc(TMSG &Msg) +{ + // handle internal actions + switch (Msg.Msg) + { + case WM_QUERYENDSESSION: + case WM_CLOSE: + if (!CanClose()) return TRUE; //returns 0 + break; + case WM_DESTROY: // quit if main window + if (this==App::MainWindow) App::Quit(); + if (Flags & W_SAVEPOS) SaveWindowState(); + Flags|=W_DESTROYED; + Destroy(); + // MainWindow is deleted on program exit + if (this==App::MainWindow) App::Quit(); + else delete this; + return TRUE; + case WM_PAINT: + PAINTSTRUCT ps; + BeginPaint(hWnd, (LPPAINTSTRUCT) &ps); + SetWindowOrgEx(ps.hdc,xPos,yPos,NULL); + Paint(ps.hdc,ps.fErase,ps.rcPaint); + + if (StatBar) // draw statbar after window + { + SetWindowOrgEx(ps.hdc,0,0,NULL); // dont scroll statbar + StatBar->Paint(ps.hdc); + } + EndPaint(hWnd, (LPPAINTSTRUCT) &ps); + break; + + case WM_HSCROLL: HScroll(Msg); break; + case WM_VSCROLL: VScroll(Msg); break; + case WM_SIZE: WMSize(Msg); break; + case WM_GETMINMAXINFO: WMGetMinMaxInfo(Msg); break; + case WM_COMMAND: + { + int id=LOWORD(Msg.wParam); + if (id>MRU_ID && id<=MRU_ID+MRU_MAX) + { + OpenFile(MruList[id-MRU_ID-1]); + return TRUE; + } + else if (id>MRU2_ID && id<=MRU2_ID+MRU_MAX) + { + OpenFile2(MruList2[id-MRU2_ID-1]); + return TRUE; + } + break; + } + case WM_INITMENU: + InitMenu((HMENU) Msg.wParam); // handle of menu to initialize + break; + } + if (StatBar) StatBar->EV_FIND(Msg); + //allow user to grab any message + return EV_FIND(Msg); +} + +BOOL Window::SetupWindow() { return TRUE; } +void Window::OpenFile(char*) {} +void Window::OpenFile2(char*) {} +void Window::InitMenu(HMENU) {} + +void Window::Paint(HDC, BOOL, RECT&) {} + +void Window::EnableMenuItem(int id,BOOL state) { +HMENU hmenu=GetMenu(hWnd); +::EnableMenuItem(hmenu,id,MF_BYCOMMAND | (state) ? MF_ENABLED : MF_GRAYED); +} + +void Window::CheckMenuItem(int id,BOOL state) { +HMENU hmenu=GetMenu(hWnd); +::CheckMenuItem(hmenu,id,MF_BYCOMMAND | (state) ? MF_CHECKED : MF_UNCHECKED); +} + + +void Window::AssignMenu(LPSTR menu) +{ + HMENU OldMenu,hM; + if (HIWORD(Menu)) delete[] Menu; + + if (HIWORD(menu)) Menu = strdup(menu); + else Menu = menu; + + if (hWnd) { + OldMenu=GetMenu(hWnd); + hM=menu ? LoadMenu(App::hInstance,menu) : (HMENU) NULL; + SetMenu(hWnd,hM); + + if (OldMenu) DestroyMenu(OldMenu); + } +} + +void Window::AssignMenu(int MenuId) { +AssignMenu((LPSTR)MAKEINTRESOURCE(MenuId)); +} + +void Window::ShowTitleBar() { +Style=GetWindowLong(hWnd,GWL_STYLE); // need this ? +Style|=WS_CAPTION | OldStyle; +SetWindowLong(hWnd,GWL_STYLE,Style); +SetWindowPos(hWnd,NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_DRAWFRAME); +ShowWindow(hWnd,SW_SHOW); +// allow update of scroll bars ? +} + +void Window::HideTitleBar() { +Style=GetWindowLong(hWnd,GWL_STYLE); // need this ? +OldStyle=Style & ( WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); +Style&=~( (WS_CAPTION & ~WS_BORDER) | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); +SetWindowLong(hWnd,GWL_STYLE,Style); +SetWindowPos(hWnd,NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_DRAWFRAME); +ShowWindow(hWnd,SW_SHOW); +// allow update of scroll bars ? +} + +BOOL Window::WMGetMinMaxInfo(TMSG &Msg) +{ + if (!(Flags & W_SCROLLBARS) || (Flags & W_OVERSCROLL)) return TRUE; + MINMAXINFO *lpmmi = (MINMAXINFO*) Msg.lParam; /* address of structure */ +#ifdef WIN32 + RECT rc={0,0,xMax,yMax}; // client area + AdjustWindowRect(&rc,Style,(BOOL) GetMenu(hWnd)); + rc.right+=GetSystemMetrics(SM_CXVSCROLL); + rc.bottom+=GetSystemMetrics(SM_CYHSCROLL)-rc.top; + + // AdjustWindowRect should do this ??????????????? + int cxf=GetSystemMetrics(SM_CXSIZEFRAME); + int cyf=GetSystemMetrics(SM_CYSIZEFRAME); + rc.right+=2*cxf+1; + rc.bottom+=2*cyf+1; + + RECT Deskrc; + GetWindowRect(GetDesktopWindow(),&Deskrc); + if ( Deskrc.right > rc.right ) lpmmi->ptMaxTrackSize.x=lpmmi->ptMaxSize.x=rc.right; + if ( Deskrc.bottom > rc.bottom ) lpmmi->ptMaxTrackSize.y=lpmmi->ptMaxSize.y=rc.bottom; + lpmmi->ptMaxPosition.x=(Deskrc.right-lpmmi->ptMaxSize.x)/2; + lpmmi->ptMaxPosition.y=(Deskrc.bottom-lpmmi->ptMaxSize.y)/2; + +#else + //as xyMax are long only replace if neccesary, when int > xyMax so ok to cast to (int) + RECT rc; + GetWindowRect(GetDesktopWindow(),&rc); + + int cxf=GetSystemMetrics(SM_CXFRAME); + int cxvs=GetSystemMetrics(SM_CXVSCROLL); + int cyf=GetSystemMetrics(SM_CYFRAME); + int cyhs=GetSystemMetrics(SM_CYHSCROLL); + + int cym= (GetMenu(hWnd)) ? GetSystemMetrics(SM_CYMENU) : 0; + int cyc= (Style&( (WS_CAPTION & ~WS_BORDER) | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU)) + ? GetSystemMetrics(SM_CYCAPTION) : 0; + + if ( rc.right > xMax ) lpmmi->ptMaxSize.x=(int) xMax+2*cxf+cxvs; + if ( rc.bottom > yMax ) lpmmi->ptMaxSize.y=(int) yMax+2*cyf+cyhs+cyc+cym; + lpmmi->ptMaxPosition.x=(rc.right-lpmmi->ptMaxSize.x)/2; + lpmmi->ptMaxPosition.y=(rc.bottom-lpmmi->ptMaxSize.y)/2; + if ( rc.right > xMax ) lpmmi->ptMaxTrackSize.x=(int) xMax+2*cxf+cxvs; + if ( rc.bottom > yMax ) lpmmi->ptMaxTrackSize.y=(int) yMax+2*cyf+cyhs+cyc+cym; +#endif + return TRUE; +} + +BOOL Window::WMSize(TMSG &Msg) +{ + if (!(Flags & W_SCROLLBARS)) return TRUE; + + //int nWidth = LOWORD(Msg.lParam); /* width of client area */ + //int nHeight = HIWORD(Msg.lParam); /* height of client area */ + + switch (Msg.wParam) + { + case SIZE_MAXIMIZED: + case SIZE_RESTORED: + SetVirtualExtent(xMax,yMax,xStep,yStep,0,0); + break; + + case SIZE_MINIMIZED: + case SIZE_MAXHIDE: + case SIZE_MAXSHOW: + break; + } + + return TRUE; +} + + +void Window::SetVirtualExtent(long xSize,long ySize,int xSt, int ySt,int xPgSize,int yPgSize) +{ + // prevent reentrance + static BOOL InFn=FALSE; + if (InFn) return; + InFn=TRUE; + + xMax=xSize; + yMax=ySize; + xStep=xSt; + yStep=ySt; + + RECT rc; + GetClientRect(hWnd,&rc); + if (StatBar) rc.bottom-=StatBar->dyStatbar; + xPageSize=xPgSize ? xPgSize :rc.right; + yPageSize=yPgSize ? yPgSize :rc.bottom; + +#ifdef WIN32 + + SCROLLINFO si; + si.cbSize=sizeof(si); + si.fMask=SIF_ALL; + si.nMin=0; + si.nMax=xMax-1; + si.nPage=xPageSize; + si.nPos=xPos; + SetScrollInfo(hWnd,SB_HORZ,&si,TRUE); + si.nMax=yMax-1; + si.nPage=yPageSize; + si.nPos=yPos; + SetScrollInfo(hWnd,SB_VERT,&si,TRUE); + // scoll bars may have gone + GetClientRect(hWnd,&rc); + if (StatBar) rc.bottom-=StatBar->dyStatbar; + // resive oversized window + if (!(Flags & W_OVERSCROLL) && rc.right>=xMax && rc.bottom>=yMax) + { + int newW= min( (long) rc.right, xMax ); + int newH= min( (long) rc.bottom, yMax ); + RECT rc2; + GetWindowRect(hWnd,&rc2); + SetWindowPos(hWnd,NULL,0,0,newW+rc2.right-rc2.left-rc.right, + newH+rc2.bottom-rc2.top-rc.bottom, + SWP_NOMOVE | SWP_NOZORDER); +// GetClientRect(hWnd,&rc); +// if (StatBar) rc.bottom-=StatBar->dyStatbar; + } + xPageSize=xPgSize ? xPgSize :rc.right; + yPageSize=yPgSize ? yPgSize :rc.bottom; + si.fMask=SIF_PAGE; + si.nPage=xPageSize; + SetScrollInfo(hWnd,SB_HORZ,&si,TRUE); + si.nPage=yPageSize; + SetScrollInfo(hWnd,SB_VERT,&si,TRUE); + si.fMask=SIF_POS; + GetScrollInfo(hWnd,SB_HORZ,&si); + int newxPos=si.nPos; + GetScrollInfo(hWnd,SB_VERT,&si); + int newyPos=si.nPos; + if (newxPos!=xPos || newyPos!=yPos) + { + ScrollWindow(hWnd, xPos-newxPos, yPos-newyPos , NULL, NULL); + xPos=newxPos; + yPos=newyPos; + UpdateWindow(hWnd); + } + +#else + + SetScrollRange(hWnd,SB_HORZ,0, max ( (int) ( xMax - rc.right ), 0 ) , FALSE ); + // may have lost scrollbar, so get size again + GetClientRect(hWnd,&rc); + if (StatBar) rc.bottom-=StatBar->dyStatbar; + xPageSize=xPgSize ? xPgSize :rc.right; + yPageSize=yPgSize ? yPgSize :rc.bottom; + SetScrollRange(hWnd,SB_VERT,0, max ( (int) ( yMax - rc.bottom ), 0 ), FALSE ); + + long newxPos = max ( (long) min( xPos , xMax-rc.right ) , 0L); + long newyPos = max ( (long) min( yPos , yMax-rc.bottom ) , 0L); + if (newxPos!=xPos || newyPos!=yPos) + { + ScrollWindow(hWnd, (int) (xPos-newxPos), (int) (yPos-newyPos) , NULL, NULL); + xPos=newxPos; + yPos=newyPos; + UpdateWindow(hWnd); + } + SetScrollPos(hWnd, SB_HORZ, (int) xPos, TRUE); + SetScrollPos(hWnd, SB_VERT, (int) yPos, TRUE); + + if (!(Flags & W_OVERSCROLL) && rc.right>=xMax && rc.bottom>=yMax) + { + int newW= min( (long) rc.right, xMax ); + int newH= min( (long) rc.bottom, yMax ); + RECT rc2; + GetWindowRect(hWnd,&rc2); + SetWindowPos(hWnd,NULL,0,0,newW+rc2.right-rc2.left-rc.right, + newH+rc2.bottom-rc2.top-rc.bottom, + SWP_NOMOVE | SWP_NOZORDER); + } +#endif + + InFn=FALSE; + Flags |= W_SCROLLBARS; +} + +// WM_VSCROLL +BOOL Window::VScroll(TMSG &Msg) +{ +if (!(Flags & W_SCROLLBARS)) return TRUE; + +int nScrollCode = LOWORD(Msg.wParam); /* scroll bar value */ +int nPos = LLHW(Msg); /* scroll box position */ + +long newPos=yPos; +switch (nScrollCode) { + case SB_LINEUP: + newPos-=yStep; break; + case SB_PAGEUP: + newPos-=yPageSize; break; + case SB_LINEDOWN: + newPos+=yStep; break; + case SB_PAGEDOWN: + newPos+=yPageSize; break; + case SB_THUMBTRACK: + case SB_THUMBPOSITION: + newPos=(long) nPos; break; + case SB_BOTTOM: + newPos=yMax; break; + case SB_TOP: + newPos=0; break; + case SB_ENDSCROLL: + break; + } +#ifdef WIN32 + if (newPos != yPos) + { + if (StatBar) StatBar->Invalidate(); + SCROLLINFO si; + si.cbSize=sizeof(si); + si.fMask=SIF_POS; + si.nPos=newPos; + SetScrollInfo(hWnd,SB_VERT,&si,TRUE); + GetScrollInfo(hWnd,SB_VERT,&si); + + ScrollWindow(hWnd, 0, yPos-si.nPos , NULL, NULL); + yPos=si.nPos; + UpdateWindow(hWnd); + } + +#else + RECT rc; + GetClientRect(hWnd,&rc); + if (StatBar) rc.bottom-=StatBar->dyStatbar; + if ( (newPos = max( min( newPos, yMax-rc.bottom ), 0L)) != yPos) + { + if (StatBar) StatBar->Invalidate(); + ScrollWindow(hWnd, 0, (int) (yPos-newPos) , NULL, NULL); + SetScrollPos(hWnd, SB_VERT, (int) newPos, TRUE); + yPos=newPos; + UpdateWindow(hWnd); + } +#endif + +return TRUE; +} + + +BOOL Window::HScroll(TMSG &Msg) +{ +if (!(Flags & W_SCROLLBARS)) return TRUE; + +int nScrollCode = LOWORD(Msg.wParam); /* scroll bar value */ +int nPos = LLHW(Msg); /* scroll box position */ + +long newPos=xPos; +switch (nScrollCode) { + case SB_LINELEFT: + newPos-=xStep; break; + case SB_PAGELEFT: + newPos-=xPageSize; break; // page?? + case SB_LINERIGHT: + newPos+=xStep; break; + case SB_PAGERIGHT: + newPos+=xPageSize; break; + case SB_THUMBTRACK: + case SB_THUMBPOSITION: + newPos=(long) nPos; break; + case SB_LEFT: + newPos=0; break; + case SB_RIGHT: + newPos=xMax; break; + case SB_ENDSCROLL: + break; + } + +#ifdef WIN32 + if (newPos != xPos) + { + if (StatBar) StatBar->Invalidate(); + SCROLLINFO si; + si.cbSize=sizeof(si); + si.fMask=SIF_POS; + si.nPos=newPos; + SetScrollInfo(hWnd,SB_HORZ,&si,TRUE); + GetScrollInfo(hWnd,SB_HORZ,&si); + + ScrollWindow(hWnd, xPos-si.nPos ,0, NULL, NULL); + xPos=si.nPos; + UpdateWindow(hWnd); + } +#else + + RECT rc; + GetClientRect(hWnd,&rc); + if ( (newPos = max( min( newPos , xMax-rc.right ), 0L)) !=xPos) + { + if (StatBar) StatBar->Invalidate(); + ScrollWindow(hWnd, (int) (xPos-newPos), 0 , NULL, NULL); + SetScrollPos(hWnd, SB_HORZ, (int) newPos, TRUE); + xPos=newPos; + UpdateWindow(hWnd); + } +#endif + +return TRUE; +} + +void Window::SetBkColor(COLORREF Col) +{ + HBRUSH Brush=CreateSolidBrush(Col); + #ifdef WIN32 + HBRUSH OldBrush=(HBRUSH) SetClassLong(hWnd,GCL_HBRBACKGROUND,(LONG) Brush); + #else + HBRUSH OldBrush=SetClassWord(hWnd,GCW_HBRBACKGROUND,Brush); + #endif + DeleteObject(OldBrush); // what if stock objcect ? + InvalidateRect(hWnd,NULL,TRUE); +} + +void Window::SetIcon(int id) +{ + Icon=id; + if (hWnd && Icon>=0) + { + HICON I=LoadIcon(App::hInstance,MAKEINTRESOURCE(id)); + #ifdef WIN32 + ::SendMessage(hWnd,WM_SETICON,ICON_BIG,(LPARAM)I); + #else + HICON OldIcon=(HICON) SetClassWord(hWnd,GCW_HICON, I); + if (OldIcon) DestroyIcon(OldIcon); + #endif + } +} + + +void Window::SaveWindowState() +{ + WINDOWPLACEMENT wp; + wp.length=sizeof(WINDOWPLACEMENT); + GetWindowPlacement(hWnd,&wp); + + if (Flags & W_SAVESTATE) + { + char *temp="Normal"; + BOOL RestToMax=wp.flags & WPF_RESTORETOMAXIMIZED; + if (wp.showCmd==SW_SHOWMAXIMIZED || + ( wp.showCmd==SW_SHOWMINIMIZED && !(Flags & W_SAVEMIN) && RestToMax ) ) temp="Maximized"; + else if ( (Flags & W_SAVEMIN) && (wp.showCmd==SW_SHOWMINIMIZED)) temp= RestToMax ? "MinFromMax" : "Minimized"; + else if ( wp.showCmd==SW_HIDE) temp="Hidden"; + WriteIniString(Name,"State",temp); + } + WriteIniString(Name,"Window", String()<< (int) wp.rcNormalPosition.left << ',' << (int) wp.rcNormalPosition.top << ',' << (int) wp.rcNormalPosition.right << ',' << (int) wp.rcNormalPosition.bottom); +} + +void Window::GetWindowState(int aSaveFlags) +{ + if (Name==NULL) Name=strdup(Title); + Flags|=aSaveFlags | W_SAVEPOS; + + WINDOWPLACEMENT wp; + wp.length=sizeof(WINDOWPLACEMENT); + if (hWnd) GetWindowPlacement(hWnd,&wp); // fill max position + wp.flags=0; + wp.showCmd=SW_SHOWNORMAL; + + String S; + ReadIniString(Name,"State",S); + if (!S.Empty()) + { + if (S=="MinFromMax") + { + Style|=WS_MINIMIZE; + wp.flags=WPF_RESTORETOMAXIMIZED; + wp.showCmd=SW_SHOWMINIMIZED; + } + else if (S=="Maximized") + { + Style|=WS_MAXIMIZE; + wp.showCmd=SW_SHOWMAXIMIZED; + } + else if (S=="Minimized") + { + Style|=WS_MINIMIZE; + wp.showCmd=SW_SHOWMINIMIZED; + } + else if (S=="Hidden") + { + wp.showCmd=SW_HIDE; + } + } + + ReadIniString(Name,"Window",S=""); + if (!S.Empty()) + { + int ww,hh; + sscanf(S,"%d,%d,%d,%d",&x,&y,&ww,&hh); + if (!(Flags & W_NOSIZE)) { w=ww-x; h=hh-y;} + // wp.ptMinPosition + // wp.ptMaxPosition.x=0; + // wp.ptMaxPosition.y=0; + wp.rcNormalPosition.left=x; + wp.rcNormalPosition.top=y; + wp.rcNormalPosition.right=x+w; + wp.rcNormalPosition.bottom=y+h; + } + if (this==App::MainWindow) + { + App::nCmdShow=wp.showCmd; + // minimizing MDI frame now messes up + if (wp.showCmd==SW_SHOWMINIMIZED) + { + wp.showCmd= (wp.flags==WPF_RESTORETOMAXIMIZED) ? SW_SHOWMAXIMIZED : SW_SHOWNORMAL; + } + } + if (hWnd) SetWindowPlacement(hWnd,&wp); +} diff --git a/Win/Classlib/window.h b/Win/Classlib/window.h new file mode 100755 index 0000000..d751587 --- /dev/null +++ b/Win/Classlib/window.h @@ -0,0 +1,68 @@ +// window.h + +#define W_SAVEPOS 1 +#define W_SAVESTATE 2 +#define W_SAVEMIN 4 +#define W_NOSIZE 8 + +#define W_DESTROYED 16 +#define W_SCROLLBARS 32 +#define W_OVERSCROLL 64 + +class Window : public Object +{ +public: + Window(Object *,char *,char *aName=NULL); + virtual ~Window(); +// xpos and ypos are the coords of the client area origin in virtual coords +// and xMax,yMax are the virtual client area extents + static Window* CreationWindow; + long OldStyle; + LPSTR Menu; + int x,y,w,h,Flags,Icon; + DWORD Style; + char *Title,*Name; + virtual BOOL SetupWindow(); + void AssignMenu(LPSTR); + void AssignMenu(int); + void EnableMenuItem(int id,BOOL state); + void CheckMenuItem(int id,BOOL state); + void ShowTitleBar(); + void HideTitleBar(); + + void SetVirtualExtent(long,long,int,int,int,int); + BOOL HScroll(TMSG &); + BOOL VScroll(TMSG &); + BOOL WMSize(TMSG &); + BOOL WMGetMinMaxInfo(TMSG &); + + virtual void Paint(HDC, BOOL, RECT &); + BOOL Create(); + virtual void Destroy(); + virtual void DestroyWindow(); + virtual char *GetClassName(); + BOOL Register(); + virtual BOOL WndProc(TMSG &); + virtual void OpenFile(char*); + virtual void OpenFile2(char*); + virtual void InitMenu(HMENU); + void SetBkColor(COLORREF); + void SetIcon(int); + void GetWindowState(int aSaveFlags=W_SAVESTATE); + StatusBar *StatBar; + + long xPos,yPos,xMax,yMax; + int xStep,yStep,xPageSize,yPageSize; + void SaveWindowState(); +}; + +inline Window *GetPointer( HWND hWnd ) +{ + return (Window *) GetWindowLong( hWnd, 0 ); +} + +inline void SetPointer( HWND hWnd, Window *pWindow ) +{ + SetWindowLong( hWnd, 0, (LONG) pWindow ); +} + diff --git a/Win/Compile.txt b/Win/Compile.txt new file mode 100755 index 0000000..85dd68a --- /dev/null +++ b/Win/Compile.txt @@ -0,0 +1,17 @@ + +Source Code for Windows Version of the Level 9 Interpreter +---------------------------------------------------------- + +Included in this archive is complete source code to rebuild the 32 bit versions of +the Level 9 interpreter, version 3.0. This source has been tested with Microsoft +Visual C++. However, it should be relatively easy to get it to compile with others. + +Note that the 16 bit Windows version is no longer supported. None of the 16 bit +code has been removed however, so it may be possible to get a 16 bit version to +build. Should you suceed in doing so, please get in touch with us. + +To compile, the first stage is to build the class library. For Visual C++, open +the project in the "classlib" directory. For other compilers, all the .cpp files +in "classlib" need to be built into a single library. The interpreter can now be +built by compiling "level9.c", "lev9win.cpp" and linking with the class library. + diff --git a/Win/Lev9win.cpp b/Win/Lev9win.cpp new file mode 100755 index 0000000..560f650 --- /dev/null +++ b/Win/Lev9win.cpp @@ -0,0 +1,1183 @@ +/***********************************************************************\ +* +* Level 9 interpreter +* Version 3.0 +* Copyright (c) 1996 Glen Summers +* Copyright (c) 2002 Glen Summers and David Kinder +* +* Level9 32 bit Windows version by Glen Summers. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. +* +\***********************************************************************/ + +#include +#pragma hdrstop + +#include + +#include "level9.h" + +// define application name, main window title +#define AppName "Level9" +#define MainWinTitle "Level9" + +// help file name and ini file are set from AppName +char HelpFileName[] = AppName".hlp"; + +#ifdef WIN16 +char Ini[] = AppName".ini"; +#else +char Ini[] = "Software\\Level 9\\Interpreter"; +#endif + +#ifdef __BORLANDC__ +#include "level9.rh" +#endif + +#ifdef _MSC_VER +#include "resource.h" +#endif + +String Output=""; +int Line=0; +int LineOffset=0; +int LineStart=0; +int LastWordEnd=0; +HWND hWndMain=0; +HRGN hClip=0; +int GfxMode=0; +int GfxHeight=0; +int GfxPicWidth=0,GfxPicHeight=0; +HBITMAP hGfx=0,hGfxDraw=0; +HDC hGfxDC=0,hGfxDrawDC=0; +int FontHeight=0,LineSpacing=0; +LOGFONT lf; +HFONT Font=0; +COLORREF FontColour; +int PageWidth=0,PageHeight=0,WndHeight=0; +int Margin=0; +SimpleList InputChars; +int iPos=0,Input=0; +String Hash(20); + +void DrawPicture(void); + +void DisplayLine(int Line,char *Str,int Len) +{ + HDC dc=GetDC(hWndMain); + HFONT OldFont=(HFONT) SelectObject(dc,Font); + COLORREF OldCol=SetTextColor(dc,FontColour); + COLORREF OldBk=SetBkColor(dc,GetSysColor(COLOR_WINDOW)); + if(hClip) SelectClipRgn(dc,hClip); + TextOut(dc,Margin,Line*LineSpacing-LineOffset+GfxHeight,Str,Len); + SelectObject(dc,OldFont); + SetTextColor(dc,OldCol); + SetBkColor(dc,OldBk); + ReleaseDC(hWndMain,dc); +} + +void DisplayLineJust(int Line,char *Str,int Len) +{ + HDC dc=GetDC(hWndMain); + HFONT OldFont=(HFONT) SelectObject(dc,Font); + COLORREF OldCol=SetTextColor(dc,FontColour); + COLORREF OldBk=SetBkColor(dc,GetSysColor(COLOR_WINDOW)); + if(hClip) SelectClipRgn(dc,hClip); + + SIZE Size; +#ifdef WIN32 + GetTextExtentPoint32(dc,Str,Len,&Size); +#else + GetTextExtentPoint(dc,Str,Len,&Size); +#endif + + // count spaces + int nBreaks=0; + char *Ptr=Str; + for (int i=0;i 0) + DrawPicture(); + } + +#ifdef WIN32 + Sleep(millis); +#endif +} + +char os_readchar(int millis) +{ + SimpleNode *C; + App::PeekLoop(); + if ((C=InputChars.GetFirst())==NULL) + { + Wait(millis); + App::PeekLoop(); + if ((C=InputChars.GetFirst())==NULL) return 0; + } + char InputChar=(char)C->Item; + delete C; + return InputChar; +} + +L9BOOL os_stoplist(void) +{ + return (os_readchar(0) != 0); +} + +SimpleList History; +int HistoryLines=0; +#define NUMHIST 20 + +void Erase(int Start,int End) +{ + RECT rc; + rc.left=Margin+Start; + rc.right=Margin+End; + rc.top=Line*LineSpacing-LineOffset+GfxHeight; + rc.bottom=Line*LineSpacing+FontHeight-LineOffset+GfxHeight; + HDC dc=GetDC(hWndMain); + if(hClip) SelectClipRgn(dc,hClip); +#ifdef WIN32 + FillRect(dc,&rc,(HBRUSH) GetClassLong(hWndMain,GCL_HBRBACKGROUND)); +#else + FillRect(dc,&rc,(HBRUSH) GetClassLong(hWndMain,GCW_HBRBACKGROUND)); +#endif + ReleaseDC(hWndMain,dc); +} + +void CancelInput() +{ + if (Caret) InputChars.AddTail(-1); +} + +void HashCommand(char *h) +{ + if (Caret) + { + Hash=h; + InputChars.AddTail(-2); + } +} + +BOOL os_input(char*ibuff,int size) +{ + slIterator HistPtr(History); + if (HistPtr()) HistPtr--; // force out + + Input=Output.Len(); + iPos=0; + Caret=TRUE; + MakeCaret(); + SetCaret(Margin+LineLength(Output+LineStart,Input-LineStart),Line*LineSpacing-LineOffset); + + while (TRUE) + { + SimpleNode *C; + while ((C=InputChars.GetFirst())==NULL) + { + Wait(20); + App::PeekLoop(); + } + int InputChar=C->Item; + delete C; + if (InputChar<0) + { + strcpy(ibuff,Hash); + KillCaret(); + Caret=FALSE; + return InputChar<-1; + } + else if (InputChar=='\r') + { + strcpy(ibuff,Output+Input); // >500 chrs?? + KillCaret(); + Caret=FALSE; + HistPtr.Last(); + if (*ibuff && (!HistPtr() || HistPtr.Get()!=ibuff)) + { + History.AddTailRef(String(ibuff)); + if (HistoryLines==NUMHIST) + delete History.GetFirst(); + else HistoryLines++; + } + return TRUE; + } + else switch (InputChar) + { + case 256+VK_DELETE: + if (iPos>=Output.Len()-Input) break; + iPos++; + case 8: + if (iPos>0) + { + int OldLen=LineLength(Output+LineStart,Output.Len()-LineStart); + Output.Remove(Input+--iPos,1); + int Len=LineLength(Output+LineStart,Output.Len()-LineStart); + Erase(Len,OldLen); + } + break; + case 256+VK_UP: + if (!HistPtr()) HistPtr.Last(); + else + { + --HistPtr; + if (!HistPtr()) HistPtr.First(); + } + if (HistPtr()) + { + int OldLen=LineLength(Output+LineStart,Output.Len()-LineStart); + Output.Len(Input); + Output.Insert(HistPtr.Get(),Input); + iPos=Output.Len()-Input; + int Len=LineLength(Output+LineStart,Output.Len()-LineStart); + Erase(Len,OldLen); + } + break; + case 256+VK_DOWN: + if (HistPtr()) + { + ++HistPtr; + if (!HistPtr()) HistPtr.Last(); + else + { + int OldLen=LineLength(Output+LineStart,Output.Len()-LineStart); + Output.Len(Input); + Output.Insert(HistPtr.Get(),Input); + iPos=Output.Len()-Input; + int Len=LineLength(Output+LineStart,Output.Len()-LineStart); + Erase(Len,OldLen); + } + } + break; + case 256+VK_LEFT: + if (iPos>0) iPos--; + break; + case 256+VK_RIGHT: + if (iPosPageWidth-2*Margin) return LastWordEnd+1; + else if (c==0) return Pos-1; + else if (c=='\r') return Pos; + LastWordEnd=Pos-LineStart-1; + } + } +} + +#define SCROLLBACK 2000 + +void NewLine() +{ + while (Output.Len()>SCROLLBACK) + { + int Len=FindLineLength(0); + Output.Remove(0,Len); + LineStart-=Len; + LineOffset-=LineSpacing; + Line--; + } + Line++; + + if (Line*LineSpacing+FontHeight-LineOffset>PageHeight) + { + int Shift=Line*LineSpacing+FontHeight-LineOffset-PageHeight; + RECT rc; + rc.left=0; + rc.top=GfxHeight; + rc.right=PageWidth; + rc.bottom=PageHeight+GfxHeight; + ScrollWindow(hWndMain,0,-Shift,NULL,&rc); + LineOffset+=Shift; + UpdateWindow(hWndMain); + } +} + +void os_flush() +{ + if (LineLength((char*)Output+LineStart,Output.Len()-LineStart)>PageWidth-2*Margin) + { + DisplayLineJust(Line,(char*) Output+LineStart,LastWordEnd); + LineStart+=LastWordEnd+1; + NewLine(); + } + DisplayLine(Line,(char*) Output+LineStart,Output.Len()-LineStart); + LastWordEnd=Output.Len()-LineStart; +} + +void os_printchar(char c) +{ + Output << c; + if (c=='\r' || c==' ') + { + if (LineLength((char*)Output+LineStart,Output.Len()-LineStart-1)>PageWidth-2*Margin) + { + DisplayLineJust(Line,(char*) Output+LineStart,LastWordEnd); + LineStart+=LastWordEnd+1; + NewLine(); + } + if (c=='\r') + { + DisplayLine(Line,(char*) Output+LineStart,Output.Len()-LineStart-1); + LineStart=Output.Len(); + NewLine(); + } + LastWordEnd=Output.Len()-LineStart-1; + } +} + +void Redraw() +{ + int l=0; + int LineStart=0,LastWordEnd=0; + int Pos=0; + char c; + do + { + c= Output[Pos++]; + if (c=='\r' || c==' ' || c==0) + { + if (LineLength((char*)Output+LineStart,Pos-LineStart-1)>PageWidth-2*Margin) + { + DisplayLineJust(l,(char*) Output+LineStart,LastWordEnd); + LineStart+=LastWordEnd+1; + l++; + } + if (c=='\r' || c==0) + { + DisplayLine(l,(char*) Output+LineStart,Pos-LineStart-1); + if (c=='\r') + { + LineStart=Pos; + l++; + } + } + LastWordEnd=Pos-LineStart-1; + } + } while (c); + DrawPicture(); +} + +void Paginate() +{ + int l=0; + int LineStart=0,LastWordEnd=0; + int Pos=0; + char c; + do + { + c= Output[Pos++]; + if (c=='\r' || c==' ' || c==0) + { + if (LineLength((char*)Output+LineStart,Pos-LineStart-1)>PageWidth-2*Margin) + { + LineStart+=LastWordEnd+1; + l++; + } + if (c=='\r') + { + LineStart=Pos; + l++; + } + LastWordEnd=Pos-LineStart-1; + } + } while (c); + Line=l; + LineOffset=max(0,Line*LineSpacing+FontHeight-PageHeight); + + if (Caret) SetCaret(Margin+LineLength(Output+LineStart,Input-LineStart+iPos),Line*LineSpacing-LineOffset); +} + +void Resize() +{ + PageHeight = WndHeight; + if (GfxMode) + { + GfxHeight = WndHeight/2; + PageHeight -= GfxHeight; + } + else + GfxHeight = 0; + + if (hClip) + DeleteObject(hClip); + hClip=CreateRectRgn(0,GfxHeight,PageWidth,WndHeight); + + if (hGfxDraw) + DeleteObject(hGfxDraw); + hGfxDraw = 0; + if (hGfxDrawDC) + DeleteDC(hGfxDrawDC); + hGfxDrawDC = 0; + + Paginate(); + InvalidateRect(hWndMain,NULL,TRUE); +} + +const char Filters[]="Level 9 Game Files (*.dat)\0*.dat\0Spectrum Snapshots (*.sna)\0*.sna\0All Files (*.*)\0*.*\0\0"; +int FiltIndex; +const char GameFilters[]="Saved game file (*.sav)\0*.sav\0All Files (*.*)\0*.*\0\0"; +FName LastGameFile; +int GameFiltIndex; + +void os_set_filenumber(char *NewName,int Size,int n) +{ + FName fn(NewName); + String S; + fn.GetBaseName(S); + while (isdigit(S.Last())) S.Remove(S.Len()-1,1); + fn.NewBaseName(S << n); + strcpy(NewName,fn); +} + +BOOL os_get_game_file(char* Name,int Size) +{ + return CustFileDlg(App::MainWindow,-1,Name,Size,"Open Game File",Filters,&FiltIndex).Execute(); +} + +BOOL os_save_file(BYTE *Ptr,int Bytes) +{ + CancelInput(); + if (CustFileDlg(App::MainWindow,-1,LastGameFile,LastGameFile.Size(),"Save File",GameFilters,&GameFiltIndex,OFN_OVERWRITEPROMPT | OFN_EXPLORER).Execute()) + { + LastGameFile.Update(); + if (!LastGameFile.GetExt()) LastGameFile.NewExt("sav"); + + FILE *f=fopen(LastGameFile,"wb"); + if (f) + { + fwrite(Ptr,1,Bytes,f); + fclose(f); + return TRUE; + } + } + return FALSE; +} + +BOOL os_load_file(BYTE *Ptr,int *Bytes,int Max) +{ + CancelInput(); + if (CustFileDlg(App::MainWindow,-1,LastGameFile,LastGameFile.Size(),"Load File",GameFilters,&GameFiltIndex,OFN_FILEMUSTEXIST | OFN_EXPLORER).Execute()) + { + LastGameFile.Update(); + FILE *f=fopen(LastGameFile,"rb"); + if (f) + { + *Bytes=filelength(f); + if (*Bytes>Max) + MessageBox(App::MainWindow->hWnd,"Not a valid saved game file","Load Error",MB_OK | MB_ICONEXCLAMATION); + else + { + fread(Ptr,1,*Bytes,f); + fclose(f); + return TRUE; + } + } + } + return FALSE; +} + +typedef struct tagBITMAPINFO8 +{ + BITMAPINFOHEADER bmiHeader; + RGBQUAD bmiColors[8]; +} +BITMAPINFO8; + +RGBQUAD Colours[8] = +{ + { 0x00,0x00,0x00,0 }, + { 0x00,0x00,0xFF,0 }, + { 0x30,0xE8,0x30,0 }, + { 0x00,0xFF,0xFF,0 }, + { 0xFF,0x00,0x00,0 }, + { 0x00,0x68,0xA0,0 }, + { 0xFF,0xFF,0x00,0 }, + { 0xFF,0xFF,0xFF,0 } +}; +RGBQUAD Palette[8]; + +COLORREF GetIndexColour(int index) +{ + int x = 0x10*index; + return RGB(x,x,x); +} + +void SetIndexPalette(RGBQUAD* pal) +{ + for (int i = 0; i < 8; i++) + { + pal[i].rgbBlue = 0x10*i; + pal[i].rgbGreen = 0x10*i; + pal[i].rgbRed = 0x10*i; + } +} + +void DrawPicture(void) +{ + if (hGfxDC) + { + if (hGfxDrawDC == 0) + { + hGfxDrawDC = CreateCompatibleDC(hGfxDC); + SetStretchBltMode(hGfxDrawDC,COLORONCOLOR); + + BITMAPINFO8 info; + ZeroMemory(&info,sizeof(BITMAPINFO8)); + info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + info.bmiHeader.biPlanes = 1; + info.bmiHeader.biBitCount = 4; + info.bmiHeader.biCompression = BI_RGB; + info.bmiHeader.biWidth = PageWidth; + info.bmiHeader.biHeight = GfxHeight * -1; + info.bmiHeader.biClrUsed = 8; + info.bmiHeader.biClrImportant = 8; + SetIndexPalette(info.bmiColors); + + VOID* bits; + hGfxDraw = CreateDIBSection(hGfxDrawDC,(BITMAPINFO*)&info,DIB_RGB_COLORS,&bits,NULL,0); + SelectObject(hGfxDrawDC,hGfxDraw); + } + + RGBQUAD pal[8]; + SetIndexPalette(pal); + SetDIBColorTable(hGfxDrawDC,0,8,pal); + StretchBlt(hGfxDrawDC,0,0,PageWidth,GfxHeight, + hGfxDC,0,0,GfxPicWidth,GfxPicHeight,SRCCOPY); + + SetDIBColorTable(hGfxDrawDC,0,8,Palette); + HDC dc = GetDC(hWndMain); + BitBlt(dc,0,0,PageWidth,GfxHeight,hGfxDrawDC,0,0,SRCCOPY); + ReleaseDC(hWndMain,dc); + } +} + +void os_graphics(int mode) +{ + GfxMode = mode ? 1 : 0; + Resize(); + + if (hGfx) + DeleteObject(hGfx); + hGfx = 0; + if (hGfxDC) + DeleteDC(hGfxDC); + hGfxDC = 0; + + if (GfxMode) + { + HDC dc = GetDC(hWndMain); + hGfxDC = CreateCompatibleDC(dc); + SetStretchBltMode(hGfxDC,COLORONCOLOR); + ReleaseDC(hWndMain,dc); + + GetPictureSize(&GfxPicWidth,&GfxPicHeight); + + BITMAPINFO8 info; + ZeroMemory(&info,sizeof(BITMAPINFO8)); + info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + info.bmiHeader.biPlanes = 1; + info.bmiHeader.biBitCount = 4; + info.bmiHeader.biCompression = BI_RGB; + info.bmiHeader.biWidth = GfxPicWidth; + info.bmiHeader.biHeight = GfxPicHeight * -1; + info.bmiHeader.biClrUsed = 8; + info.bmiHeader.biClrImportant = 8; + SetIndexPalette(info.bmiColors); + + VOID* bits; + hGfx = CreateDIBSection(hGfxDC,(BITMAPINFO*)&info,DIB_RGB_COLORS,&bits,NULL,0); + SelectObject(hGfxDC,hGfx); + } +} + +void os_cleargraphics(void) +{ + if (hGfxDC) + { + RECT rc; + rc.left = 0; + rc.right = GfxPicWidth; + rc.top = 0; + rc.bottom = GfxPicHeight; + + HBRUSH br = CreateSolidBrush(GetIndexColour(0)); + HGDIOBJ old = SelectObject(hGfxDC,br); + FillRect(hGfxDC,&rc,br); + SelectObject(hGfxDC,old); + DeleteObject(br); + } +} + +void os_setcolour(int colour, int index) +{ + Palette[colour] = Colours[index]; +} + +COLORREF LineColour1 = 0; +COLORREF LineColour2 = 0; + +VOID CALLBACK LineProc(int x, int y, LPARAM) +{ + if (GetPixel(hGfxDC,x,y) == LineColour2) + SetPixel(hGfxDC,x,y,LineColour1); +} + +void os_drawline(int x1, int y1, int x2, int y2, int colour1, int colour2) +{ + if (hGfxDC) + { + LineColour1 = GetIndexColour(colour1); + LineColour2 = GetIndexColour(colour2); + LineDDA(x1,y1,x2,y2,LineProc,NULL); + LineProc(x2,y2,NULL); + } +} + +void os_fill(int x, int y, int colour1, int colour2) +{ + if (hGfxDC) + { + COLORREF colour = GetIndexColour(colour2); + if (GetPixel(hGfxDC,x,y) == colour) + { + HBRUSH br = CreateSolidBrush(GetIndexColour(colour1)); + HGDIOBJ old = SelectObject(hGfxDC,br); + ExtFloodFill(hGfxDC,x,y,colour,FLOODFILLSURFACE); + SelectObject(hGfxDC,old); + DeleteObject(br); + } + } +} + +// About Dialog *************************************** + +class AboutDialog : public Dialog +{ +public: + AboutDialog(Object *Parent) : Dialog(Parent,IDD_ABOUT,"AboutDialog") {} +}; + +// MainWindow ***************************************** + +class MainWindow : public HashWindow +{ +public: + MainWindow(Object *Parent,char *Title); + ~MainWindow(); + static FName LastFile; + + BOOL HelpState; + BOOL Playing; + + void Destroy(); + BOOL SetupWindow(); + void OpenFile(char *name); + + void CmHelpContents(); + void CmAbout(); + void CmExit(); + void CmOpen(); + void CmSelectFont(); + void CmRestore() { HashCommand("#restore"); } + void CmSave() { HashCommand("save"); } + void CmDictionary() { HashCommand("#dictionary"); } + + void SetFont(); + void DelFonts(); + +// message response functions + + BOOL LButtonDown(TMSG &); + BOOL RButtonDown(TMSG &); + BOOL LButtonUp(TMSG &); + BOOL WMMouseMove(TMSG &); + BOOL WMSize(TMSG &); + + BOOL WMKeyDown(TMSG &); + BOOL WMChar(TMSG &); + BOOL WMSetFocus(TMSG&); + BOOL WMKillFocus(TMSG&); + +// window paint request + void Paint(HDC, BOOL, RECT&); + +// enable message response + HASH_EV_ENABLE(MainWindow) +}; + +// define response functions +EV_START(MainWindow) +// command messages + EV_COMMAND(CM_ABOUT, CmAbout) + EV_COMMAND(CM_HELPCONTENTS, CmHelpContents) + EV_COMMAND(CM_EXIT, CmExit) + EV_COMMAND(CM_OPEN, CmOpen) + EV_COMMAND(CM_FONT, CmSelectFont) + EV_COMMAND(CM_FILELOAD, CmRestore) + EV_COMMAND(CM_FILESAVE, CmSave) + EV_COMMAND(CM_DICTIONARY, CmDictionary) + +// windows messages + EV_MESSAGE(WM_LBUTTONDOWN, LButtonDown) + EV_MESSAGE(WM_RBUTTONDOWN, RButtonDown) + EV_MESSAGE(WM_MOUSEMOVE, WMMouseMove) + EV_MESSAGE(WM_LBUTTONUP, LButtonUp) + EV_MESSAGE(WM_KEYDOWN, WMKeyDown) + EV_MESSAGE(WM_CHAR, WMChar) + EV_MESSAGE(WM_SIZE, WMSize) + EV_MESSAGE(WM_SETFOCUS,WMSetFocus) + EV_MESSAGE(WM_KILLFOCUS,WMKillFocus) + +EV_END + +// main window constructor +MainWindow::MainWindow(Object *Parent,char *Title) : HashWindow(Parent,Title,"MainWindow") +{ + // set window style + Style=WS_OVERLAPPEDWINDOW; + +#ifdef __BORLANDC__ + // give it a menu and icon + AssignMenu(IDM_MENU); + SetIcon(IDI_ICON); +#endif + + // set help flag + HelpState = FALSE; + Flags|=W_OVERSCROLL; +} + +// this is called to setup the window +BOOL MainWindow::SetupWindow() +{ +#ifdef WIN32 + SetWindowLong(hWnd,GWL_EXSTYLE,WS_EX_OVERLAPPEDWINDOW); +#endif + +#ifdef _MSC_VER + AssignMenu(IDM_MENU); + SetIcon(IDI_ICON); +#endif + + // load window pos from ini file (will also be automatically saved on exit) + GetWindowState(); + SetMru(hWnd,CM_EXIT); + hWndMain=hWnd; + SetFont(); + Playing=FALSE; + + return TRUE; +} + +void MainWindow::CmSelectFont() +{ + CHOOSEFONT cf; + + cf.lStructSize=sizeof(cf); + cf.hwndOwner=hWnd; + cf.lpLogFont=&lf; + cf.rgbColors=FontColour; + + cf.Flags=CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS | CF_EFFECTS; + + if (ChooseFont(&cf)) + { + DelFonts(); + SetFont(); + FontColour=cf.rgbColors; + KillCaret(); + Paginate(); + InvalidateRect(hWnd,NULL,TRUE); + MakeCaret(); + } +} + +void MainWindow::SetFont() +{ + Font=CreateFontIndirect(&lf); + HDC dc=GetDC(hWnd); + HFONT OldFont=(HFONT) SelectObject(dc,Font); + TEXTMETRIC tm; + GetTextMetrics(dc,&tm); + FontHeight=tm.tmHeight; + Margin=tm.tmAveCharWidth; + LineSpacing=(int) 1.1*FontHeight; + SelectObject(dc,OldFont); + ReleaseDC(hWnd,dc); +} + +void MainWindow::DelFonts() +{ + DeleteObject(Font); +} + +// this is called when the window is destroyed +void MainWindow::Destroy() +{ + // close help if open + if (HelpState) WinHelp(hWnd,HelpFileName, HELP_QUIT, 0L); + DelFonts(); + StopGame(); + Playing=FALSE; + FreeMemory(); + CancelInput(); +} + +MainWindow::~MainWindow() +{ +} + +FName MainWindow::LastFile; + +void MainWindow::OpenFile(char *name) +{ + // in input routine?, cause fall through + CancelInput(); + // clear buffers etc..., even if fails as invalidates game memory + Output=""; + LineStart=LastWordEnd=0; + Paginate(); + InvalidateRect(hWnd,NULL,TRUE); + + // look for a picture datafile + FName picname(name); + picname.NewExt("pic"); + if (!picname.Exist()) picname.NewExt("cga"); + if (!picname.Exist()) picname.NewExt("hrc"); + if (!picname.Exist()) picname.NewName("picture.dat"); + + if (!LoadGame(name,picname)) MessageBox(hWnd,"Unable to load game file","Load Error",MB_OK | MB_ICONEXCLAMATION); + else + { + LastFile=name; + AddToMru(LastFile); + + if (Playing) return; + Playing=TRUE; + while (Playing && RunGame()) App::PeekLoop(); + Playing=FALSE; + } +} + +void MainWindow::CmOpen() +{ + FName fn=LastFile; + if (os_get_game_file(fn,fn.Size())) + OpenFile(fn); +} + +void MainWindow::CmExit() +{ + DestroyWindow(); +} + +void MainWindow::CmHelpContents() +{ + FName HelpFile; + GetModuleFileName(App::hInstance,HelpFile,HelpFile.Size()); + HelpFile.Update(); + HelpFile.NewName(HelpFileName); + HelpState = WinHelp(hWnd, HelpFile, HELP_CONTENTS, 0L); +} + +void MainWindow::Paint(HDC, BOOL, RECT&) +{ + Redraw(); +} + +void MainWindow::CmAbout() +{ + AboutDialog(this).Execute(); +} + +BOOL MainWindow::WMKeyDown(TMSG &Msg) +{ + switch ((int) Msg.wParam) // the virtual key code + { + case VK_F1: + CmHelpContents(); + break; + case VK_F2: CmOpen(); break; + case VK_F3: CmRestore(); break; + case VK_F4: CmSave(); break; + case VK_F5: CmDictionary(); break; + + case VK_LEFT: + case VK_RIGHT: + case VK_UP: + case VK_DOWN: + case VK_DELETE: + case VK_END: + case VK_HOME: + InputChars.AddTail(256+Msg.wParam); + break; + } + return TRUE; // message handled +} + +BOOL MainWindow::WMChar(TMSG &Msg) +{ + InputChars.AddTail(Msg.wParam); + return TRUE; +} + +BOOL MainWindow::LButtonDown(TMSG &) +{ + return TRUE; +} + +BOOL MainWindow::WMMouseMove(TMSG &) +{ + return TRUE; +} + +BOOL MainWindow::LButtonUp(TMSG &) +{ + return TRUE; +} + +BOOL MainWindow::RButtonDown(TMSG &) +{ + return TRUE; +} + +BOOL MainWindow::WMSize(TMSG &Msg) +{ + if (Msg.wParam != SIZE_MINIMIZED) + { + PageWidth = LOWORD(Msg.lParam); + WndHeight = HIWORD(Msg.lParam); + Resize(); + } + return TRUE; +} + +BOOL MainWindow::WMSetFocus(TMSG&) +{ + MakeCaret(); + return TRUE; +} + +BOOL MainWindow::WMKillFocus(TMSG&) +{ + KillCaret(); + return TRUE; +} + +// App ***************************************** + +class MyApp : public App +{ +public: + MyApp(char *Name); + ~MyApp(); + +private: + void InitMainWindow(); + void SetDefs(); + void ReadIni(); + void WriteIni(); + void FirstIn(); +}; + +void MyApp::SetDefs() +{ + // Set application default settings here + MainWindow::LastFile=""; + FiltIndex=0; + LastGameFile=""; + GameFiltIndex=0; + + memset(&lf,0,sizeof(lf)); + lf.lfHeight=-16; + lf.lfWeight=FW_NORMAL; + lf.lfCharSet=ANSI_CHARSET; + lf.lfOutPrecision=OUT_TT_PRECIS; + lf.lfClipPrecision=CLIP_DEFAULT_PRECIS; + lf.lfQuality=PROOF_QUALITY; + lf.lfPitchAndFamily=4; + strcpy(lf.lfFaceName,"Times New Roman"); + + FontColour=RGB(0,0,0); +} + +void MyApp::ReadIni() +{ + // read information from ini file + SetDefs(); + ReadIniString("General","LastFile",(String&)MainWindow::LastFile); + ReadIniInt("General","FiltIndex",FiltIndex); + ReadIniString("General","LastGameFile",(String&)LastGameFile); + ReadIniInt("General","GameFiltIndex",GameFiltIndex); + + ReadIniInt("Font","Size",lf.lfHeight); + String S(LF_FACESIZE); + ReadIniString("Font","Name",S); + if (*S) strcpy(lf.lfFaceName,S); + ReadIniInt("Font","Colour",(long&) FontColour); +} + +void MyApp::WriteIni() +{ + // write information to ini file + WriteIniString("General","LastFile",MainWindow::LastFile); + WriteIniInt("General","FiltIndex",FiltIndex); + WriteIniString("General","LastGameFile",LastGameFile); + WriteIniInt("General","GameFiltIndex",GameFiltIndex); + + long FontHeight=lf.lfHeight; + WriteIniInt("Font","Size",FontHeight); + WriteIniString("Font","Name",lf.lfFaceName); + WriteIniInt("Font","Colour",(long) FontColour); +} + +MyApp::MyApp(char *Name) : App(Name,Ini) +{ + // enable 3d dialog boxes +#ifdef WIN16 + EnableCtl3d(); +#else + OSVERSIONINFO ovi; + ovi.dwOSVersionInfoSize=sizeof(ovi); + GetVersionEx(&ovi); + if (ovi.dwMajorVersion == 3) EnableCtl3d(); +#endif + + // read from ini file + ReadMru(4); + ReadIni(); +}; + +void MyApp::InitMainWindow() +{ + MainWindow=new ::MainWindow(0,MainWinTitle); +} + +MyApp::~MyApp() +{ + // write information to ini file + WriteMru(); + WriteIni(); +} + +void MyApp::FirstIn() +{ + ((::MainWindow*) MainWindow)->CmOpen(); +} + +// main function, called from WinMain() +int Main() +{ + // create and run application + return MyApp(AppName).Run(); +} + diff --git a/Win/Level9.hpj b/Win/Level9.hpj new file mode 100755 index 0000000..5323395 --- /dev/null +++ b/Win/Level9.hpj @@ -0,0 +1,15 @@ +; This file is maintained by HCW. Do not modify this file directly. + +[OPTIONS] +COMPRESS=12 Hall Zeck +LCID=0x809 0x0 0x0 ; English (British) +REPORT=Yes +CONTENTS=contents +TITLE=Level 9 Interpreter +HLP=Level9.hlp + +[FILES] +level9.rtf + +[WINDOWS] +Main="",(440,23,570,935),20736,(r14876671),(r12632256),f3 diff --git a/Win/Level9.ico b/Win/Level9.ico new file mode 100755 index 0000000000000000000000000000000000000000..633dd4bcd136f017af565468b9a041e9747a1130 GIT binary patch literal 766 zcmdUry%B&Q427Sbjh&S>rDHf6Bha!9MDnmAGLJ z+p$3t9#DIRmr!|M1D;eVC88B_QRQqIhS-z2Ud*u}XpTX+UQSa*>+k2+$T~mhJueI0 T`^5M)zi(QO-1EP7+rNYp!_Jy> literal 0 HcmV?d00001 diff --git a/Win/Level9.manifest b/Win/Level9.manifest new file mode 100755 index 0000000..5959ff2 --- /dev/null +++ b/Win/Level9.manifest @@ -0,0 +1,20 @@ + + + + Level 9 adventure interpreter + + + + + + diff --git a/Win/Level9.rc b/Win/Level9.rc new file mode 100755 index 0000000..9adba65 --- /dev/null +++ b/Win/Level9.rc @@ -0,0 +1,149 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.K.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_ABOUT DIALOGEX 47, 43, 214, 94 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | + WS_SYSMENU +CAPTION "About" +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK",1,83,76,48,13 + LTEXT "Level 9 Interpreter v3.0",-1,40,9,77,8 + LTEXT "Copyright (c) 1996 Glen Summers",-1,40,19,112,8 + ICON IDI_ICON,-1,5,7,21,20,SS_REALSIZEIMAGE | WS_BORDER, + WS_EX_CLIENTEDGE + GROUPBOX "",-1,34,0,175,71 + LTEXT "Copyright (c) 2002 Glen Summers and David Kinder",-1,40, + 29,165,8 + LTEXT "Level9 is released under the terms of the GNU General Public License. See the file COPYING that is included with this program for details.", + -1,40,41,161,26 +END + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON ICON "level9.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDM_MENU MENU +BEGIN + POPUP "&File" + BEGIN + MENUITEM "&Load Level 9 Game File...\tF2", CM_OPEN + MENUITEM SEPARATOR + MENUITEM "&Restore Saved Position...\tF3", CM_FILELOAD + MENUITEM "&Save Current Position...\tF4", CM_FILESAVE + MENUITEM "Game &Dictionary\tF5", CM_DICTIONARY + MENUITEM SEPARATOR + MENUITEM "E&xit\tAlt+F4", CM_EXIT + END + POPUP "&Options" + BEGIN + MENUITEM "&Select Font...", CM_FONT + END + POPUP "&Help" + BEGIN + MENUITEM "Contents...\tF1", CM_HELPCONTENTS + MENUITEM SEPARATOR + MENUITEM "&About...", CM_ABOUT + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// RT_MANIFEST +// + +IDR_MANIFEST RT_MANIFEST "Level9.manifest" + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_ABOUT, DIALOG + BEGIN + BOTTOMMARGIN, 89 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.K.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/Win/Level9.rtf b/Win/Level9.rtf new file mode 100755 index 0000000..388383a --- /dev/null +++ b/Win/Level9.rtf @@ -0,0 +1,196 @@ +{\rtf1\ansi\ansicpg1252\uc1 \deff1\deflang1033\deflangfe1033{\fonttbl{\f0\froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f1\fswiss\fcharset0\fprq2{\*\panose 020b0604020202020204}Arial;} +{\f2\fmodern\fcharset0\fprq1{\*\panose 02070309020205020404}Courier New;}{\f16\froman\fcharset238\fprq2 Times New Roman CE;}{\f17\froman\fcharset204\fprq2 Times New Roman Cyr;}{\f19\froman\fcharset161\fprq2 Times New Roman Greek;} +{\f20\froman\fcharset162\fprq2 Times New Roman Tur;}{\f21\froman\fcharset186\fprq2 Times New Roman Baltic;}{\f22\fswiss\fcharset238\fprq2 Arial CE;}{\f23\fswiss\fcharset204\fprq2 Arial Cyr;}{\f25\fswiss\fcharset161\fprq2 Arial Greek;} +{\f26\fswiss\fcharset162\fprq2 Arial Tur;}{\f27\fswiss\fcharset186\fprq2 Arial Baltic;}{\f28\fmodern\fcharset238\fprq1 Courier New CE;}{\f29\fmodern\fcharset204\fprq1 Courier New Cyr;}{\f31\fmodern\fcharset161\fprq1 Courier New Greek;} +{\f32\fmodern\fcharset162\fprq1 Courier New Tur;}{\f33\fmodern\fcharset186\fprq1 Courier New Baltic;}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0; +\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}{\stylesheet{ +\widctlpar\adjustright \f1\fs18\lang2057 \snext0 Normal;}{\s1\sb60\sa60\keepn\widctlpar\outlinelevel0\adjustright \b\f1\fs28\lang2057\kerning28 \sbasedon0 \snext0 heading 1;}{\s2\sb240\sa60\keepn\widctlpar\adjustright \b\i\f1\fs18\lang2057 +\sbasedon0 \snext0 heading 2;}{\s3\sb240\sa60\keepn\widctlpar\adjustright \f1\fs18\lang2057 \sbasedon0 \snext0 heading 3;}{\*\cs10 \additive Default Paragraph Font;}{\s15\widctlpar\adjustright \f1\fs18\lang2057 \sbasedon0 \snext15 footnote text;}{\*\cs16 +\additive \super \sbasedon10 footnote reference;}{\s17\fi-567\li709\ri509\widctlpar\tx709\adjustright \f1\fs18\lang2057 \sbasedon0 \snext17 MyList;}{\s18\li1134\ri1785\widctlpar\tx3686\adjustright \f1\fs18\lang2057 \sbasedon0 \snext18 MyGames;}{ +\s19\li851\widctlpar\tx3119\adjustright \f1\fs18\lang2057 \sbasedon0 \snext19 Problem;}{\*\cs20 \additive \ul\cf2 \sbasedon10 Hyperlink;}{\s21\widctlpar\adjustright \f2\fs20\lang2057\cgrid \sbasedon0 \snext21 Plain Text;}}{\info{\author Smeg Head} +{\operator David Kinder}{\creatim\yr1996\mo7\dy25\hr12\min57}{\revtim\yr2002\mo5\dy4\hr21\min27}{\version57}{\edmins25}{\nofpages8}{\nofwords1299}{\nofchars7405}{\*\company Jupiter Mining Corporation}{\nofcharsws0}{\vern89}}\paperw11906\paperh16838 +\widowctrl\ftnbj\aenddoc\hyphcaps0\formshade\viewkind1\viewscale100 \fet0\sectd \linex0\headery709\footery709\colsx709\endnhere\sectdefaultcl {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang +{\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang{\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang +{\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}\pard\plain +\s1\sb60\sa60\keepn\widctlpar\outlinelevel0\adjustright \b\f1\fs28\lang2057\kerning28 {\cs16\super ${\footnote \pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super $}{ History}}}{ }{\cs16\super #{\footnote \pard\plain +\s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super #}{ History}}}{ }{\cs16\super K{\footnote \pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super K}{ History}}}{ History +\par }\pard\plain \widctlpar\adjustright \f1\fs18\lang2057 { +\par }\pard\plain \s17\fi-567\li709\ri509\widctlpar\tx709\adjustright \f1\fs18\lang2057 {\b v3.0}{\tab Implemented support for line-drawn graphics. +\par \tab Fixed a problem with v4 detection that stopped some versions of Scapeghost running correctly. +\par \tab Implemented an opcode used by the disk based versions of Time and Magick, which now means that saving from these games works. +\par \tab The interpreter now gives you a reasonable amount of time to enter the Lenslok code in protected games, and also tells you what the code should be. +\par }{\b +\par v2.0}{\tab Revised the description of v4 games to include all the post-1987 games, which are now supported. +\par \tab v2 games are also now supported. +\par \tab Added meta commands. +\par +\par }{\b v1.0}{\tab First release. +\par }\pard\plain \widctlpar\adjustright \f1\fs18\lang2057 { +\par }\pard\plain \s1\sb60\sa60\keepn\widctlpar\outlinelevel0\adjustright \b\f1\fs28\lang2057\kerning28 {\page }{\cs16\super ${\footnote \pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super $}{ The Interactive Fiction Archive}}}{ }{ +\cs16\super #{\footnote \pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super #}{ IFArchive}}}{ }{\cs16\super K{\footnote \pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super K}{ The Interactive Fiction Archive}}}{ + The Interactive Fiction Archive +\par }\pard\plain \widctlpar\adjustright \f1\fs18\lang2057 { +\par If you have access to the Internet and are interested in text adventures, then you can find all sorts of programs and information at The Interactive Fiction Archive, at the ftp site +\par +\par }{\f2 \tab }{\field\fldedit{\*\fldinst {\f2 HYPERLINK ftp://ftp.gmd.de/if-archive/ }{\f2\fs20 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b02000000170000001d0000006600740070003a002f002f006600740070002e0067006d0064002e00640065002f00690066002d0061007200630068006900760065002f000000e0c9ea79f9bace118c8200aa004ba90b3a0000006600740070003a002f002f006600740070002e00 +67006d0064002e00640065002f00690066002d0061007200630068006900760065002f0000000064000000540100000100000400000000000000001e00000000000000010000020000}}}{\fldrslt {\cs20\f2\ul\cf2 ftp://ftp.ifarchive.org/if-archive/}}}{\f2 +\par }{ +\par The latest version of this program can always be found here. +\par +\par }\pard\plain \s1\sb60\sa60\keepn\widctlpar\outlinelevel0\adjustright \b\f1\fs28\lang2057\kerning28 {\page }{\cs16\super ${\footnote \pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super $}{ Credits}}}{ }{\cs16\super #{\footnote +\pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super #}{ Credits}}}{ }{\cs16\super K{\footnote \pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super K}{ Credits}}}{ Credits +\par }\pard\plain \widctlpar\adjustright \f1\fs18\lang2057 { +\par The Level9 interpreter was originally written by Glen Summers. The changes for 3.0 were made by David Kinder (d.kinder@btinternet.com) with additional code from Alan Staniforth and Simon Baldwin +. Help, testing and information on the various Level 9 formats was provided by Paul David Doherty. +\par +\par }\pard\plain \s1\sb60\sa60\keepn\widctlpar\outlinelevel0\adjustright \b\f1\fs28\lang2057\kerning28 {\page }{\cs16\super ${\footnote \pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super $}{ Introduction}}}{ }{\cs16\super #{\footnote +\pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super #}{ Introduction}}}{ }{\cs16\super K{\footnote \pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super K}{ Introduction}}}{ Introduction +\par }\pard\plain \widctlpar\adjustright \f1\fs18\lang2057 { +\par During the 1980s a small British company called Level 9, run by two brothers (Mike and Pete Austin), produced a series of text adventure games for a variety of computers. + These games received considerable critical acclaim and are probably the best text adventures written for the small cassette based 8 bit computers common in Britain in the 80s. +\par +\par Level 9 wrote their games using a custom designed system known as "A-Code", wh +ich evolved from games on the 8 bit computers such as the Acorn BBC Model B, the Sinclair Spectrum and the Commodore 64 to the (then new) 16 bit machines such as the Amiga and the Atari ST. +\par +\par From disassembly of Level 9 games there are thought to be four va +riants of A-Code, which are detailed below. Thanks go to Paul David Doherty for analysing the games and producing the table which follows. At present this interpreter supports v2, v3 and v4 games. +\par +\par }\pard\plain \s17\fi-567\li709\ri509\widctlpar\tx709\adjustright \f1\fs18\lang2057 {\b v1}{\tab This was used for the earliest games. Spectrum v1 games had black text on a grey background. Games known to be released in this format: +\par }\pard\plain \widctlpar\adjustright \f1\fs18\lang2057 { +\par }\pard\plain \s18\li1134\ri1785\widctlpar\tx3686\adjustright \f1\fs18\lang2057 {Colossal Adventure +\par Adventure Quest +\par Dungeon Adventure +\par Snowball +\par Lords of Time +\par }\pard\plain \widctlpar\adjustright \f1\fs18\lang2057 { +\par }\pard\plain \s17\fi-567\li709\ri509\widctlpar\tx709\adjustright \f1\fs18\lang2057 {\b v2}{\tab \tab These releases were made between 1984 and 1985 (and usually say so in the initial copyright m +essage). This version introduced the yellow text on a black background which became standard. Games in this format were: +\par }\pard\plain \widctlpar\adjustright \f1\fs18\lang2057 { +\par }\pard\plain \s18\li1134\ri1785\widctlpar\tx3686\adjustright \f1\fs18\lang2057 {Adventure Quest +\par Dungeon Adventure +\par Lords of Time +\par Snowball +\par Return to Eden +\par Erik the Viking +\par Emerald Isle +\par Red Moon\tab \tab +\par }\pard\plain \widctlpar\adjustright \f1\fs18\lang2057 { +\par }\pard\plain \s17\fi-567\li709\ri509\widctlpar\tx709\adjustright \f1\fs18\lang2057 {\b v3}{\tab This format, dated 1986, was used by the largest number of releases. These were: +\par }\pard\plain \ri1076\widctlpar\adjustright \f1\fs18\lang2057 { +\par }\pard\plain \s18\li1134\ri935\widctlpar\tx3686\adjustright \f1\fs18\lang2057 {Worm in Paradise +\par The Price of Magick +\par The Secret Diary of Adrian Mole +\par The Growing Pains of Adrian Mole +\par The Archers +\par The Jewels of Darkness Trilogy (Colossal Adventure, Adventure Quest and Dungeon Adventure released as one package) +\par The Silicon Dreams Trilogy (Snowball, Return to Eden and Worm in Paradise) +\par }\pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 { +\par }\pard\plain \s17\fi-567\li709\ri509\widctlpar\tx709\adjustright \f1\fs18\lang2057 {\b v4}{\tab This was used for the Time and Magick Trilogy (Lords of Time, Red Moon and The Price of Magick), and all Level 9's new games from 1987 onwards: +\par +\par }\pard\plain \s18\li1134\ri1785\widctlpar\tx3686\adjustright \f1\fs18\lang2057 {Knight Orc +\par Gnome Ranger +\par Ingrid's Back +\par Lancelot +\par Scapeghost +\par }\pard\plain \s17\fi-567\li709\ri509\widctlpar\tx709\adjustright \f1\fs18\lang2057 { +\par }\pard\plain \s1\sb60\sa60\keepn\widctlpar\outlinelevel0\adjustright \b\f1\fs28\lang2057\kerning28 {\page }{\cs16\super ${\footnote \pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super $}{ Level 9 Interpreter}}}{ }{\cs16\super # +{\footnote \pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super #}{ Contents}}}{ }{\cs16\super K{\footnote \pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super K}{ Contents}}}{ Level 9 Interpreter +\par }\pard\plain \widctlpar\adjustright \f1\fs18\lang2057 { +\par Level 9 Interpreter v3.0 +\par An interpreter for Level 9 games in any format, including Spectrum snapshots. +\par +\par Copyright (c) 1996 Glen Summers +\par Copyright (c) 2002 Glen Summers and David Kinder +\par +\par Level9 is released under the terms of the GNU General Public License. See the file COPYING that is included with this program for details. +\par +\par }\pard \li567\widctlpar\adjustright {\fs22\uldb Introduction}{\v\fs22 Introduction}{\fs22 +\par }{\fs22\uldb Supported Formats}{\v\fs22 SupportedFormats}{\fs22 +\par }{\fs22\uldb Graphics}{\v\fs22 Graphics}{\fs22 +\par }{\fs22\uldb Meta Commands and Menus}{\v\fs22 Menus}{\fs22 +\par }{\fs22\uldb History}{\v\fs22 History}{\fs22\uldb +\par Credits}{\v\fs22 Credits}{\fs22 +\par }{\fs22\uldb The Interactive Fiction Archive}{\v\fs22 IFArchive}{\fs22 +\par }\pard \widctlpar\adjustright { +\par }\pard\plain \s1\sb60\sa60\keepn\widctlpar\outlinelevel0\adjustright \b\f1\fs28\lang2057\kerning28 {\page }{\cs16\super ${\footnote \pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super $}{ Meta Commands and Menus}}}{ }{\cs16\super # +{\footnote \pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super #}{ Menus}}}{ }{\cs16\super K{\footnote \pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super K}{ Meta Commands and Menus}}}{ Meta Commands and Menus +\par }\pard\plain \widctlpar\adjustright \f1\fs18\lang2057 { +\par Level9 supports several meta commands, which can be entered on the input line. These commands are handled by the interpreter rather than being passed to the game. They are: +\par +\par }\pard\plain \s17\fi-1418\li1418\ri509\widctlpar\tx-2268\tx1418\adjustright \f1\fs18\lang2057 {#save\tab Saves out a game position directly, bypassing any prompting (such as for disk changes). +\par }\pard \s17\fi-1418\li1418\ri509\widctlpar\tx1418\adjustright { +\par #restore\tab Loads in a saved position directly, bypassing any protection code within the game. +\par }\pard \s17\fi-1418\li1418\ri509\widctlpar\tx-2127\tx1418\adjustright { +\par #quit\tab \tab Quits the current game. +\par +\par #cheat\tab Tries to bypass the copy protection code which asks for a specific word. This is done by trying every word in the game's dictionary. On a slow machine, this can take a long time. +\par +\par #dictionary\tab Lists the game dictionary. Press a key to stop the listing and return to the input line. Note that the v2 dictionary appears to have random characters following on the end: The original in +terpreter code to detect the end of dictionary does not appear to agree with the characters actually at the end. +\par +\par }\pard \s17\fi-1418\li1418\ri509\widctlpar\tx-2410\tx-2127\tx1418\adjustright {#picture\tab If graphics are available, shows the picture specified as a number after the picture command, e.g. "#picture 520". +\par }\pard\plain \widctlpar\adjustright \f1\fs18\lang2057 { +\par The following menus are also available: +\par +\par Load Level 9 Game File... +\par Starts playing the specified Level 9 game +\par +\par Restore Saved Position... +\par Restores a previously saved position, the same as entering #restore. +\par +\par Save Current Position... +\par Saves your current game position, the same as entering save. +\par +\par Select Font\'85 +\par Selects the font used in the game. +\par +\par }\pard\plain \s1\sb60\sa60\keepn\widctlpar\outlinelevel0\adjustright \b\f1\fs28\lang2057\kerning28 {\page }{\cs16\super ${\footnote \pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super $}{ Supported Formats}}}{ }{\cs16\super #{\footnote +\pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super #}{ SupportedFormats}}}{ }{\cs16\super K{\footnote \pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super K}{ Supported Formats}}}{ Supported Formats +\par }\pard\plain \widctlpar\adjustright \f1\fs18\lang2057 { +\par On several machines (such as the Amiga) Level 9 games were distributed as an interpreter plus a data file, usually called "gamedata.dat" or something similar. These games can be played with this interpreter simply by loading the "gamedata.dat" file. + +\par +\par For the Amiga (and possibly some other formats) the v4 games were released in three parts, each in a separate data file: +\par +\par \tab gamedat1.dat +\par \tab gamedat2.dat +\par \tab gamedat3.dat +\par +\par Starting the first game gives a menu from which you can choose which part or game to play. For this to work the files must have the same basic structure with a number in it somewhere, e.g. file names +\par +\par \tab TimeAndMagick1.dat +\par \tab TimeAndMagick2.dat +\par \tab TimeAndMagick3.dat +\par +\par will work. +\par +\par On other (especially older and smaller) computers the games were distributed a +s a single file containing both an interpreter and the game data. Level9 can cope with these files as well, as it automatically searches files for valid Level 9 games. This however requires that the file not be compressed in any way. For example, there ar +e + several Spectrum snapshots of Level 9 games available which this interpreter can play, but these snapshots must be in an uncompressed format (e.g. SNA). Commonly snapshots are available in the compressed Z80 format, but these files can be converted to SN +A using the widely available conversion program "SPConv". Version 1.06 or higher of "SPConv" is recommended. +\par +\par Paul David Doherty has written "L9Cut", which can extract Level 9 games from many different formats. L9Cut can also remove copy protection from Level 9 games. L9Cut can be downloaded from +\par +\par }{\f2 \tab }{\field\fldedit{\*\fldinst {\f2 HYPERLINK ftp://ftp.gmd.de/if-archive/level9/tools/ }{\f2\fs20 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b02000000170000002a0000006600740070003a002f002f006600740070002e0067006d0064002e00640065002f00690066002d0061007200630068006900760065002f006c006500760065006c0039002f0074006f006f006c0073002f000000e0c9ea79f9bace118c8200aa004b +a90b540000006600740070003a002f002f006600740070002e0067006d0064002e00640065002f00690066002d0061007200630068006900760065002f006c006500760065006c0039002f0074006f006f006c0073002f000000003000000000000000000000002d000000360000000000000000000000000000006f00}} +}{\fldrslt {\cs20\f2\ul\cf2 ftp://ftp.ifarchive.org/if-archive/level9/tools/}}}{\f2 +\par }{ +\par This program has been tested on files obtained from releases for the Amiga, Atari ST, IBM PC, C-64, Spectrum, Atari 800, BBC, Amstrad CPC, Apple 2 and MSX computers. +\par +\par }\pard\plain \s1\sb60\sa60\keepn\widctlpar\outlinelevel0\adjustright \b\f1\fs28\lang2057\kerning28 {\page }{\cs16\super ${\footnote \pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super $}{ Graphics}}}{ }{\cs16\super #{\footnote +\pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super #}{ Graphics}}}{ }{\cs16\super K{\footnote \pard\plain \s15\widctlpar\adjustright \f1\fs18\lang2057 {\cs16\super K}{ Graphics}}}{ Graphics +\par }\pard\plain \widctlpar\adjustright \f1\fs18\lang2057 { +\par The first games from Level 9 were text only, but later games included line drawn graphics. These graphics are supported by some versions of the interpreter, including the Windows version. +\par +\par One some platforms these graphics were placed into separate files (For example, in the Amiga release of "J +ewels of Darkness" the game is in "gamedata.dat" and the graphics in "picture.dat".). In order to show the pictures the graphics file should be given the same name as the game data file, but with an extension of ".pic", ".cga" or ".hrc" (the latter two be +ing used for graphics files in MS-DOS Level 9 releases). +\par +\par If no graphics file is specified, Level 9 will search the given game data file for graphics data. This is useful when the game data is a Spectrum SNA snapshot or a similar memory dump of an emulator. +\par +\par Later Level 9 games used bitmap graphics. As yet these are not supported by this interpreter. +\par +\par }} \ No newline at end of file diff --git a/Win/Level9.sln b/Win/Level9.sln new file mode 100755 index 0000000..6964e35 --- /dev/null +++ b/Win/Level9.sln @@ -0,0 +1,21 @@ +Microsoft Visual Studio Solution File, Format Version 7.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Level9", "Level9.vcproj", "{6376AFCA-CD2A-4B98-AD85-EF232DB76628}" +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + ConfigName.0 = Debug + ConfigName.1 = Release + EndGlobalSection + GlobalSection(ProjectDependencies) = postSolution + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {6376AFCA-CD2A-4B98-AD85-EF232DB76628}.Debug.ActiveCfg = Debug|Win32 + {6376AFCA-CD2A-4B98-AD85-EF232DB76628}.Debug.Build.0 = Debug|Win32 + {6376AFCA-CD2A-4B98-AD85-EF232DB76628}.Release.ActiveCfg = Release|Win32 + {6376AFCA-CD2A-4B98-AD85-EF232DB76628}.Release.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/Win/Level9.vcproj b/Win/Level9.vcproj new file mode 100755 index 0000000..744a008 --- /dev/null +++ b/Win/Level9.vcproj @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Win/Resource.h b/Win/Resource.h new file mode 100755 index 0000000..0ef33ea --- /dev/null +++ b/Win/Resource.h @@ -0,0 +1,27 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by Level9.rc +// +#define IDR_MANIFEST 1 +#define IDD_ABOUT 100 +#define IDI_ICON 101 +#define IDM_MENU 102 +#define CM_OPEN 200 +#define CM_FILELOAD 201 +#define CM_FILESAVE 202 +#define CM_DICTIONARY 203 +#define CM_EXIT 204 +#define CM_FONT 205 +#define CM_HELPCONTENTS 206 +#define CM_ABOUT 207 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 208 +#define _APS_NEXT_COMMAND_VALUE 40000 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 100 +#endif +#endif diff --git a/generic.c b/generic.c new file mode 100755 index 0000000..cb55c1f --- /dev/null +++ b/generic.c @@ -0,0 +1,234 @@ +/***********************************************************************\ +* +* Level 9 interpreter +* Version 3.0 +* Copyright (c) 1996 Glen Summers +* Copyright (c) 2002 Glen Summers and David Kinder +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. +* +\***********************************************************************/ + +#include +#include +#include "level9.h" + +#define TEXTBUFFER_SIZE 10240 +char TextBuffer[TEXTBUFFER_SIZE+1]; +int TextBufferPtr = 0; + +int Column = 0; +#define SCREENWIDTH 76 + +void os_printchar(char c) +{ + if (c == '\r') + { + os_flush(); + putchar('\n'); + Column = 0; + return; + } + if (isprint(c) == 0) return; + if (TextBufferPtr >= TEXTBUFFER_SIZE) os_flush(); + *(TextBuffer + (TextBufferPtr++)) = c; +} + +L9BOOL os_input(char *ibuff, int size) +{ +char *nl; + + os_flush(); + fgets(ibuff, size, stdin); + nl = strchr(ibuff, '\n'); + if (nl) *nl = 0; +} + +char os_readchar(int millis) +{ + static int count = 0; + char c; + + os_flush(); + if (millis == 0) return 0; + + if (++count < 1024) + return 0;/* fake no keypress on occasions */ + count = 0; + + c = getc(stdin); /* will require enter key as well */ + if (c != '\n') + { + while (getc(stdin) != '\n') + ;/* unbuffer input until enter key */ + } + + return c; +} + +L9BOOL os_stoplist(void) +{ + return FALSE; +} + +void os_flush(void) +{ +static int semaphore = 0; +int ptr, space, lastspace, searching; + + if (TextBufferPtr < 1) return; + if (semaphore) return; + semaphore = 1; + + *(TextBuffer+TextBufferPtr) = ' '; + ptr = 0; + while (TextBufferPtr + Column > SCREENWIDTH) + { + space = ptr; + lastspace = space; + searching = 1; + while (searching) + { + while (TextBuffer[space] != ' ') space++; + if (space - ptr + Column > SCREENWIDTH) + { + space = lastspace; + printf("%.*s\n", space - ptr, TextBuffer + ptr); + Column = 0; + space++; + if (TextBuffer[space] == ' ') space++; + TextBufferPtr -= (space - ptr); + ptr = space; + searching = 0; + } + else lastspace = space; + space++; + } + } + printf("%.*s", TextBufferPtr, TextBuffer + ptr); + Column += TextBufferPtr; + TextBufferPtr = 0; + + semaphore = 0; +} + +L9BOOL os_save_file(L9BYTE * Ptr, int Bytes) +{ +char name[256]; +char *nl; +FILE *f; + + os_flush(); + printf("Save file: "); + fgets(name, 256, stdin); + nl = strchr(name, '\n'); + if (nl) *nl = 0; + f = fopen(name, "wb"); + if (!f) return FALSE; + fwrite(Ptr, 1, Bytes, f); + fclose(f); + return TRUE; +} + +L9BOOL os_load_file(L9BYTE *Ptr,int *Bytes,int Max) +{ +char name[256]; +char *nl; +FILE *f; + + os_flush(); + printf("Load file: "); + fgets(name, 256, stdin); + nl = strchr(name, '\n'); + if (nl) *nl = 0; + f = fopen(name, "rb"); + if (!f) return FALSE; + *Bytes = fread(Ptr, 1, Max, f); + fclose(f); + return TRUE; +} + +L9BOOL os_get_game_file(char *NewName,int Size) +{ +char *nl; + + os_flush(); + printf("Load next game: "); + fgets(NewName, Size, stdin); + nl = strchr(NewName, '\n'); + if (nl) *nl = 0; + return TRUE; +} + +void os_set_filenumber(char *NewName,int Size,int n) +{ +char *p; +int i; + +#if defined(_Windows) || defined(__MSDOS__) || defined (_WIN32) || defined (__WIN32__) + p = strrchr(NewName,'\\'); +#else + p = strrchr(NewName,'/'); +#endif + if (p == NULL) p = NewName; + for (i = strlen(p)-1; i >= 0; i--) + { + if (isdigit(p[i])) + { + p[i] = '0'+n; + return; + } + } +} + +void os_graphics(int mode) +{ +} + +void os_cleargraphics(void) +{ +} + +void os_setcolour(int colour, int index) +{ +} + +void os_drawline(int x1, int y1, int x2, int y2, int colour1, int colour2) +{ +} + +void os_fill(int x, int y, int colour1, int colour2) +{ +} + +int main(int argc, char **argv) +{ + printf("Level 9 Interpreter\n\n"); + if (argc != 2) + { + printf("Syntax: %s \n",argv[0]); + return 0; + } + if (!LoadGame(argv[1],NULL)) + { + printf("Error: Unable to open game file\n"); + return 0; + } + while (RunGame()); + StopGame(); + FreeMemory(); + return 0; +} + diff --git a/level9.c b/level9.c new file mode 100755 index 0000000..3fb3eef --- /dev/null +++ b/level9.c @@ -0,0 +1,3532 @@ +/***********************************************************************\ +* +* Level 9 interpreter +* Version 3.0 +* Copyright (c) 1996 Glen Summers +* Copyright (c) 2002 Glen Summers and David Kinder +* Additional code by Alan Staniforth +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. +* +* The input routine will repond to the following 'hash' commands +* #save saves position file directly (bypasses any +* disk change prompts) +* #restore restores position file directly (bypasses any +* protection code) +* #quit terminates current game, RunGame() will return FALSE +* #cheat tries to bypass restore protection on v3,4 games +* (can be slow) +* #dictionary lists game dictionary (press a key to interrupt) +* #picture show picture +* +\***********************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include "level9.h" + +/* #define L9DEBUG */ +/* #define CODEFOLLOW */ +/* #define FULLSCAN */ + +/* "L901" */ +#define L9_ID 0x4c393031 + +#define IBUFFSIZE 500 +#define RAMSAVESLOTS 10 +#define GFXSTACKSIZE 100 + + +/* Typedefs */ +typedef struct +{ + L9UINT16 vartable[256]; + L9BYTE listarea[LISTAREASIZE]; +} SaveStruct; + +typedef struct +{ + L9BYTE *a5; + int scale; +} GfxState; + + +/* Enumerations */ +enum L9GameTypes {L9_V1,L9_V2,L9_V3,L9_V4}; +enum V2MsgTypes {V2M_NORMAL,V2M_ERIK}; + + +/* Global Variables */ +L9BYTE* startfile=NULL,*pictureaddress=NULL,*picturedata=NULL; +L9BYTE* startdata; +L9UINT32 FileSize,picturesize; + +L9BYTE *L9Pointers[12]; +L9BYTE *absdatablock,*list2ptr,*list3ptr,*list9startptr,*acodeptr; +L9BYTE *startmd,*endmd,*endwdp5,*wordtable,*dictdata,*defdict; +L9UINT16 dictdatalen; +L9BYTE *startmdV2; +L9BOOL DriverV4Calls; + +int wordcase; +int unpackcount; +char unpackbuf[8]; +L9BYTE* dictptr; +char threechars[34]; +int L9GameType; +int V2MsgType; +char LastGame[MAX_PATH]; + + +#if defined(AMIGA) && defined(_DCC) +__far SaveStruct ramsavearea[RAMSAVESLOTS]; +#else +SaveStruct ramsavearea[RAMSAVESLOTS]; +#endif + +GameState workspace; + +L9UINT16 randomseed; +L9BOOL Running; + +char ibuff[IBUFFSIZE]; +L9BYTE* ibuffptr; +char obuff[34]; + +L9BOOL Cheating=FALSE; +int CheatWord; +GameState CheatWorkspace; + +int reflectflag,scale,gintcolour,option; +int l9textmode=0,drawx=0,drawy=0,screencalled=0; +L9BYTE *gfxa5=NULL; + +GfxState GfxStack[GFXSTACKSIZE]; +int GfxStackPos=0; + +char lastchar='.'; +char lastactualchar=0; +int d5; + +L9BYTE* codeptr; /* instruction codes */ +L9BYTE code; /* instruction codes */ + +L9BYTE* list9ptr; + +int unpackd3; + +L9BYTE exitreversaltable[16]= {0x00,0x04,0x06,0x07,0x01,0x08,0x02,0x03,0x05,0x0a,0x09,0x0c,0x0b,0xff,0xff,0x0f}; + +L9UINT16 gnostack[128]; +L9BYTE gnoscratch[32]; +int object,gnosp,numobjectfound,searchdepth,inithisearchpos; + + +/* Prototypes */ +L9BOOL LoadGame2(char *filename,char *picname); +L9BOOL checksubs(void); +int getlongcode(void); +L9BOOL GetWordV2(char *buff,int Word); +L9BOOL GetWordV3(char *buff,int Word); +void show_picture(int pic); + + +#ifdef CODEFOLLOW +#define CODEFOLLOWFILE "c:\\temp\\output" +FILE *f; +L9UINT16 *cfvar,*cfvar2; +char *codes[]= +{ +"Goto", +"intgosub", +"intreturn", +"printnumber", +"messagev", +"messagec", +"function", +"input", +"varcon", +"varvar", +"_add", +"_sub", +"ilins", +"ilins", +"jump", +"Exit", +"ifeqvt", +"ifnevt", +"ifltvt", +"ifgtvt", +"_screen", +"cleartg", +"picture", +"getnextobject", +"ifeqct", +"ifnect", +"ifltct", +"ifgtct", +"printinput", +"ilins", +"ilins", +"ilins", +}; +char *functions[]= +{ + "calldriver", + "L9Random", + "save", + "restore", + "clearworkspace", + "clearstack" +}; +char *drivercalls[]= +{ +"init", +"drivercalcchecksum", +"driveroswrch", +"driverosrdch", +"driverinputline", +"driversavefile", +"driverloadfile", +"settext", +"resettask", +"returntogem", +"10 *", +"loadgamedatafile", +"randomnumber", +"13 *", +"v4 driver14", +"15 *", +"driverclg", +"line", +"fill", +"driverchgcol", +"20 *", +"21 *", +"ramsave", +"ramload", +"24 *", +"lensdisplay", +"26 *", +"27 *", +"28 *", +"29 *", +"allocspace", +"31 *", +"v4 driver 32", +"33 *", +"v4 checkfordisc" +}; +#endif + +void initdict(L9BYTE *ptr) +{ + dictptr=ptr; + unpackcount=8; +} + +char getdictionarycode(void) +{ + if (unpackcount!=8) return unpackbuf[unpackcount++]; + else + { + /* unpackbytes */ + L9BYTE d1=*dictptr++,d2; + unpackbuf[0]=d1>>3; + d2=*dictptr++; + unpackbuf[1]=((d2>>6) + (d1<<2)) & 0x1f; + d1=*dictptr++; + unpackbuf[2]=(d2>>1) & 0x1f; + unpackbuf[3]=((d1>>4) + (d2<<4)) & 0x1f; + d2=*dictptr++; + unpackbuf[4]=((d1<<1) + (d2>>7)) & 0x1f; + d1=*dictptr++; + unpackbuf[5]=(d2>>2) & 0x1f; + unpackbuf[6]=((d2<<3) + (d1>>5)) & 0x1f; + unpackbuf[7]=d1 & 0x1f; + unpackcount=1; + return unpackbuf[0]; + } +} + +int getdictionary(int d0) +{ + if (d0>=0x1a) return getlongcode(); + else return d0+0x61; +} + +int getlongcode(void) +{ + int d0,d1; + d0=getdictionarycode(); + if (d0==0x10) + { + wordcase=1; + d0=getdictionarycode(); + return getdictionary(d0); /* reentrant? */ + } + d1=getdictionarycode(); + return 0x80 | ((d0<<5) & 0xe0) | (d1 & 0x1f); +} + +void printchar(char c) +{ + if (Cheating) return; + + if (c&128) lastchar=(c&=0x7f); + else if (c!=0x20 && c!=0x0d && c!=0x7e && (c<'\"' || c>='.')) + { + if (lastchar=='!' || lastchar=='?' || lastchar=='.') c=toupper(c); + lastchar=c; + } + /* eat multiple CRs */ + if (c!=0x0d || lastactualchar!=0x0d) os_printchar(c); + lastactualchar=c; +} + +void printstring(char*buf) +{ + int i; + for (i=0;i< (int) strlen(buf);i++) printchar(buf[i]); +} + +void printdecimald0(int d0) +{ + char temp[12]; + sprintf(temp,"%d",d0); + printstring(temp); +} + +void error(char *fmt,...) +{ + char buf[256]; + int i; + va_list ap; + va_start(ap,fmt); + vsprintf(buf,fmt,ap); + va_end(ap); + for (i=0;i< (int) strlen(buf);i++) os_printchar(buf[i]); +} + +void printautocase(int d0) +{ + if (d0 & 128) printchar((char) d0); + else + { + if (wordcase) printchar((char) toupper(d0)); + else if (d5<6) printchar((char) d0); + else + { + wordcase=0; + printchar((char) toupper(d0)); + } + } +} + +void displaywordref(L9UINT16 Off) +{ + static int mdtmode=0; + + wordcase=0; + d5=(Off>>12)&7; + Off&=0xfff; + if (Off<0xf80) + { + /* dwr01 */ + L9BYTE *a0,*oPtr,*a3; + int d0,d2,i; + + if (mdtmode==1) printchar(0x20); + mdtmode=1; + + /* setindex */ + a0=dictdata; + d2=dictdatalen; + + /* dwr02 */ + oPtr=a0; + while (d2 && Off >= L9WORD(a0+2)) + { + a0+=4; + d2--; + } + /* dwr04 */ + if (a0==oPtr) + { + a0=defdict; + } + else + { + a0-=4; + Off-=L9WORD(a0+2); + a0=startdata+L9WORD(a0); + } + /* dwr04b */ + Off++; + initdict(a0); + a3=(L9BYTE*) threechars; /* a3 not set in original, prevent possible spam */ + + /* dwr05 */ + while (TRUE) + { + d0=getdictionarycode(); + if (d0<0x1c) + { + /* dwr06 */ + if (d0>=0x1a) d0=getlongcode(); + else d0+=0x61; + *a3++=d0; + } + else + { + d0&=3; + a3=(L9BYTE*) threechars+d0; + if (--Off==0) break; + } + } + for (i=0;i=0x1b) return; + printautocase(getdictionary(d0)); + } + } + + else + { + if (d5&2) printchar(0x20); /* prespace */ + mdtmode=2; + Off&=0x7f; + if (L9GameType!=L9_V4 || Off!=0x7e) printchar((char)Off); + if (d5&1) printchar(0x20); /* postspace */ + } +} + +int getmdlength(L9BYTE **Ptr) +{ + int tot=0,len; + do + { + len=(*(*Ptr)++ -1) & 0x3f; + tot+=len; + } while (len==0x3f); + return tot; +} + +void printmessage(int Msg) +{ + L9BYTE* Msgptr=startmd; + L9BYTE Data; + + int len; + L9UINT16 Off; + + while (Msg>0 && Msgptr-endmd<=0) + { + Data=*Msgptr; + if (Data&128) + { + Msgptr++; + Msg-=Data&0x7f; + } + else Msgptr+=getmdlength(&Msgptr); + Msg--; + } + if (Msg<0 || *Msgptr & 128) return; + + len=getmdlength(&Msgptr); + if (len==0) return; + + while (len) + { + Data=*Msgptr++; + len--; + if (Data&128) + { + /* long form (reverse word) */ + Off=(Data<<8) + *Msgptr++; + len--; + } + else + { + Off=(wordtable[Data*2]<<8) + wordtable[Data*2+1]; + } + if (Off==0x8f80) break; + displaywordref(Off); + } +} + +/* v2 message stuff */ + +int msglenV2(L9BYTE **ptr) +{ + int i=0; + L9BYTE a; + + /* catch berzerking code */ + if (*ptr >= startdata+FileSize) return 0; + + while ((a=**ptr)==0) + { + (*ptr)++; + + if (*ptr >= startdata+FileSize) return 0; + + i+=255; + } + i+=a; + return i; +} + +void printcharV2(char c) +{ + if (c==0x25) c=0xd; + else if (c==0x5f) c=0x20; + printautocase(c); +} + +void displaywordV2(L9BYTE *ptr,int msg) +{ + int n; + L9BYTE a; + if (msg==0) return; + while (--msg) + { + ptr+=msglenV2(&ptr); + } + n=msglenV2(&ptr); + + while (--n>0) + { + a=*++ptr; + if (a<3) return; + + if (a>=0x5e) displaywordV2(startmdV2-1,a-0x5d); + else printcharV2((char)(a+0x1d)); + } +} + +int msglenV25(L9BYTE **ptr) +{ + L9BYTE *ptr2=*ptr; + while (ptr20) + { + a=*ptr++; + if (a<3) return; + + if (a>=0x5e) displaywordV25(startmdV2,a-0x5e); + else printcharV2((char)(a+0x1d)); + } +} + +L9BOOL amessageV2(L9BYTE *ptr,int msg,long *w,long *c) +{ + int n; + L9BYTE a; + static int depth=0; + if (msg==0) return FALSE; + while (--msg) + { + ptr+=msglenV2(&ptr); + } + if (ptr >= startdata+FileSize) return FALSE; + n=msglenV2(&ptr); + + while (--n>0) + { + a=*++ptr; + if (a<3) return TRUE; + + if (a>=0x5e) + { + if (++depth>10 || !amessageV2(startmdV2-1,a-0x5d,w,c)) + { + depth--; + return FALSE; + } + depth--; + } + else + { + char ch=a+0x1d; + if (ch==0x5f || ch==' ') (*w)++; + else (*c)++; + } + } + return TRUE; +} + +L9BOOL amessageV25(L9BYTE *ptr,int msg,long *w,long *c) +{ + int n; + L9BYTE a; + static int depth=0; + + while (msg--) + { + ptr+=msglenV25(&ptr); + } + if (ptr >= startdata+FileSize) return FALSE; + n=msglenV25(&ptr); + + while (--n>0) + { + a=*ptr++; + if (a<3) return TRUE; + + if (a>=0x5e) + { + if (++depth>10 || !amessageV25(startmdV2,a-0x5e,w,c)) + { + depth--; + return FALSE; + } + depth--; + } + else + { + char ch=a+0x1d; + if (ch==0x5f || ch==' ') (*w)++; + else (*c)++; + } + } + return TRUE; +} + +L9BOOL analyseV2(double *wl) +{ + long words=0,chars=0; + int i; + for (i=1;i<256;i++) + { + long w=0,c=0; + if (amessageV2(startmd,i,&w,&c)) + { + words+=w; + chars+=c; + } + else return FALSE; + } + *wl=words ? (double) chars/words : 0.0; + return TRUE; +} + +L9BOOL analyseV25(double *wl) +{ + long words=0,chars=0; + int i; + for (i=0;i<256;i++) + { + long w=0,c=0; + if (amessageV25(startmd,i,&w,&c)) + { + words+=w; + chars+=c; + } + else return FALSE; + } + + *wl=words ? (double) chars/words : 0.0; + return TRUE; +} + +void printmessageV2(int Msg) +{ + if (V2MsgType==V2M_NORMAL) displaywordV2(startmd,Msg); + else displaywordV25(startmd,Msg); +} + +L9UINT32 filelength(FILE *f) +{ + L9UINT32 pos,FileSize; + + pos=ftell(f); + fseek(f,0,SEEK_END); + FileSize=ftell(f); + fseek(f,pos,SEEK_SET); + return FileSize; +} + +void L9Allocate(L9BYTE **ptr,L9UINT32 Size) +{ + if (*ptr) free(*ptr); + *ptr=malloc(Size); + if (*ptr==NULL) + { + fprintf(stderr,"Unable to allocate memory for the game! Exiting...\n"); + exit(0); + } +} + +void FreeMemory(void) +{ + if (startfile) + { + free(startfile); + startfile=NULL; + } + if (pictureaddress) + { + free(pictureaddress); + pictureaddress=NULL; + } + picturedata=NULL; + picturesize=0; + gfxa5=NULL; +} + +L9BOOL load(char *filename) +{ + FILE *f=fopen(filename,"rb"); + if (!f) return FALSE; + FileSize=filelength(f); + L9Allocate(&startfile,FileSize); + + if (fread(startfile,1,FileSize,f)!=FileSize) + { + fclose(f); + return FALSE; + } + fclose(f); + return TRUE; +} + +L9UINT16 scanmovewa5d0(L9BYTE* Base,L9UINT32 *Pos) +{ + L9UINT16 ret=L9WORD(Base+*Pos); + (*Pos)+=2; + return ret; +} + +L9UINT32 scangetaddr(int Code,L9BYTE *Base,L9UINT32 *Pos,L9UINT32 acode,int *Mask) +{ + (*Mask)|=0x20; + if (Code&0x20) + { + /* getaddrshort */ + signed char diff=Base[*Pos]; + (*Pos)++; + return (*Pos)+diff-1; + } + else + { + return acode+scanmovewa5d0(Base,Pos); + } +} + +void scangetcon(int Code,L9UINT32 *Pos,int *Mask) +{ + (*Pos)++; + if (!(Code & 64)) (*Pos)++; + (*Mask)|=0x40; +} + +L9BOOL CheckCallDriverV4(L9BYTE* Base,L9UINT32 Pos) +{ + int i,j; + + /* Look back for an assignment from a variable + * to list9[0], which is used to specify the + * driver call. + */ + for (i = 0; i < 2; i++) + { + int x = Pos - ((i+1)*3); + if ((Base[x] == 0x89) && (Base[x+1] == 0x00)) + { + /* Get the variable being copied to list9[0] */ + int var = Base[x+2]; + + /* Look back for an assignment to the variable. */ + for (j = 0; j < 2; j++) + { + int y = x - ((j+1)*3); + if ((Base[y] == 0x48) && (Base[y+2] == var)) + { + /* If this a V4 driver call? */ + switch (Base[y+1]) + { + case 0x0E: + case 0x20: + case 0x22: + return TRUE; + } + return FALSE; + } + } + } + } + return FALSE; +} + +L9BOOL ValidateSequence(L9BYTE* Base,L9BYTE* Image,L9UINT32 iPos,L9UINT32 acode,L9UINT32 *Size,L9UINT32 FileSize,L9UINT32 *Min,L9UINT32 *Max,L9BOOL Rts,L9BOOL *JumpKill, L9BOOL *DriverV4) +{ + L9UINT32 Pos; + L9BOOL Finished=FALSE,Valid; + L9UINT32 Strange=0; + int ScanCodeMask; + int Code; + *JumpKill=FALSE; + + if (iPos>=FileSize) + return FALSE; + Pos=iPos; + if (Pos<*Min) *Min=Pos; + + if (Image[Pos]) return TRUE; /* hit valid code */ + + do + { + Code=Base[Pos]; + Valid=TRUE; + if (Image[Pos]) break; /* converged to found code */ + Image[Pos++]=2; + if (Pos>*Max) *Max=Pos; + + ScanCodeMask=0x9f; + if (Code&0x80) + { + ScanCodeMask=0xff; + if ((Code&0x1f)>0xa) + Valid=FALSE; + Pos+=2; + } + else switch (Code & 0x1f) + { + case 0: /* goto */ + { + L9UINT32 Val=scangetaddr(Code,Base,&Pos,acode,&ScanCodeMask); + Valid=ValidateSequence(Base,Image,Val,acode,Size,FileSize,Min,Max,TRUE/*Rts*/,JumpKill,DriverV4); + Finished=TRUE; + break; + } + case 1: /* intgosub */ + { + L9UINT32 Val=scangetaddr(Code,Base,&Pos,acode,&ScanCodeMask); + Valid=ValidateSequence(Base,Image,Val,acode,Size,FileSize,Min,Max,TRUE,JumpKill,DriverV4); + break; + } + case 2: /* intreturn */ + Valid=Rts; + Finished=TRUE; + break; + case 3: /* printnumber */ + Pos++; + break; + case 4: /* messagev */ + Pos++; + break; + case 5: /* messagec */ + scangetcon(Code,&Pos,&ScanCodeMask); + break; + case 6: /* function */ + switch (Base[Pos++]) + { + case 2:/* random */ + Pos++; + break; + case 1:/* calldriver */ + if (DriverV4) + { + if (CheckCallDriverV4(Base,Pos-2)) + *DriverV4 = TRUE; + } + break; + case 3:/* save */ + case 4:/* restore */ + case 5:/* clearworkspace */ + case 6:/* clear stack */ + break; + case 250: /* printstr */ + while (Base[Pos++]); + break; + + default: +#ifdef L9DEBUG + /* printf("scan: illegal function call: %d",Base[Pos-1]); */ +#endif + Valid=FALSE; + break; + } + break; + case 7: /* input */ + Pos+=4; + break; + case 8: /* varcon */ + scangetcon(Code,&Pos,&ScanCodeMask); + Pos++; + break; + case 9: /* varvar */ + Pos+=2; + break; + case 10: /* _add */ + Pos+=2; + break; + case 11: /* _sub */ + Pos+=2; + break; + case 14: /* jump */ +#ifdef L9DEBUG + /* printf("jmp at codestart: %ld",acode); */ +#endif + *JumpKill=TRUE; + Finished=TRUE; + break; + case 15: /* exit */ + Pos+=4; + break; + case 16: /* ifeqvt */ + case 17: /* ifnevt */ + case 18: /* ifltvt */ + case 19: /* ifgtvt */ + { + L9UINT32 Val; + Pos+=2; + Val=scangetaddr(Code,Base,&Pos,acode,&ScanCodeMask); + Valid=ValidateSequence(Base,Image,Val,acode,Size,FileSize,Min,Max,Rts,JumpKill,DriverV4); + break; + } + case 20: /* _screen */ + if (Base[Pos++]) Pos++; + break; + case 21: /* cleartg */ + Pos++; + break; + case 22: /* picture */ + Pos++; + break; + case 23: /* getnextobject */ + Pos+=6; + break; + case 24: /* ifeqct */ + case 25: /* ifnect */ + case 26: /* ifltct */ + case 27: /* ifgtct */ + { + L9UINT32 Val; + Pos++; + scangetcon(Code,&Pos,&ScanCodeMask); + Val=scangetaddr(Code,Base,&Pos,acode,&ScanCodeMask); + Valid=ValidateSequence(Base,Image,Val,acode,Size,FileSize,Min,Max,Rts,JumpKill,DriverV4); + break; + } + case 28: /* printinput */ + break; + case 12: /* ilins */ + case 13: /* ilins */ + case 29: /* ilins */ + case 30: /* ilins */ + case 31: /* ilins */ +#ifdef L9DEBUG + /* printf("scan: illegal instruction"); */ +#endif + Valid=FALSE; + break; + } + if (Valid && (Code & ~ScanCodeMask)) + Strange++; + } while (Valid && !Finished && Pos=0x8000+LISTAREASIZE) return FALSE; + } + + num=L9WORD(StartFile+Offset)+1; + if (Offset+num>FileSize) return FALSE; + if (calcchecksum(StartFile+Offset,num)) return FALSE; + + Image=calloc(FileSize,1); + + Min=Max=Offset+d0; + ret=ValidateSequence(StartFile,Image,Offset+d0,Offset+d0,&Size,FileSize,&Min,&Max,FALSE,&JumpKill,NULL); + free(Image); + return ret; +} +*/ + +long Scan(L9BYTE* StartFile,L9UINT32 FileSize) +{ + L9BYTE *Chk=malloc(FileSize+1); + L9BYTE *Image=calloc(FileSize,1); + L9UINT32 i,num,Size,MaxSize=0; + int j; + L9UINT16 d0=0,l9,md,ml,dd,dl; + L9UINT32 Min,Max; + long Offset=-1; + L9BOOL JumpKill, DriverV4; + + if ((Chk==NULL)||(Image==NULL)) + { + fprintf(stderr,"Unable to allocate memory for game scan! Exiting...\n"); + exit(0); + } + + Chk[0]=0; + for (i=1;i<=FileSize;i++) + Chk[i]=Chk[i-1]+StartFile[i-1]; + + for (i=0;i0x2000 && i+num<=FileSize && Chk[i+num]==Chk[i]) + { + md=L9WORD(StartFile+i+0x2); + ml=L9WORD(StartFile+i+0x4); + dd=L9WORD(StartFile+i+0xa); + dl=L9WORD(StartFile+i+0xc); + + if (ml>0 && md>0 && i+md+ml<=FileSize && dd>0 && dl>0 && i+dd+dl*4<=FileSize) + { + /* v4 files may have acodeptr in 8000-9000, need to fix */ + for (j=0;j<12;j++) + { + d0=L9WORD (StartFile+i+0x12 + j*2); + if (j!=11 && d0>=0x8000 && d0<0x9000) + { + if (d0>=0x8000+LISTAREASIZE) break; + } + else if (i+d0>FileSize) break; + } + /* list9 ptr must be in listarea, acode ptr in data */ + if (j<12 /*|| (d0>=0x8000 && d0<0x9000)*/) continue; + + l9=L9WORD(StartFile+i+0x12 + 10*2); + if (l9<0x8000 || l9>=0x8000+LISTAREASIZE) continue; + + Size=0; + Min=Max=i+d0; + DriverV4=0; + if (ValidateSequence(StartFile,Image,i+d0,i+d0,&Size,FileSize,&Min,&Max,FALSE,&JumpKill,&DriverV4)) + { +#ifdef L9DEBUG + printf("Found valid header at %ld, code size %ld",i,Size); +#endif + if (Size>MaxSize) + { + Offset=i; + MaxSize=Size; + DriverV4Calls=DriverV4; + } + } + } + } + } + free(Chk); + free(Image); + return Offset; +} + +long ScanV2(L9BYTE* StartFile,L9UINT32 FileSize) +{ + L9BYTE *Chk=malloc(FileSize+1); + L9BYTE *Image=calloc(FileSize,1); + L9UINT32 i,Size,MaxSize=0,num; + int j; + L9UINT16 d0=0,l9; + L9UINT32 Min,Max; + long Offset=-1; + L9BOOL JumpKill; + + if ((Chk==NULL)||(Image==NULL)) + { + fprintf(stderr,"Unable to allocate memory for game scan! Exiting...\n"); + exit(0); + } + + Chk[0]=0; + for (i=1;i<=FileSize;i++) + Chk[i]=Chk[i-1]+StartFile[i-1]; + + for (i=0;i=0x8000 && d0<0x9000) + { + if (d0>=0x8000+LISTAREASIZE) break; + } + else if (i+d0>FileSize) break; + } + /* list9 ptr must be in listarea, acode ptr in data */ + if (j<14 /*|| (d0>=0x8000 && d0<0x9000)*/) continue; + + l9=L9WORD(StartFile+i+6 + 9*2); + if (l9<0x8000 || l9>=0x8000+LISTAREASIZE) continue; + + Size=0; + Min=Max=i+d0; + if (ValidateSequence(StartFile,Image,i+d0,i+d0,&Size,FileSize,&Min,&Max,FALSE,&JumpKill,NULL)) + { +#ifdef L9DEBUG + printf("Found valid V2 header at %ld, code size %ld",i,Size); +#endif + if (Size>MaxSize) + { + Offset=i; + MaxSize=Size; + } + } + } + } + free(Chk); + free(Image); + return Offset; +} + +long ScanV1(L9BYTE* StartFile,L9UINT32 FileSize) +{ + return -1; +/* + L9BYTE *Image=calloc(FileSize,1); + L9UINT32 i,Size; + int Replace; + L9BYTE* ImagePtr; + long MaxPos=-1; + L9UINT32 MaxCount=0; + L9UINT32 Min,Max,MaxMin,MaxMax; + L9BOOL JumpKill,MaxJK; + + L9BYTE c; + int maxdict,maxdictlen; + L9BYTE *ptr,*start; + + for (i=0;iMaxCount) + { + MaxCount=Size; + MaxMin=Min; + MaxMax=Max; + + MaxPos=i; + MaxJK=JumpKill; + } + Replace=0; + } + for (ImagePtr=Image+Min;ImagePtr<=Image+Max;ImagePtr++) if (*ImagePtr==2) *ImagePtr=Replace; + } +#ifdef L9DEBUG + printf("V1scan found code at %ld size %ld",MaxPos,MaxCount); +#endif + + ptr=StartFile; + maxdictlen=0; + do + { + start=ptr; + do + { + do + { + c=*ptr++; + } while (((c>='A' && c<='Z') || c=='-') && ptr'Z') && (c&0x7f)!='/')) break; + ptr++; + } while (TRUE); + if (ptr-start-1>maxdictlen) + { + maxdict=start-StartFile; + maxdictlen=ptr-start-1; + } + } while (ptr0) printf("V1scan found dictionary at %ld size %ld",maxdict,maxdictlen); +#endif + + MaxPos=-1; + + free(Image); + return MaxPos; +*/ +} + +#ifdef FULLSCAN +void FullScan(L9BYTE* StartFile,L9UINT32 FileSize) +{ + L9BYTE *Image=calloc(FileSize,1); + L9UINT32 i,Size; + int Replace; + L9BYTE* ImagePtr; + L9UINT32 MaxPos=0; + L9UINT32 MaxCount=0; + L9UINT32 Min,Max,MaxMin,MaxMax; + int Offset; + L9BOOL JumpKill,MaxJK; + for (i=0;iMaxCount) + { + MaxCount=Size; + MaxMin=Min; + MaxMax=Max; + + MaxPos=i; + MaxJK=JumpKill; + } + Replace=0; + } + for (ImagePtr=Image+Min;ImagePtr<=Image+Max;ImagePtr++) if (*ImagePtr==2) *ImagePtr=Replace; + } + printf("%ld %ld %ld %ld %s",MaxPos,MaxCount,MaxMin,MaxMax,MaxJK ? "jmp killed" : ""); + /* search for reference to MaxPos */ + Offset=0x12 + 11*2; + for (i=0;i=0x8000 && d0<=0x9000) ? workspace.listarea+d0-0x8000 : startdata+d0; + } + absdatablock=L9Pointers[0]; + list2ptr=L9Pointers[3]; + list3ptr=L9Pointers[4]; + /*list9startptr */ +/* + if ((((L9UINT32) L9Pointers[10])&1)==0) L9Pointers[10]++; amiga word access hack +*/ + list9startptr=L9Pointers[10]; + acodeptr=L9Pointers[11]; + } + + switch (L9GameType) + { + case L9_V1: + break; + case L9_V2: + { + double a2,a25; + startmd=startdata + L9WORD(startdata+0x0); + startmdV2=startdata + L9WORD(startdata+0x2); + + /* determine message type */ + if (analyseV2(&a2) && a2>2 && a2<10) + { + V2MsgType=V2M_NORMAL; + #ifdef L9DEBUG + printf("V2 msg table: normal, wordlen=%.2lf",a2); + #endif + } + else if (analyseV25(&a25) && a25>2 && a25<10) + { + V2MsgType=V2M_ERIK; + #ifdef L9DEBUG + printf("V2 msg table: Erik, wordlen=%.2lf",a25); + #endif + } + else + { + error("\rUnable to identify V2 message table in file: %s\r",filename); + return FALSE; + } + break; + } + case L9_V3: + case L9_V4: + startmd=startdata + L9WORD(startdata+0x2); + endmd=startmd + L9WORD(startdata+0x4); + defdict=startdata+L9WORD(startdata+6); + endwdp5=defdict + 5 + L9WORD(startdata+0x8); + dictdata=startdata+L9WORD(startdata+0x0a); + dictdatalen=L9WORD(startdata+0x0c); + wordtable=startdata + L9WORD(startdata+0xe); + break; + } + +#ifndef NO_SCAN_GRAPHICS + /* If there was no graphics file, look in the game data */ + if (picturedata==NULL) + { + int sz=FileSize-(acodeptr-startdata); + int i=0; + while ((i0xfa) *a6=1; + else if (d1+1>=RAMSAVESLOTS) *a6=0xff; + else + { + *a6=0; + if (d0==0x16) ramsave(d1+1); else ramload(d1+1); + } + *list9startptr=*a6; + } + else if (d0==0x0b) + { + char NewName[MAX_PATH]; + strcpy(NewName,LastGame); + if (*a6==0) + { + printstring("\rSearching for next sub-game file.\r"); + if (!os_get_game_file(NewName,MAX_PATH)) + { + printstring("\rFailed to load game.\r"); + return; + } + } + else + { + os_set_filenumber(NewName,MAX_PATH,*a6); + } + LoadGame2(NewName,NULL); + } + else driver(d0,a6); +} + +void L9Random(void) +{ + randomseed=(((randomseed<<8) + 0x0a - randomseed) <<2) + randomseed + 1; + *getvar()=randomseed & 0xff; +} + +void save(void) +{ + L9UINT16 checksum; + int i; +#ifdef L9DEBUG + printf("function - save"); +#endif +/* does a full save, workpace, stack, codeptr, stackptr, game name, checksum */ + + workspace.Id=L9_ID; + workspace.codeptr=codeptr-acodeptr; + workspace.listsize=LISTAREASIZE; + workspace.stacksize=STACKSIZE; + workspace.filenamesize=MAX_PATH; + workspace.checksum=0; + strcpy(workspace.filename,LastGame); + + checksum=0; + for (i=0;iId!=L9_ID) return FALSE; + checksum=gs->checksum; + gs->checksum=0; + for (i=0;ifilename,LastGame)) + { + printstring("\rWarning: game path name does not match, you may be about to load this position file into the wrong story file.\r"); + printstring("Are you sure you want to restore? (Y/N)"); + os_flush(); + + c = '\0'; + while ((c != 'y') && (c != 'Y') && (c != 'n') && (c != 'N')) + c = os_readchar(20); + } + if ((c == 'y') || (c == 'Y')) + return TRUE; + return FALSE; +} + +void NormalRestore(void) +{ + GameState temp; + int Bytes; +#ifdef L9DEBUG + printf("function - restore"); +#endif + if (Cheating) + { + /* not really an error */ + Cheating=FALSE; + error("\rWord is: %s\r",ibuff); + } + + if (os_load_file((L9BYTE*) &temp,&Bytes,sizeof(GameState))) + { + if (Bytes==V1FILESIZE) + { + printstring("\rGame restored.\r"); + memset(workspace.listarea,0,LISTAREASIZE); + memmove(workspace.vartable,&temp,V1FILESIZE); + } + else if (CheckFile(&temp)) + { + printstring("\rGame restored.\r"); + /* only copy in workspace */ + memmove(workspace.vartable,temp.vartable,sizeof(SaveStruct)); + } + else + { + printstring("\rSorry, unrecognised format. Unable to restore\r"); + } + } + else printstring("\rUnable to restore game.\r"); +} + +void restore(void) +{ + int Bytes; + GameState temp; + if (os_load_file((L9BYTE*) &temp,&Bytes,sizeof(GameState))) + { + if (Bytes==V1FILESIZE) + { + printstring("\rGame restored.\r"); + /* only copy in workspace */ + memset(workspace.listarea,0,LISTAREASIZE); + memmove(workspace.vartable,&temp,V1FILESIZE); + } + else if (CheckFile(&temp)) + { + printstring("\rGame restored.\r"); + /* full restore */ + memmove(&workspace,&temp,sizeof(GameState)); + codeptr=acodeptr+workspace.codeptr; + } + else + { + printstring("\rSorry, unrecognised format. Unable to restore\r"); + } + } + else printstring("\rUnable to restore game.\r"); +} + +void clearworkspace(void) +{ + memset(workspace.vartable,0,sizeof(workspace.vartable)); +} + +void ilins(int d0) +{ + error("\rIllegal instruction: %d\r",d0); + Running=FALSE; +} + +void function(void) +{ + int d0=*codeptr++; +#ifdef CODEFOLLOW + fprintf(f," %s",d0==250 ? "printstr" : functions[d0-1]); +#endif + + switch (d0) + { + case 1: calldriver(); break; + case 2: L9Random(); break; + case 3: save(); break; + case 4: NormalRestore(); break; + case 5: clearworkspace(); break; + case 6: workspace.stackptr=0; break; + case 250: + printstring((char*) codeptr); + while (*codeptr++); + break; + + default: ilins(d0); + } +} + +void findmsgequiv(int d7) +{ + int d4=-1,d0; + L9BYTE* a2=startmd; + + do + { + d4++; + if (a2>endmd) return; + d0=*a2; + if (d0&0x80) + { + a2++; + d4+=d0&0x7f; + } + else if (d0&0x40) + { + int d6=getmdlength(&a2); + do + { + int d1; + if (d6==0) break; + + d1=*a2++; + d6--; + if (d1 & 0x80) + { + if (d1<0x90) + { + a2++; + d6--; + } + else + { + d0=(d1<<8) + *a2++; + d6--; + if (d7==(d0 & 0xfff)) + { + d0=((d0<<1)&0xe000) | d4; + list9ptr[1]=d0; + list9ptr[0]=d0>>8; + list9ptr+=2; + if (list9ptr>=list9startptr+0x20) return; + } + } + } + } while (TRUE); + } + else a2+=getmdlength(&a2); + } while (TRUE); +} + +L9BOOL unpackword(void) +{ + L9BYTE *a3; + + if (unpackd3==0x1b) return TRUE; + + a3=(L9BYTE*) threechars + (unpackd3&3); + +/*uw01 */ + while (TRUE) + { + L9BYTE d0=getdictionarycode(); + if (dictptr>=endwdp5) return TRUE; + if (d0>=0x1b) + { + *a3=0; + unpackd3=d0; + return FALSE; + } + *a3++=getdictionary(d0); + } +} + +L9BOOL initunpack(L9BYTE* ptr) +{ + initdict(ptr); + unpackd3=0x1c; + return unpackword(); +} + +int partword(char c) +{ + c=tolower(c); + + if (c==0x27 || c==0x2d) return 0; + if (c<0x30) return 1; + if (c<0x3a) return 0; + if (c<0x61) return 1; + if (c<0x7b) return 0; + return 1; +} + +L9UINT32 readdecimal(char *buff) +{ + return atol(buff); +} + +void checknumber(void) +{ + if (*obuff>=0x30 && *obuff<0x3a) + { + /* MS-DOS Scapeghost calls the input routine for a number before + * making any driver calls that would identify it as a V4 game, + * so here the code relies on the intial code scan noticing V4 + * driver calls. + */ + if (L9GameType==L9_V4 || (L9GameType==L9_V3 && DriverV4Calls)) + { + *list9ptr=1; + L9SETWORD(list9ptr+1,readdecimal(obuff)); + L9SETWORD(list9ptr+3,0); + } + else + { + L9SETDWORD(list9ptr,readdecimal(obuff)); + L9SETWORD(list9ptr+4,0); + } + } + else + { + L9SETWORD(list9ptr,0x8000); + L9SETWORD(list9ptr+2,0); + } +} + +void NextCheat(void) +{ + /* restore game status */ + memmove(&workspace,&CheatWorkspace,sizeof(GameState)); + codeptr=acodeptr+workspace.codeptr; + + if (!((L9GameType==L9_V2) ? GetWordV2(ibuff,CheatWord++) : GetWordV3(ibuff,CheatWord++))) + { + Cheating=FALSE; + printstring("\rCheat failed.\r"); + *ibuff=0; + } +} + +void StartCheat(void) +{ + Cheating=TRUE; + CheatWord=0; + /* save current game status */ + + memmove(&CheatWorkspace,&workspace,sizeof(GameState)); + CheatWorkspace.codeptr=codeptr-acodeptr; + + NextCheat(); +} + +/* v3,4 input routine */ + +L9BOOL GetWordV3(char *buff,int Word) +{ + int i; + int subdict=0; + /* 26*4-1=103 */ + + initunpack(startdata+L9WORD(dictdata)); + unpackword(); + + while (Word--) + { + if (unpackword()) + { + if (++subdict==dictdatalen) return FALSE; + initunpack(startdata+L9WORD(dictdata+(subdict<<2))); + Word++; /* force unpack again */ + } + } + strcpy(buff,threechars); + for (i=0;i<(int)strlen(buff);i++) buff[i]&=0x7f; + return TRUE; +} + +L9BOOL CheckHash(void) +{ + if (stricmp(ibuff,"#cheat")==0) StartCheat(); + else if (stricmp(ibuff,"#save")==0) + { + save(); + return TRUE; + } + else if (stricmp(ibuff,"#restore")==0) + { + restore(); + return TRUE; + } + else if (stricmp(ibuff,"#quit")==0) + { + StopGame(); + printstring("\rGame Terminated\r"); + return TRUE; + } + else if (stricmp(ibuff,"#dictionary")==0) + { + CheatWord=0; + printstring("\r"); + while ((L9GameType==L9_V2) ? GetWordV2(ibuff,CheatWord++) : GetWordV3(ibuff,CheatWord++)) + { + error("%s ",ibuff); + if (os_stoplist() || !Running) break; + } + printstring("\r"); + return TRUE; + } + else if (strnicmp(ibuff,"#picture ",9)==0) + { + int pic = 0; + if (sscanf(ibuff+9,"%d",&pic) == 1) + show_picture(pic); + lastactualchar = 0; + printchar('\r'); + return TRUE; + } + return FALSE; +} + +L9BOOL corruptinginput(void) +{ + L9BYTE *a0,*a2,*a6; + int d0,d1,d2,keywordnumber,abrevword; + char *iptr; + + list9ptr=list9startptr; + + if (ibuffptr==NULL) + { + if (Cheating) NextCheat(); + else + { + /* flush */ + os_flush(); + lastchar='.'; + /* get input */ + if (!os_input(ibuff,IBUFFSIZE)) return FALSE; /* fall through */ + if (CheckHash()) return FALSE; + + /* check for invalid chars */ + for (iptr=ibuff;*iptr!=0;iptr++) + { + if (!isalnum(*iptr)) + *iptr=' '; + } + + /* force CR but prevent others */ + os_printchar(lastactualchar='\r'); + } + ibuffptr=(L9BYTE*) ibuff; + } + + a2=(L9BYTE*) obuff; + a6=ibuffptr; + +/*ip05 */ + while (TRUE) + { + d0=*a6++; + if (d0==0) + { + ibuffptr=NULL; + L9SETWORD(list9ptr,0); + return TRUE; + } + if (partword((char)d0)==0) break; + if (d0!=0x20) + { + ibuffptr=a6; + L9SETWORD(list9ptr,d0); + L9SETWORD(list9ptr+2,0); + *a2=0x20; + keywordnumber=-1; + return TRUE; + } + } + + a6--; +/*ip06loop */ + do + { + d0=*a6++; + if (partword((char)d0)==1) break; + d0=tolower(d0); + *a2++=d0; + } while (a2<(L9BYTE*) obuff+0x1f); +/*ip06a */ + *a2=0x20; + a6--; + ibuffptr=a6; + abrevword=-1; + keywordnumber=-1; + list9ptr=list9startptr; +/* setindex */ + a0=dictdata; + d2=dictdatalen; + d0=*obuff-0x61; + if (d0<0) + { + a6=defdict; + d1=0; + } + else + { + /*ip10 */ + d1=0x67; + if (d0<0x1a) + { + d1=d0<<2; + d0=obuff[1]; + if (d0!=0x20) d1+=((d0-0x61)>>3)&3; + } + /*ip13 */ + if (d1>=d2) + { + checknumber(); + return TRUE; + } + a0+=d1<<2; + a6=startdata+L9WORD(a0); + d1=L9WORD(a0+2); + } +/*ip13gotwordnumber */ + + initunpack(a6); +/*ip14 */ + d1--; + do + { + d1++; + if (unpackword()) + {/* ip21b */ + if (abrevword==-1) break; /* goto ip22 */ + else d0=abrevword; /* goto ip18b */ + } + else + { + L9BYTE* a1=(L9BYTE*) threechars; + int d6=-1; + + a0=(L9BYTE*) obuff; + /*ip15 */ + do + { + d6++; + d0=tolower(*a1++ & 0x7f); + d2=*a0++; + } while (d0==d2); + + if (d2!=0x20) + {/* ip17 */ + if (abrevword==-1) continue; + else d0=-1; + } + else if (d0==0) d0=d1; + else if (abrevword!=-1) break; + else if (d6>=4) d0=d1; + else + { + abrevword=d1; + continue; + } + } + /*ip18b */ + findmsgequiv(d1); + + abrevword=-1; + if (list9ptr!=list9startptr) + { + L9SETWORD(list9ptr,0); + return TRUE; + } + } while (TRUE); +/* ip22 */ + checknumber(); + return TRUE; +} + +/* version 2 stuff hacked from bbc v2 files */ + +L9BOOL GetWordV2(char *buff,int Word) +{ + L9BYTE *ptr=L9Pointers[1],x; + + while (Word--) + { + do + { + x=*ptr++; + } while (x>0 && x<0x7f); + if (x==0) return FALSE; /* no more words */ + ptr++; + } + do + { + x=*ptr++; + *buff++=x&0x7f; + } while (x>0 && x<0x7f); + *buff=0; + return TRUE; +} + +L9BOOL inputV2(int *wordcount) +{ + L9BYTE a,x; + L9BYTE *ibuffptr,*obuffptr,*ptr,*list0ptr; + char *iptr; + + if (Cheating) NextCheat(); + else + { + os_flush(); + lastchar='.'; + /* get input */ + if (!os_input(ibuff,IBUFFSIZE)) return FALSE; /* fall through */ + if (CheckHash()) return FALSE; + + /* check for invalid chars */ + for (iptr=ibuff;*iptr!=0;iptr++) + { + if (!isalnum(*iptr)) + *iptr=' '; + } + + /* force CR but prevent others */ + os_printchar(lastactualchar='\r'); + } + /* add space onto end */ + ibuffptr=(L9BYTE*) strchr(ibuff,0); + *ibuffptr++=32; + *ibuffptr=0; + + *wordcount=0; + ibuffptr=(L9BYTE*) ibuff; + obuffptr=(L9BYTE*) obuff; + /* ibuffptr=76,77 */ + /* obuffptr=84,85 */ + /* list0ptr=7c,7d */ + list0ptr=L9Pointers[1]; + + while (*ibuffptr==32) ++ibuffptr; + + ptr=ibuffptr; + do + { + while (*ptr==32) ++ptr; + if (*ptr==0) break; + (*wordcount)++; + do + { + a=*++ptr; + } while (a!=32 && a!=0); + } while (*ptr>0); + + while (TRUE) + { + ptr=ibuffptr; /* 7a,7b */ + while (*ibuffptr==32) ++ibuffptr; + + while (TRUE) + { + a=*ibuffptr; + x=*list0ptr++; + + if (a==32) break; + if (a==0) + { + *obuffptr++=0; + return TRUE; + } + + ++ibuffptr; + if (tolower(x&0x7f) != tolower(a)) + { + while (x>0 && x<0x7f) x=*list0ptr++; + if (x==0) + { + do + { + a=*ibuffptr++; + if (a==0) + { + *obuffptr=0; + return TRUE; + } + } while (a!=32); + while (*ibuffptr==32) ++ibuffptr; + list0ptr=L9Pointers[1]; + ptr=ibuffptr; + } + else + { + list0ptr++; + ibuffptr=ptr; + } + } + else if (x>=0x7f) break; + } + + a=*ibuffptr; + if (a!=32) + { + ibuffptr=ptr; + list0ptr+=2; + continue; + } + --list0ptr; + while (*list0ptr++<0x7e); + *obuffptr++=*list0ptr; + while (*ibuffptr==32) ++ibuffptr; + list0ptr=L9Pointers[1]; + } +} + +void input(void) +{ + /* if corruptinginput() returns false then, input will be called again + next time around instructionloop, this is used when save() and restore() + are called out of line */ + + codeptr--; + if (L9GameType==L9_V2) + { + int wordcount; + if (inputV2(&wordcount)) + { + L9BYTE *obuffptr=(L9BYTE*) obuff; + codeptr++; + *getvar()=*obuffptr++; + *getvar()=*obuffptr++; + *getvar()=*obuffptr; + *getvar()=wordcount; + } + } + else + if (corruptinginput()) codeptr+=5; +} + +void varcon(void) +{ + L9UINT16 d6=getcon(); + *getvar()=d6; + +#ifdef CODEFOLLOW + fprintf(f," Var[%d]=%d)",cfvar-workspace.vartable,*cfvar); +#endif +} + +void varvar(void) +{ + L9UINT16 d6=*getvar(); + *getvar()=d6; + +#ifdef CODEFOLLOW + fprintf(f," Var[%d]=Var[%d] (=%d)",cfvar-workspace.vartable,cfvar2-workspace.vartable,d6); +#endif +} + +void _add(void) +{ + L9UINT16 d0=*getvar(); + *getvar()+=d0; + +#ifdef CODEFOLLOW + fprintf(f," Var[%d]+=Var[%d] (+=%d)",cfvar-workspace.vartable,cfvar2-workspace.vartable,d0); +#endif +} + +void _sub(void) +{ + L9UINT16 d0=*getvar(); + *getvar()-=d0; + +#ifdef CODEFOLLOW + fprintf(f," Var[%d]-=Var[%d] (-=%d)",cfvar-workspace.vartable,cfvar2-workspace.vartable,d0); +#endif +} + +void jump(void) +{ + L9UINT16 d0=L9WORD(codeptr); + L9BYTE* a0; + codeptr+=2; + + a0=acodeptr+((d0+((*getvar())<<1))&0xffff); + codeptr=acodeptr+L9WORD(a0); +} + +/* bug */ +void exit1(L9BYTE *d4,L9BYTE *d5,L9BYTE d6,L9BYTE d7) +{ + L9BYTE* a0=absdatablock; + L9BYTE d1=d7,d0; + if (--d1) + { + do + { + d0=*a0; + a0+=2; + } + while ((d0&0x80)==0 || --d1); + } + + do + { + *d4=*a0++; + if (((*d4)&0xf)==d6) + { + *d5=*a0; + return; + } + a0++; + } + while (((*d4)&0x80)==0); + + /* notfn4 */ + d6=exitreversaltable[d6]; + a0=absdatablock; + *d5=1; + + do + { + *d4=*a0++; + if (((*d4)&0x10)==0 || ((*d4)&0xf)!=d6) a0++; + else if (*a0++==d7) return; + /* exit6noinc */ + if ((*d4)&0x80) (*d5)++; + } while (*d4); + *d5=0; +} + +void Exit(void) +{ + L9BYTE d4,d5; + L9BYTE d7=(L9BYTE) *getvar(); + L9BYTE d6=(L9BYTE) *getvar(); + exit1(&d4,&d5,d6,d7); + + *getvar()=(d4&0x70)>>4; + *getvar()=d5; +} + +void ifeqvt(void) +{ + L9UINT16 d0=*getvar(); + L9UINT16 d1=*getvar(); + L9BYTE* a0=getaddr(); + if (d0==d1) codeptr=a0; + +#ifdef CODEFOLLOW + fprintf(f," if Var[%d]=Var[%d] goto %d (%s)",cfvar2-workspace.vartable,cfvar-workspace.vartable,(L9UINT32) (a0-acodeptr),d0==d1 ? "Yes":"No"); +#endif +} + +void ifnevt(void) +{ + L9UINT16 d0=*getvar(); + L9UINT16 d1=*getvar(); + L9BYTE* a0=getaddr(); + if (d0!=d1) codeptr=a0; + +#ifdef CODEFOLLOW + fprintf(f," if Var[%d]!=Var[%d] goto %d (%s)",cfvar2-workspace.vartable,cfvar-workspace.vartable,(L9UINT32) (a0-acodeptr),d0!=d1 ? "Yes":"No"); +#endif +} + +void ifltvt(void) +{ + L9UINT16 d0=*getvar(); + L9UINT16 d1=*getvar(); + L9BYTE* a0=getaddr(); + if (d0d1) codeptr=a0; + +#ifdef CODEFOLLOW + fprintf(f," if Var[%d]>Var[%d] goto %d (%s)",cfvar2-workspace.vartable,cfvar-workspace.vartable,(L9UINT32) (a0-acodeptr),d0>d1 ? "Yes":"No"); +#endif +} + +int scalex(int x) +{ + return (L9GameType == L9_V2) ? x>>6 : x>>5; +} + +int scaley(int y) +{ + return 96 - (((y>>5)+(y>>6))>>3); +} + +void _screen(void) +{ + l9textmode = *codeptr++; + os_graphics(l9textmode && picturedata); + + screencalled = 1; + +#ifdef L9DEBUG + printf("screen %s",l9textmode ? "graphics" : "text"); +#endif + + if (l9textmode) + { + codeptr++; +/* clearg */ +/* gintclearg */ + os_cleargraphics(); + } +/* screent */ +} + +void cleartg(void) +{ + int d0 = *codeptr++; +#ifdef L9DEBUG + printf("cleartg %s",d0 ? "graphics" : "text"); +#endif + + if (d0) + { +/* clearg */ + if (l9textmode) +/* gintclearg */ + os_cleargraphics(); + } +/* cleart */ +/* oswrch(0x0c) */ +} + +L9BOOL validgfxptr(L9BYTE* a5) +{ + return ((a5 >= picturedata) && (a5 < picturedata+picturesize)); +} + +L9BOOL findsub(int d0,L9BYTE** a5) +{ + int d1,d2,d3,d4; + + d1=d0 << 4; + d2=d1 >> 8; + *a5=picturedata; +/* findsubloop */ + while (TRUE) + { + d3=*(*a5)++; + if (!validgfxptr(*a5)) + return FALSE; + if (d3&0x80) + return FALSE; + if (d2==d3) + { + if ((d1&0xff)==(*(*a5) & 0xf0)) + { + (*a5)+=2; + return TRUE; + } + } + + d3=*(*a5)++ & 0x0f; + if (!validgfxptr(*a5)) + return FALSE; + + d4=**a5; + if ((d3|d4)==0) + return FALSE; + + (*a5)+=(d3<<8) + d4 - 2; + if (!validgfxptr(*a5)) + return FALSE; + } +} + +L9BOOL checksubs(void) +{ + L9BYTE* a5; + int i,cnt=0; + + if (picturedata[0]!=0 || picturedata[1]!=0) + return FALSE; + + for (i = 1; i < 50; i++) + { + if (findsub(i,&a5)) + cnt++; + } + return (cnt > 30); +} + +void gosubd0(int d0, L9BYTE** a5) +{ + if (GfxStackPos < GFXSTACKSIZE) + { + GfxStack[GfxStackPos].a5 = *a5; + GfxStack[GfxStackPos].scale = scale; + GfxStackPos++; + + if (findsub(d0,a5) == FALSE) + { + GfxStackPos--; + *a5 = GfxStack[GfxStackPos].a5; + scale = GfxStack[GfxStackPos].scale; + } + } +} + +void newxy(int x, int y) +{ + drawx += (x*scale)&~7; + drawy += (y*scale)&~7; +} + +/* sdraw instruction plus arguments are stored in an 8 bit word. + 76543210 + iixxxyyy + where i is instruction code + x is x argument, high bit is sign + y is y argument, high bit is sign +*/ +void sdraw(int d7) +{ + int x,y,x1,y1; + +/* getxy1 */ + x = (d7&0x18)>>3; + if (d7&0x20) + x = (x|0xfc) - 0x100; + y = (d7&0x3)<<2; + if (d7&0x4) + y = (y|0xf0) - 0x100; + + if (reflectflag&2) + x = -x; + if (reflectflag&1) + y = -y; + +/* gintline */ + x1 = drawx; + y1 = drawy; + newxy(x,y); + +#ifdef L9DEBUG + printf("gfx - sdraw (%d,%d) (%d,%d) colours %d,%d", + x1,y1,drawx,drawy,gintcolour&3,option&3); +#endif + + os_drawline(scalex(x1),scaley(y1),scalex(drawx),scaley(drawy), + gintcolour&3,option&3); +} + +/* smove instruction plus arguments are stored in an 8 bit word. + 76543210 + iixxxyyy + where i is instruction code + x is x argument, high bit is sign + y is y argument, high bit is sign +*/ +void smove(int d7) +{ + int x,y; + +/* getxy1 */ + x = (d7&0x18)>>3; + if (d7&0x20) + x = (x|0xfc) - 0x100; + y = (d7&0x3)<<2; + if (d7&0x4) + y = (y|0xf0) - 0x100; + + if (reflectflag&2) + x = -x; + if (reflectflag&1) + y = -y; + newxy(x,y); +} + +void sgosub(int d7, L9BYTE** a5) +{ + int d0 = d7&0x3f; +#ifdef L9DEBUG + printf("gfx - sgosub 0x%.2x",d0); +#endif + gosubd0(d0,a5); +} + +/* draw instruction plus arguments are stored in a 16 bit word. + FEDCBA9876543210 + iiiiixxxxxxyyyyy + where i is instruction code + x is x argument, high bit is sign + y is y argument, high bit is sign +*/ +void draw(int d7, L9BYTE** a5) +{ + int xy,x,y,x1,y1; + +/* getxy2 */ + xy = (d7<<8)+(*(*a5)++); + x = (xy&0x3e0)>>5; + if (xy&0x400) + x = (x|0xe0) - 0x100; + y = (xy&0xf)<<2; + if (xy&0x10) + y = (y|0xc0) - 0x100; + + if (reflectflag&2) + x = -x; + if (reflectflag&1) + y = -y; + +/* gintline */ + x1 = drawx; + y1 = drawy; + newxy(x,y); + +#ifdef L9DEBUG + printf("gfx - draw (%d,%d) (%d,%d) colours %d,%d", + x1,y1,drawx,drawy,gintcolour&3,option&3); +#endif + + os_drawline(scalex(x1),scaley(y1),scalex(drawx),scaley(drawy), + gintcolour&3,option&3); +} + +/* move instruction plus arguments are stored in a 16 bit word. + FEDCBA9876543210 + iiiiixxxxxxyyyyy + where i is instruction code + x is x argument, high bit is sign + y is y argument, high bit is sign +*/ +void _move(int d7, L9BYTE** a5) +{ + int xy,x,y; + +/* getxy2 */ + xy = (d7<<8)+(*(*a5)++); + x = (xy&0x3e0)>>5; + if (xy&0x400) + x = (x|0xe0) - 0x100; + y = (xy&0xf)<<2; + if (xy&0x10) + y = (y|0xc0) - 0x100; + + if (reflectflag&2) + x = -x; + if (reflectflag&1) + y = -y; + newxy(x,y); +} + +void icolour(int d7) +{ + gintcolour = d7&3; +#ifdef L9DEBUG + printf("gfx - icolour 0x%.2x",gintcolour); +#endif +} + +void size(int d7) +{ + static int sizetable[7] = { 0x02,0x04,0x06,0x07,0x09,0x0c,0x10 }; + + d7 &= 7; + if (d7) + { + int d0 = (scale*sizetable[d7-1])>>3; + scale = (d0 < 0x100) ? d0 : 0xff; + } + else +/* sizereset */ + scale = 0x80; + +#ifdef L9DEBUG + printf("gfx - size 0x%.2x",scale); +#endif +} + +void gintfill(int d7) +{ + if ((d7&7) == 0) +/* filla */ + d7 = gintcolour; + else + d7 &= 3; +/* fillb */ + +#ifdef L9DEBUG + printf("gfx - gintfill (%d,%d) colours %d,%d",drawx,drawy,d7&3,option&3); +#endif + + os_fill(scalex(drawx),scaley(drawy),d7&3,option&3); +} + +void gosub(int d7, L9BYTE** a5) +{ + int d0 = ((d7&7)<<8)+(*(*a5)++); +#ifdef L9DEBUG + printf("gfx - gosub 0x%.2x",d0); +#endif + gosubd0(d0,a5); +} + +void reflect(int d7) +{ +#ifdef L9DEBUG + printf("gfx - reflect 0x%.2x",d7); +#endif + + if (d7&4) + { + d7 &= 3; + d7 ^= reflectflag; + } +/* reflect1 */ + reflectflag = d7; +} + +void notimp(void) +{ +#ifdef L9DEBUG + printf("gfx - notimp"); +#endif +} + +void gintchgcol(L9BYTE** a5) +{ + int d0 = *(*a5)++; + +#ifdef L9DEBUG + printf("gfx - gintchgcol %d %d",(d0>>3)&3,d0&7); +#endif + + os_setcolour((d0>>3)&3,d0&7); +} + +void amove(L9BYTE** a5) +{ + drawx = 0x40*(*(*a5)++); + drawy = 0x40*(*(*a5)++); +#ifdef L9DEBUG + printf("gfx - amove (%d,%d)",drawx,drawy); +#endif +} + +void opt(L9BYTE** a5) +{ + int d0 = *(*a5)++; +#ifdef L9DEBUG + printf("gfx - opt 0x%.2x",d0); +#endif + + if (d0) + d0 = (d0&3)|0x80; +/* optend */ + option = d0; +} + +void restorescale(void) +{ +#ifdef L9DEBUG + printf("gfx - restorescale"); +#endif + if (GfxStackPos > 0) + scale = GfxStack[GfxStackPos-1].scale; +} + +L9BOOL rts(L9BYTE** a5) +{ + if (GfxStackPos > 0) + { + GfxStackPos--; + *a5 = GfxStack[GfxStackPos].a5; + scale = GfxStack[GfxStackPos].scale; + return TRUE; + } + return FALSE; +} + +L9BOOL getinstruction(L9BYTE** a5) +{ + int d7 = *(*a5)++; + if ((d7&0xc0) != 0xc0) + { + switch ((d7>>6)&3) + { + case 0: sdraw(d7); break; + case 1: smove(d7); break; + case 2: sgosub(d7,a5); break; + } + } + else if ((d7&0x38) != 0x38) + { + switch ((d7>>3)&7) + { + case 0: draw(d7,a5); break; + case 1: _move(d7,a5); break; + case 2: icolour(d7); break; + case 3: size(d7); break; + case 4: gintfill(d7); break; + case 5: gosub(d7,a5); break; + case 6: reflect(d7); break; + } + } + else + { + switch (d7&7) + { + case 0: notimp(); break; + case 1: gintchgcol(a5); break; + case 2: notimp(); break; + case 3: amove(a5); break; + case 4: opt(a5); break; + case 5: restorescale(); break; + case 6: notimp(); break; + case 7: return rts(a5); + } + } + return TRUE; +} + +void absrunsub(int d0) +{ + L9BYTE* a5; + if (!findsub(d0,&a5)) + return; + while (getinstruction(&a5)); +} + +void show_picture(int pic) +{ + if (picturedata) + { + /* Some games don't call the screen() opcode before drawing + graphics, so here graphics are enabled if necessary. */ + if ((screencalled == 0) && (l9textmode == 0)) + { + l9textmode = 1; + os_graphics(1); + } + +#ifdef L9DEBUG + printf("picture %d",pic); +#endif + + os_cleargraphics(); +/* gintinit */ + gintcolour = 3; + option = 0x80; + reflectflag = 0; + drawx = 0x1400; + drawy = 0x1400; +/* sizereset */ + scale = 0x80; + + GfxStackPos=0; + absrunsub(0); + if (!findsub(pic,&gfxa5)) + gfxa5 = NULL; + } +} + +void picture(void) +{ + show_picture(*getvar()); +} + +void GetPictureSize(int* width, int* height) +{ + if (width != NULL) + *width = (L9GameType == L9_V2) ? 160 : 320; + if (height != NULL) + *height = 96; +} + +L9BOOL RunGraphics(void) +{ + if (gfxa5) + { + if (!getinstruction(&gfxa5)) + gfxa5 = NULL; + return TRUE; + } + return FALSE; +} + +void initgetobj(void) +{ + int i; + numobjectfound=0; + object=0; + for (i=0;i<32;i++) gnoscratch[i]=0; +} + +void getnextobject(void) +{ + int d2,d3,d4; + L9UINT16 *hisearchposvar,*searchposvar; + +#ifdef L9DEBUG + printf("getnextobject"); +#endif + + d2=*getvar(); + hisearchposvar=getvar(); + searchposvar=getvar(); + d3=*hisearchposvar; + d4=*searchposvar; + +/* gnoabs */ + do + { + if ((d3 | d4)==0) + { + /* initgetobjsp */ + gnosp=128; + searchdepth=0; + initgetobj(); + break; + } + + if (numobjectfound==0) inithisearchpos=d3; + + /* gnonext */ + do + { + if (d4==list2ptr[++object]) + { + /* gnomaybefound */ + int d6=list3ptr[object]&0x1f; + if (d6!=d3) + { + if (d6==0 || d3==0) continue; + if (d3!=0x1f) + { + gnoscratch[d6]=d6; + continue; + } + d3=d6; + } + /* gnofound */ + numobjectfound++; + gnostack[--gnosp]=object; + gnostack[--gnosp]=0x1f; + + *hisearchposvar=d3; + *searchposvar=d4; + *getvar()=object; + *getvar()=numobjectfound; + *getvar()=searchdepth; + return; + } + } while (object<=d2); + + if (inithisearchpos==0x1f) + { + gnoscratch[d3]=0; + d3=0; + + /* gnoloop */ + do + { + if (gnoscratch[d3]) + { + gnostack[--gnosp]=d4; + gnostack[--gnosp]=d3; + } + } while (++d3<0x1f); + } + /* gnonewlevel */ + if (gnosp!=128) + { + d3=gnostack[gnosp++]; + d4=gnostack[gnosp++]; + } + else d3=d4=0; + + numobjectfound=0; + if (d3==0x1f) searchdepth++; + + initgetobj(); + } while (d4); + +/* gnofinish */ +/* gnoreturnargs */ + *hisearchposvar=0; + *searchposvar=0; + *getvar()=object=0; + *getvar()=numobjectfound; + *getvar()=searchdepth; +} + +void ifeqct(void) +{ + L9UINT16 d0=*getvar(); + L9UINT16 d1=getcon(); + L9BYTE* a0=getaddr(); + if (d0==d1) codeptr=a0; +#ifdef CODEFOLLOW + fprintf(f," if Var[%d]=%d goto %d (%s)",cfvar-workspace.vartable,d1,(L9UINT32) (a0-acodeptr),d0==d1 ? "Yes":"No"); +#endif +} + +void ifnect(void) +{ + L9UINT16 d0=*getvar(); + L9UINT16 d1=getcon(); + L9BYTE* a0=getaddr(); + if (d0!=d1) codeptr=a0; +#ifdef CODEFOLLOW + fprintf(f," if Var[%d]!=%d goto %d (%s)",cfvar-workspace.vartable,d1,(L9UINT32) (a0-acodeptr),d0!=d1 ? "Yes":"No"); +#endif +} + +void ifltct(void) +{ + L9UINT16 d0=*getvar(); + L9UINT16 d1=getcon(); + L9BYTE* a0=getaddr(); + if (d0d1) codeptr=a0; +#ifdef CODEFOLLOW + fprintf(f," if Var[%d]>%d goto %d (%s)",cfvar-workspace.vartable,d1,(L9UINT32) (a0-acodeptr),d0>d1 ? "Yes":"No"); +#endif +} + +void printinput(void) +{ + L9BYTE* ptr=(L9BYTE*) obuff; + char c; + while ((c=*ptr++)!=' ') printchar(c); + +#ifdef L9DEBUG + printf("printinput"); +#endif +} + +void listhandler(void) +{ + L9BYTE *a4,*MinAccess,*MaxAccess; + L9UINT16 val; + L9UINT16 *var; +#ifdef CODEFOLLOW + int offset; +#endif + + if ((code&0x1f)>0xa) + { + error("\rillegal list access %d\r",code&0x1f); + Running=FALSE; + return; + } + a4=L9Pointers[1+code&0x1f]; + + if (a4>=workspace.listarea && a4=0xe0) + { + /* listvv */ +#ifndef CODEFOLLOW + a4+=*getvar(); + val=*getvar(); +#else + offset=*getvar(); + a4+=offset; + var=getvar(); + val=*var; + fprintf(f," list %d [%d]=Var[%d] (=%d)",code&0x1f,offset,var-workspace.vartable,val); +#endif + + if (a4>=MinAccess && a4=0xc0) + { + /* listv1c */ +#ifndef CODEFOLLOW + a4+=*codeptr++; + var=getvar(); +#else + offset=*codeptr++; + a4+=offset; + var=getvar(); + fprintf(f," Var[%d]= list %d [%d])",var-workspace.vartable,code&0x1f,offset); + if (a4>=MinAccess && a4=MinAccess && a4=0xa0) + { + /* listv1v */ +#ifndef CODEFOLLOW + a4+=*getvar(); + var=getvar(); +#else + offset=*getvar(); + a4+=offset; + var=getvar(); + + fprintf(f," Var[%d] =list %d [%d]",var-workspace.vartable,code&0x1f,offset); + if (a4>=MinAccess && a4=MinAccess && a4=MinAccess && a4>8); + #define L9SETDWORD(x,val) *(x)=(L9BYTE)val; *(x+1)=(L9BYTE)(val>>8); *(x+2)=(L9BYTE)(val>>16); *(x+3)=(L9BYTE)(val>>24); +#endif + +#if defined(_Windows) && !defined(__WIN32__) +#include +#define malloc farmalloc +#define calloc farcalloc +#define free farfree +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* routines provided by os dependent code */ +void os_printchar(char c); +L9BOOL os_input(char* ibuff, int size); +char os_readchar(int millis); +L9BOOL os_stoplist(void); +void os_flush(void); +L9BOOL os_save_file(L9BYTE* Ptr, int Bytes); +L9BOOL os_load_file(L9BYTE* Ptr, int* Bytes, int Max); +L9BOOL os_get_game_file(char* NewName, int Size); +void os_set_filenumber(char* NewName, int Size, int n); +void os_graphics(int mode); +void os_cleargraphics(void); +void os_setcolour(int colour, int index); +void os_drawline(int x1, int y1, int x2, int y2, int colour1, int colour2); +void os_fill(int x, int y, int colour1, int colour2); + +/* routines provided by level9 interpreter */ +L9BOOL LoadGame(char* filename, char* picname); +L9BOOL RunGame(void); +void StopGame(void); +void RestoreGame(char* filename); +void FreeMemory(void); +void GetPictureSize(int* width, int* height); +L9BOOL RunGraphics(void); + +#ifdef NEED_STRICMP_PROTOTYPE +int stricmp(const char* str1, const char* str2); +int strnicmp(const char* str1, const char* str2, size_t n); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/level9.txt b/level9.txt new file mode 100755 index 0000000..a77d6cf --- /dev/null +++ b/level9.txt @@ -0,0 +1,225 @@ + + Level 9 Interpreter v3.0 + An interpreter for Level 9 games in any format, + including Spectrum snapshots. + + Copyright (c) 1996 Glen Summers + Copyright (c) 2002 Glen Summers and David Kinder + + Level9 is released under the terms of the GNU General + Public License. See the file COPYING that is included + with this program for details. + + +Introduction +------------ + +During the 1980s a small British company called Level 9, run by two brothers +(Mike and Pete Austin), produced a series of text adventure games for a +variety of computers. These games received considerable critical acclaim and +are probably the best text adventures written for the small cassette based 8 +bit computers common in Britain in the 80s. + +Level 9 wrote their games using a custom designed system known as "A-Code", +which evolved from games on the 8 bit computers such as the Acorn BBC Model +B, the Sinclair Spectrum and the Commodore 64 to the (then new) 16 bit +machines such as the Amiga and the Atari ST. + +From disassembly of Level 9 games there are thought to be four variants of +A-Code, which are detailed below. Thanks go to Paul David Doherty for +analysing the games and producing the table which follows. At present this +interpreter supports v2, v3 and v4 games. + + v1 This was used for the earliest games. Spectrum v1 games had + black text on a grey background. Games known to be released in + this format: + + Colossal Adventure + Adventure Quest + Dungeon Adventure + Snowball + Lords of Time + + v2 These releases were made between 1984 and 1985 (and usually say + so in the initial copyright message). This version introduced + the yellow text on a black background which became standard. + Games in this format were: + + Adventure Quest + Dungeon Adventure + Lords of Time + Snowball + Return to Eden + Erik the Viking + Emerald Isle + Red Moon + + v3 This format, dated 1986, was used by the largest number of + releases. These were: + + Worm in Paradise + The Price of Magick + The Secret Diary of Adrian Mole + The Growing Pains of Adrian Mole + The Archers + The Jewels of Darkness Trilogy (Colossal Adventure, + Adventure Quest and Dungeon Adventure released as + one package) + The Silicon Dreams Trilogy + (Snowball, Return to Eden and Worm in Paradise) + + v4 This was used for the Time and Magick Trilogy (Lords of Time, + Red Moon and The Price of Magick), and all Level 9's new games + from 1987 onwards: + + Knight Orc + Gnome Ranger + Ingrid's Back + Lancelot + Scapeghost + + +Supported Formats +----------------- + +On several machines (such as the Amiga) Level 9 games were distributed as +an interpreter plus a data file, usually called "gamedata.dat" or something +similar. These games can be played with this interpreter simply by loading +the "gamedata.dat" file. + +For the Amiga (and possibly some other formats) the v4 games were released +in three parts, each in a separate data file: + + gamedat1.dat + gamedat2.dat + gamedat3.dat + +Starting the first game gives a menu from which you can choose which part or +game to play. For this to work the files must have the same basic structure +with a number in it somewhere, e.g. file names + + TimeAndMagick1.dat + TimeAndMagick2.dat + TimeAndMagick3.dat + +will work. + +On other (especially older and smaller) computers the games were distributed +as a single file containing both an interpreter and the game data. Level9 +can cope with these files as well, as it automatically searches files for +valid Level 9 games. This however requires that the file not be compressed +in any way. For example, there are several Spectrum snapshots of Level 9 +games available which this interpreter can play, but these snapshots must +be in an uncompressed format (e.g. SNA). Commonly snapshots are available in +the compressed Z80 format, but these files can be converted to SNA using the +widely available conversion program "SPConv". Version 1.06 or higher of +"SPConv" is recommended. + +Paul David Doherty has written "L9Cut", which can extract Level 9 games from +many different formats. L9Cut can also remove copy protection from Level 9 +games. L9Cut can be downloaded from + + ftp://ftp.ifarchive.org/if-archive/level9/tools/ + +This program has been tested on files obtained from releases for the Amiga, +Atari ST, IBM PC, C-64, Spectrum, Atari 800, BBC, Amstrad CPC, Apple 2 and +MSX computers. + + +Graphics +-------- + +The first games from Level 9 were text only, but later games included line +drawn graphics. These graphics are supported by some versions of the +interpreter (The 32-bit DOS version does support graphics, while the 16-bit +DOS version does not.). + +One some platforms these graphics were placed into separate files (For +example, in the Amiga release of "Jewels of Darkness" the game is in +"gamedata.dat" and the graphics in "picture.dat".). In order to show the +pictures the graphics file can be passed as a second command line argument +when starting the interpreter. If no graphics file is specified in this +way, the interpreter will look for a file with the same name as the game +data, but with a file extension of ".pic", ".cga" or ".hrc" (the latter +two being used for graphics files in MS-DOS Level 9 releases). + +If no graphics file is specified, Level 9 will search the given game data +file for graphics data. This is useful when the game data is a Spectrum +SNA snapshot or a similar memory dump of an emulator. + +Later Level 9 games used bitmap graphics. As yet these are not supported by +this interpreter. + + +Meta Commands +------------- + +Level9 supports several meta commands, which can be entered on the input +line. These commands are handled by the interpreter rather than being passed +to the game. They are: + + #save Saves out a game position directly, bypassing any + prompting (such as for disk changes). + + #restore Loads in a saved position directly, bypassing any + protection code within the game. + + #quit Quits the current game. + + #cheat Tries to bypass the copy protection code which asks for + a specific word. This is done by trying every word in + the game's dictionary. On a slow machine, this can take + a long time. + + #dictionary Lists the game dictionary. Press a key to stop the + listing and return to the input line. Note that the v2 + dictionary appears to have random characters following + on the end: The original interpreter code to detect the + end of dictionary does not appear to agree with the + characters actually at the end. + + #picture If graphics are available, shows the picture specified + as a number after the picture command, e.g. "#picture 520". + +The 32-bit DOS version of Level 9 also supports several hotkeys. Press +Alt-H when playing a game to view a list of the available hotkeys. + + +History +------- + + v3.0 Implemented support for line-drawn graphics. + Fixed a problem with v4 detection that stopped some versions of + Scapeghost running correctly. + Implemented an opcode used by the disk based versions of + Time and Magick, which now means that saving from these games + works. + The interpreter now gives you a reasonable amount of time to + enter the Lenslok code in protected games, and also tells you + what the code should be. + + v2.0 Revised the description of v4 games to include all the post-1987 + games, which are now supported. + v2 games are also now supported. + Added meta commands. + + v1.0 First release. + + +Credits +------- + +The Level9 interpreter was originally written by Glen Summers. The changes for +3.0 were made by David Kinder (d.kinder@btinternet.com) with additional code +from Alan Staniforth and Simon Baldwin. Help, testing and information on the +various Level 9 formats was provided by Paul David Doherty. + + +The Interactive Fiction Archive +------------------------------- + +If you have access to the Internet and are interested in text adventures, +then you can find all sorts of programs and information at The Interactive +Fiction Archive, at the ftp site ftp.ifarchive.org, in the /if-archive/ +directory. The latest version of this program can always be found here. + diff --git a/porting.txt b/porting.txt new file mode 100755 index 0000000..e26a8ea --- /dev/null +++ b/porting.txt @@ -0,0 +1,322 @@ + +Level 9 interpreter +Version 3.0 +Copyright (c) 1996 Glen Summers +Copyright (c) 2002 Glen Summers and David Kinder + +This guide by David Kinder (d.kinder@btinternet.com) + + +Level9 has already been compiled on DOS, Windows, Amiga, Acorn and Macintosh +systems, so it should be quite easy to get it working on any other modern +computer. + + +The first thing that must be done is to check the typedefs in level9.h. +The typedefs must have the following properties: + + L9BYTE unsigned 8 bit quantity + L9UINT16 unsigned 16 bit quantity + L9UINT32 unsigned 32 bit quantity + L9BOOL quantity capable of holding the values + TRUE (1) and FALSE (0) + +Also the define MAX_PATH (the maximum length of the full pathname of a file) +must be set, e.g. + + #define MAX_PATH 256 + +If graphics are not supported defining NO_SCAN_GRAPHICS will stop the +intialization code from looking for graphics data, which may take a noticeable +length of time on slower computers. + + +It is required that several os_ functions be written for your system. Given +below is a guide to these functions, and a very simple interface is included +in generic.c. + + +void os_printchar(char c) + + os_printchar() prints a character to the output. The interface + can either buffer this character or print it immediately, but + if buffering is used then the characters must all be sent to the + output when the interpreter calls os_flush(). A paragraph of + text is output as one long stream of characters, without line + breaks, so the interface must provide its own word wrapping and + any other features that are desired, such as justification or a + [More] prompt. The carriage return character is always '\r', + rather than '\n'. + + +L9BOOL os_input (char* ibuff, int size) + + os_input() reads a line of text from the user, usually to accept + the next command to be sent to the game. The text input must be + stored in ibuff with a terminating zero, and be no longer than + size characters. Normally os_input() should return TRUE, but may + return FALSE to cause the entire input so far to be discarded. + The reason for doing so is discussed in the section at the end + on allowing the interpreter to load a new game without exiting. + + +char os_readchar (L9UINT32 millis) + + os_readchar() looks to see if a key has been pressed if one has, + returns the character to the interpreter immediately. If no key + has been pressed the interpreter should wait for a key for at + least the number of milliseconds given in the argument. If after + this period no key has been pressed, 0 should be returned. This + is most commonly used when a game is exited, causing it to print + "Press SPACE to play again" and then call os_readchar(). + + +L9BOOL os_stoplist(void) + + Called during dictionary listing. If true is returned (typically + because the user has pressed a key) then the listing is stopped. + This routine should return immediately, without waiting. If this + is not possible then FALSE should be returned. + + +void os_flush (void) + + If the calls to os_printchar() are being buffered by the + interface then the buffered text must be printed when os_flush() + is called. + + +L9BOOL os_save_file (L9BYTE* Ptr, int Bytes) + + os_save_file() should prompt the user in some way (with either + text or a file requester) for a filename to save the area of + memory of size Bytes pointed to by Ptr. TRUE or FALSE should be + returned depending on whether or not the operation was successful. + + +L9BOOL os_load_file (L9BYTE* Ptr, int* Bytes, int Max) + + os_load_file() should prompt the user for the name of a file to + load. At most Max bytes should be loaded into the memory pointed + to by Ptr, and the number of bytes read should be placed into the + variable pointed to by Bytes. + + +L9BOOL os_get_game_file(char* NewName, int Size) + + os_get_game_file() should prompt the user for a new game file, to + be stored in NewName, which can take a maximum name of Size + characters. When this function is called the NewName array + contains the name of the currently loaded game, which can be used + to derive a name to prompt the user with. + + This is used by at least the Adrian Mole games, which load in the + next part of the game after the part currently being played has + been completed. These games were originally written for tape-based + systems where the call was simply "load the next game from the + tape". + + +void os_set_filenumber(char* NewName, int Size, int n) + + os_set_filename() is for multi-part games originally written for + disk-based systems, which used game filenames such as + + gamedat1.dat + gamedat2.dat + + etc. The routine should take the full filename in NewName (of + maximum size Size) and modify it to reflect the number n, e.g. + os_set_filename("gamedat1.dat",2) should leave "gamedat2.dat" + in NewName. + + +void os_graphics(int mode) + + Called when graphics are turned on or off, either by the game or + by the user entering "graphics" or "text" as a command. If mode + is 0 graphics should be turned off, else they should be turned on. + After a os_graphics(0) call all the other graphics functions + should do nothing. + + Typically, if mode is not 0 the code will allocate some suitable + bitmap for drawing graphics into. To determine the size of the + bitmap, the code should call GetPictureSize(). The graphics + routines should draw in a bitmap of the size returned by this + function, and then scale the bitmap appropriately for display. + If instead the graphics code tries to scale the co-ordinates + passed to os_drawline() and os_fill() then problems occur with + fill colours "leaking" into other areas of the picture. The + values returned by GetPictureSize() will not change unless a new + game is loaded. + + The graphis bitmap is always 4 colour. The 4 colours are chosen + from a possible 8 by calls to os_setcolour (see below). Note that + a call to os_setcolour() must affect the colour of pixels already + drawn on the bitmap. For example, suppose a pixel in the bitmap + is set to the first colour in the palette during drawing, which at + that moment is red. If later the first colour in the palette is + set to blue, at the end the pixel should be shown blue. + + In order to actually draw graphics, the input routines os_input() + and os_readchar() should call RunGraphics(). This is discussed + further below. + + +void os_cleargraphics(void) + + Clear the current graphics bitmap by filling the entire bitmap + with colour 0. + + +void os_setcolour(int colour, int index) + + Set the given colour in the graphics bitmap's palette to the + colour at the given index in the interpreter's table of colours. + + The actual table of colours in the interpreters provided by + Level 9 vary across different machines. An acceptable palette + that matches reasonably closely to the Amiga releases is as + follows (all colours are 8 bit R,G,B): + + 0x00,0x00,0x00 (black) + 0xFF,0x00,0x00 (red) + 0x30,0xE8,0x30 (green) + 0xFF,0xFF,0x00 (yellow) + 0x00,0x00,0xFF (blue) + 0xA0,0x68,0x00 (brown) + 0x00,0xFF,0xFF (cyan) + 0xFF,0xFF,0xFF (white) + + +void os_drawline(int x1, int y1, int x2, int y2, int colour1, int colour2) + + Draw a line on the graphics bitmap between (x1,y1) and (x2,y2). + Note that either point may lie outside of the bitmap, and that it + is the responsibility of the routine to clip to the appropriate + co-ordinates. + + For each point on the line, if the colour at that point is equal + to colour2 the pixel's colour should be changed to colour1, else + it should not be modified. + + +void os_fill(int x, int y, int colour1, int colour2) + + If the pixel's colour at (x,y) is equal to colour2, fill the + region containing (x,y) with colour1. The boundaries of the + region are defined as those areas of the bitmap with a colour + other than colour2. + + +int main (int argc, char** argv) + + You must provide your own main() entry point for the program. + The simplest such main() is given in generic.c, which just calls + LoadGame() and then sits in a loop calling RunGame(). These + functions are discussed below. + + +The interpreter provides several functions to be called by the interface +code. These are: + + +L9BOOL LoadGame (char* filename, char* picname) + + LoadGame() attempts to load filename and then searches it for + a valid Level 9 game. If it is successful TRUE is returned, else + FALSE. The previous game in memory will be overwritten if the + file filename can be loaded, even if it does not contain a Level 9 + game, so even if LoadGame() returns FALSE it must be assumed that + the game memory has changed. + + The second argument is the name of the file containing picture + data, and may be NULL. Ports should usually ask the user for just + the filename and derive picname from it in some way. The + recommended approach is to first try the filename with an extension + of ".pic" and then try replacing the filename with "picture.dat". + + +L9BOOL RunGame (void) + + If LoadGame() has been successful, RunGame() can be called to run + the Level 9 game. Each call to RunGame() executes a single opcode + of the game. In pre-emptive multitasking systems or systems without + any multitasking it is enough to sit in a loop calling RunGame(), + e.g. + while (RunGame()); + + RunGame() returns TRUE if an opcode code was executed and FALSE if + the game is stopped, either by an error or by a call to StopGame(). + + +void StopGame (void) + + StopGame() stops the current game from playing. + + +void RestoreGame(char *inFile) + + RestoreGame() attempts to restore the currently running game to + the position stored in the inFile saved game file. This gives + interface code a means to restore a game position. + + +void FreeMemory (void) + + FreeMemory() frees memory used to store the game. This routine + should be called when exiting the interpreter. + + +void GetPictureSize(int* width, int* height) + + Returns the width and height of the bitmap that graphics should + be drawn into. This is constant for any particular game. + + +L9BOOL RunGraphics(void) + + Runs an opcode of the graphics routines. The simplest way to + display graphics. If a graphics opcode was run TRUE is returned, + otherwise FALSE. + + The simplest way to get graphics to display is to add a loop to + repeatedly call RunGraphics() to os_input() and os_readchar(): + + while (RunGraphics()); + /* Now draw graphics bitmap on display... */ + + Optionally, the code can provide a more "atmospheric" recreation + of the games by drawing the graphics slowly, as was the case on + the old 8-bit computers. This is achieved by calling RunGraphics() + several times then waiting for a while before calling it again. + Note that when waiting the code should still respond to user + input. + + +One more complex feature of the interpreter is that a new Level 9 game can +be loaded without exiting and restarting the interpreter. This is of use +in a windowing environment. In this case, both main() and the code that +catches a "New Game" menu item should call a routine such as the example +new_game() below. This ensures that each new game does not use up more and +more of the interpreter's stack. + +int newgame(char* game_name) +{ + static int playing = FALSE; + + if (LoadGame(game,NULL)) + { + if (playing) + return -1; + + playing = TRUE; + while (RunGame()); + playing = FALSE; + } + else + warn("Unable to load game"); + + return -1; +} +