diff options
author | Paul Gilbert | 2019-05-11 16:03:14 +1000 |
---|---|---|
committer | Paul Gilbert | 2019-05-11 16:03:14 +1000 |
commit | 34122d2f47b4a80a26ea4361a35cba50b5ab5cc0 (patch) | |
tree | 2df36fe3b10ce7e9cadd25ef358007646b375936 /engines/glk | |
parent | 1bbfcca229a3aa39854d495b0ce497d958d37b2e (diff) | |
download | scummvm-rg350-34122d2f47b4a80a26ea4361a35cba50b5ab5cc0.tar.gz scummvm-rg350-34122d2f47b4a80a26ea4361a35cba50b5ab5cc0.tar.bz2 scummvm-rg350-34122d2f47b4a80a26ea4361a35cba50b5ab5cc0.zip |
GLK: HUGO: Added herun
Diffstat (limited to 'engines/glk')
-rw-r--r-- | engines/glk/hugo/herun.cpp | 2810 | ||||
-rw-r--r-- | engines/glk/hugo/hugo.cpp | 9 | ||||
-rw-r--r-- | engines/glk/hugo/hugo.h | 193 | ||||
-rw-r--r-- | engines/glk/hugo/hugo_defines.h | 18 | ||||
-rw-r--r-- | engines/glk/hugo/hugo_types.h | 20 | ||||
-rw-r--r-- | engines/glk/module.mk | 1 |
6 files changed, 3027 insertions, 24 deletions
diff --git a/engines/glk/hugo/herun.cpp b/engines/glk/hugo/herun.cpp new file mode 100644 index 0000000000..43837977d5 --- /dev/null +++ b/engines/glk/hugo/herun.cpp @@ -0,0 +1,2810 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/hugo/hugo.h" + +namespace Glk { +namespace Hugo { + +void Hugo::RunDo() { + long skip, enterptr; + + enterptr = ++codeptr; + skip = PeekWord(codeptr); /* remember the skip distance */ + codeptr+=2; + + SetStackFrame(stack_depth, DOWHILE_BLOCK, skip+enterptr, codeptr); + +#if defined (DEBUGGER) + dbnest++; +#endif +} + +void Hugo::RunEvents() { + int i, tempundo, flag, temp_ret; + int eventin, tempself; + int templocals[MAXLOCALS]; + int temp_stack_depth; + int temp_parse_location; + long tempptr, eventaddr; +#if defined (DEBUGGER) + int tempdbnest; +#endif + + tempundo = undorecord; + undorecord = true; + + tempptr = codeptr; + tempself = var[self]; + temp_ret = ret; + temp_parse_location = parse_location; + + parse_location = var[location]; /* for Available() */ + + temp_stack_depth = stack_depth; + + for (i=0; i<MAXLOCALS; i++) + templocals[i] = var[MAXGLOBALS+i]; + + for (i=0; i<events; i++) + { + defseg = eventtable; + + eventin = PeekWord(2 + i * 4); + eventaddr = (long)PeekWord(2 + i * 4 + 2)*address_scale; + var[self] = eventin; + + domain = 0, flag = 0; + +#if defined (DEBUGGER) + /* Prevent premature stopping */ + if (debugger_step_over && !debugger_finish) + debugger_run = true; +#endif + if (eventin==0 || GrandParent(eventin)==GrandParent(var[player])) + flag = 1; + + /* true is to signal a non-grammar call */ + else if (Available(eventin, true)) + flag = 1; + + if (flag) + { + PassLocals(0); + + SetStackFrame(stack_depth, RUNROUTINE_BLOCK, 0, 0); +#if defined (DEBUGGER) + tempdbnest = dbnest; + DebugRunRoutine(eventaddr); + dbnest = tempdbnest; +#else + RunRoutine(eventaddr); +#endif + stack_depth = temp_stack_depth; + + retflag = 0; + if (var[endflag]) break; + } + } + + for (i=0; i<MAXLOCALS; i++) + var[MAXGLOBALS+i] = templocals[i]; + + codeptr = tempptr; + parse_location = temp_parse_location; + var[self] = tempself; + undorecord = (char)tempundo; + ret = temp_ret; +} + +void Hugo::playGame() { + char jw = 0; /* just wrote undo info */ + char wasxverb = 0, newinput; + int i, flag, mc, lastspeaking = 0, startlocation; + +#ifdef USE_TEXTBUFFER + TB_Init(); +#endif + +#ifdef PALMOS + if (AutoResume()) + { + goto FreshInput; + } +#endif + + /* Set up initial screen position */ + hugo_settextpos(1, physical_windowheight/lineheight); + display_needs_repaint = false; + full = 0; + + /* Load globals */ + defseg = arraytable; + for (i=0; i<MAXGLOBALS; i++) + var[i] = PeekWord(i*2); + + /* Reset the speaking-to variable */ + speaking = 0; + + if (game_version < 22) + { + passlocal[0] = objects; +#if defined (ACTUAL_LINELENGTH) + passlocal[1] = ACTUAL_LINELENGTH(); +#else + passlocal[1] = physical_windowwidth/FIXEDCHARWIDTH; +#endif + } + +#if defined (DEBUGGER) +RestartDebugger: + + dictcount = original_dictcount; /* see hd.c */ + + /* If no gamefile is loaded, jump immediately to the debugger + interrupt function. + */ + if (game==NULL) Debugger(); +#endif + + stack_depth = RESET_STACK_DEPTH; + + InitGame(); + + undoptr = 0; + undoturn = 0; + undoinvalid = 1; + undorecord = 0; + +Start: + stack_depth = 0; + + strcpy(errbuf, ""); + strcpy(oops, ""); + + do + { + if (xverb==0) + { + undorecord = true; + + SetStackFrame(RESET_STACK_DEPTH, RUNROUTINE_BLOCK, 0, 0); + + PassLocals(0); + +#if defined (DEBUGGER) + currentroutine = mainaddr; + window[VIEW_CALLS].count = 0; + + /* If there is no Main routine */ + if (currentroutine==0) goto NormalTermination; + + DebugRunRoutine((long)mainaddr*address_scale); +#else + RunRoutine((long)mainaddr*address_scale); +#endif + undorecord = false; + if (retflag) + break; + } + + if (!undoinvalid && !wasxverb) + { + undorecord = true; + SaveUndo(0, undoturn, 0, 0, 0); + undorecord = false; + undoturn = 0; + } + else if (undoinvalid) + { + undoptr = 0; + undoinvalid = 0; + undoturn = 0; + undorecord = true; + SaveUndo(0, 0, 0, 0, 0); + SaveUndo(0, 0, 0, 0, 0); + undorecord = false; + } + + xverb = true; + + jw = 1; + if (var[endflag]) + break; + jw = 0; + + full_buffer = 0; + + /* Endless game loop begins here */ + + do + { + PassLocals(0); + + /* If there's nothing waiting to be finished in the + input buffer: + */ + if (!remaining) + { + newinput = true; + speaking = 0; + do + { +FreshInput: + if (full_buffer != true) + { + newinput = true; + speaking = 0; + var[actor] = var[player]; +#if defined (DEBUGGER) + AddStringtoCodeWindow("[Waiting for input]"); + buffered_code_lines = FORCE_REDRAW; + debugger_has_stepped_back = false; + window[VIEW_LOCALS].changed = true; +#endif + if (!playback) + { + GetCommand(); + } + else + { + if (!hugo_fgets(buffer, MAXBUFFER, *playback)) + { + if (hugo_fclose(playback)) + FatalError(READ_E); + playback = NULL; + GetCommand(); + } + else + { + /* Remove CR/LF */ +/* + buffer[strlen(buffer)-1] = '\0'; + if (buffer[strlen(buffer)-1]==0x0d) + buffer[strlen(buffer)-1] = '\0'; +*/ + while (buffer[strlen(buffer)-1]==0x0d || buffer[strlen(buffer)-1]==0x0a) + buffer[strlen(buffer)-1] = '\0'; + sprintf(line, "\n%s%s", GetWord(var[prompt]), buffer); + if (script) + /* fprintf() this way for Glk */ + script->putBuffer("\n", 1); +#if defined (SCROLLBACK_DEFINED) + hugo_sendtoscrollback("\n"); +#endif + AP(line); + } + } +#if defined (DEBUGGER) + if (debugger_collapsing) + goto NormalTermination; + runaway_counter = 0; +#endif + + SeparateWords(); + + if (record) + { + for (i=1; i<=words; i++) + { + if (!strcmp(word[i], ".")) + { + /* fprintf() this way for Glk */ + if (hugo_fprintf(record, "%s", "\n")<0) + FatalError(WRITE_E); + if (i==words) goto RecordedNewline; + } + else if (hugo_fputs(word[i], record)<0 + || hugo_fprintf(record, "%s", " ")<0) + { + FatalError(WRITE_E); + } + } + if (hugo_fprintf(record, "%s", "\n")<0) FatalError(WRITE_E); +RecordedNewline:; + } + } + else full_buffer = false; + + if (!strcmp(buffer, "") || buffer[0]=='.') + { + strcpy(parseerr, ""); + + /* "What?" */ + ParseError(0, 0); + goto FreshInput; + } + } + + /* Loop until valid input */ + while (Parse()==false && strcmp(buffer, "")); + } + + + /* Else if there's something left in the input buffer */ + else + { + newinput = false; + + /* Erase the just-parsed command, and check to + to see if what's left is just blanks + */ + while (words > remaining) + KillWord(1); + flag = false; + for (i=1; i<=words; i++) + if (wd[i]!=0) flag = true; + if (!flag) + goto FreshInput; + + if (words) AP(""); + + if (Parse()==false) + { + mc = false; + goto Skipmc; + } + } + + /* Run the user Parse routine if one exists */ + CallLibraryParse(); + + reparse_everything = false; + do + { + mc = MatchCommand(); + if (mc==false) + { + remaining = 0; + } + } while (reparse_everything && !mc); +Skipmc:; + } + while (!mc); + + if (!xverb) undorecord = true; + + wasxverb = xverb; + + /* If there's an unknown string to be put in parse$ */ + if (parsestr[0]!='\0') + { + if (parsestr[0]=='\"') + { + strcpy(parseerr, Right(parsestr, strlen(parsestr)-1)); + if (parseerr[strlen(parseerr)-1]=='\"') + parseerr[strlen(parseerr)-1] = '\0'; + } + } + else + strcpy(parseerr, ""); + + /* default actor */ + var[actor] = var[player]; + + if (!newinput && lastspeaking) speaking = lastspeaking; + + if (var[verbroutine]!=0 || (speaking)) + { + /* If command addresses an object/char. */ + if (speaking) + { + lastspeaking = speaking; + var[actor] = speaking; + + /* If user Speakto routine exists */ + if (speaktoaddr) + { + if (objcount) var[object] = objlist[0]; + ret = 0; + + SetStackFrame(RESET_STACK_DEPTH, RUNROUTINE_BLOCK, 0, 0); + + passlocal[0] = speaking; + PassLocals(1); +#if defined (DEBUGGER) + DebugRunRoutine((long)speaktoaddr*address_scale); +#else + RunRoutine((long)speaktoaddr*address_scale); +#endif + if (ret==0) + { + remaining = 0; + xverb = true; + } + retflag = 0; + } + + else + { + /* "...start with a verb..." */ + ParseError(2, 0); + xverb = true; + } + + /* reset actor */ + var[actor] = var[player]; + } + + /* Regular old vanilla command: */ + else + { + speaking = 0; + lastspeaking = 0; + + /* As of v2.5, the Perform junction routine takes care of calling the + before routines, verbroutine, etc. + */ + if (game_version>=25 && performaddr!=0) + { + i = 0; +NextPerform: + if (objcount) var[object] = objlist[i]; + + /* Have to do this before passing locals, in case + Name() ends up calling a routine (which would + trash passlocal[]) + */ + if (parseerr[0]=='\0' && parsestr[0]=='\0') + strcpy(parseerr, Name(objlist[i])); + + /* Set up arguments for Perform */ + passlocal[0] = var[verbroutine]; + passlocal[1] = var[object]; + passlocal[2] = var[xobject]; + + /* 'queue' argument, >1 if objcount > 1, or if + "all" has been used to refer to object(s) */ + passlocal[3] = (objcount>1)?(i+1):(parse_allflag?1:0); + /* -1 if object is a digit */ + if (object_is_number) passlocal[3] = (short)-1; + + /* 'isxverb' argument */ + if (game_version>=31 && xverb) + passlocal[4] = 1; + + obj_match_state = -1; + startlocation = var[location]; + ret = 0; + + SetStackFrame(RESET_STACK_DEPTH, RUNROUTINE_BLOCK, 0, 0); + + PassLocals(4); +#if defined (DEBUGGER) + DebugRunRoutine((long)performaddr*address_scale); +#else + RunRoutine((long)performaddr*address_scale); +#endif + if (ret==0) + { + remaining = 0; + xverb = true; + } + retflag = 0; + + /* Break if endflag is set or if the location has + changed from the first call to Perform + */ + if (var[endflag] || startlocation!=var[location]) + goto EndofCommand; + + if (objcount>1 && ++i<objcount) + goto NextPerform; + } + + /* v2.4 or earlier had to call the verb loop via the engine + (as does v2.5 with no Perform routine */ + + /* One or more objects specified */ + else if (objcount > 0) /* "if (objcount > 0" for pre-v2.5 */ + { + obj_match_state = 1; + startlocation = var[location]; + for (i=0; i<objcount; i++) + { + if (parseerr[0]=='\0' && parsestr[0]=='\0') + strcpy(parseerr, Name(objlist[i])); + + if (ValidObj(objlist[i]) && + ((objcount>1 && objlist[i]!=var[xobject]) || objcount==1)) + { + var[object] = objlist[i]; + if (GetProp(var[player], before, 1, 0)==0) + if (GetProp(var[location], before, 1, 0)==0) + if (GetProp(var[xobject], before, 1, 0)==0) + { + /* If multiple objects are specified, print + "name: " for each: + */ + if (objcount > 1) + { + sprintf(line, "%s: \\;", Name(var[object])); + AP(line); + } + + obj_match_state = 0; + + if ((object_is_number) || GetProp(var[object], before, 1, 0)==0) + { + obj_match_state = -1; + ret = 0; + + SetStackFrame(RESET_STACK_DEPTH, RUNROUTINE_BLOCK, 0, 0); + + PassLocals(0); +#if defined (DEBUGGER) + DebugRunRoutine((long)var[verbroutine]*address_scale); +#else + RunRoutine((long)var[verbroutine]*address_scale); +#endif + if (ret==0) + { + remaining = 0; + xverb = true; + } + retflag = 0; + + GetProp(var[player], after, 1, 0); + GetProp(var[location], after, 1, 0); + } + } + } + if (var[endflag] || var[location]!=startlocation) + break; + } + } + + /* No object(s) specified */ + else + { + if (GetProp(var[player], before, 1, 0)==0) + { + if (GetProp(var[location], before, 1, 0)==0) + { + ret = 0; + + SetStackFrame(RESET_STACK_DEPTH, RUNROUTINE_BLOCK, 0, 0); + + PassLocals(0); +#if defined (DEBUGGER) + DebugRunRoutine((long)var[verbroutine]*address_scale); +#else + RunRoutine((long)var[verbroutine]*address_scale); +#endif + if (ret==0) + { + remaining = 0; + xverb = true; + } + retflag = 0; + + GetProp(var[player], after, 1, 0); + GetProp(var[location], after, 1, 0); + } + } + } + + /* (end of pre-v2.5 verbroutine-calling) */ + + } + } +EndofCommand: + if (var[endflag]) + break; + + undorecord = false; + } + while (true); /* endless loop back to start */ + + undorecord = false; + + if (var[endflag]==-1) +#if defined (DEBUGGER) + goto NormalTermination; +#else + return; +#endif + + if (!jw) + { + undorecord = true; + SaveUndo(0, undoturn, 0, 0, 0); + undorecord = false; + undoturn = 0; + } + + if (playback) + { + if (hugo_fclose(playback)) FatalError(READ_E); + playback = NULL; + } + + Flushpbuffer(); + + + /* Run the user Endgame routine if one exists */ + +#if defined (DEBUGGER) + if (endgameaddr && !debugger_collapsing) +#else + if (endgameaddr) +#endif + { + passlocal[0] = var[endflag]; + + SetStackFrame(RESET_STACK_DEPTH, RUNROUTINE_BLOCK, 0, 0); + + ret = 0; + var[endflag] = 0; + PassLocals(0); +#if defined (DEBUGGER) + DebugRunRoutine((long)endgameaddr*address_scale); +#else + RunRoutine((long)endgameaddr*address_scale); +#endif + retflag = false; + } + else + ret = 0; + + xverb = true; + wasxverb = true; + if (ret) goto Start; + + /* Stop all audio after running EndGame */ + hugo_stopmusic(); + hugo_stopsample(); + + /* The debugger will reset endflag anyway, but we need it to signal ports + (like Palm) that the game loop has exited. + */ + var[endflag] = -1; + +#if defined (DEBUGGER) + +NormalTermination: + + /* Normal program termination doesn't exit the debugger. */ + + debugger_interrupt = true; + debugger_run = false; + var[endflag] = false; + + xverb = false; + + SwitchtoDebugger(); + UpdateDebugScreen(); + + if (debugger_collapsing!=2) + { + DebugMessageBox("Program Exiting", "Normal program termination"); + + } + + debugger_collapsing = false; + + if ((game!=NULL) && !RunRestart()) + DebugMessageBox("Restart Error", "Unable to restart"); + + SwitchtoGame(); + + history_count = 0; + window[VIEW_CALLS].count = 0; + + for (i=0; i<(int)window[CODE_WINDOW].count; i++) + free(codeline[i]); + window[CODE_WINDOW].count = 0; + + /* Force Code window redraw */ + buffered_code_lines = FORCE_REDRAW; + + goto RestartDebugger; +#endif +} + +void Hugo::RunIf(char override) { + char t, tempinexpr; + long enterptr, skip; + + switch (t = MEM(codeptr)) + { + case CASE_T: + case IF_T: + case ELSEIF_T: + case WHILE_T: + case FOR_T: + { + codeptr++; + enterptr = codeptr; + + /* Remember the skip distance */ + skip = PeekWord(codeptr); + codeptr += 2; + + /* Check if we've already done an elseif */ + if (override && t==ELSEIF_T) + { + codeptr = skip+enterptr; + return; + } + + /* Read the expression */ + tempinexpr = inexpr; + inexpr = 1; + SetupExpr(); + inexpr = tempinexpr; + + /* If the expression is false, skip the + conditional block + */ + if (EvalExpr(0)==0) + { + codeptr = skip+enterptr; + return; + } + + /* Protect the stack if jumping backward */ + if (MEM(codeptr)==JUMP_T) + { + if ((long)(PeekWord(codeptr+1)*address_scale) < codeptr) + if (--stack_depth < 0) stack_depth = 0; + } + + /* Continue on into the conditional block if + the expression evaluated to non-zero + */ +PasstoBlock: + if (t==WHILE_T || t==FOR_T) + SetStackFrame(stack_depth, CONDITIONAL_BLOCK, skip+enterptr, 0); + + else /* no 'break' parameter */ + SetStackFrame(stack_depth, CONDITIONAL_BLOCK, 0, 0); +#if defined (DEBUGGER) + dbnest++; +#endif + return; + } + case ELSE_T: + { + skip = PeekWord(++codeptr); + enterptr = codeptr; + codeptr += 2; + + if (override) + { + codeptr = skip+enterptr; + return; + } + + if (MEM(codeptr)==JUMP_T) + { + if ((long)(PeekWord(codeptr+1)*address_scale) < codeptr) + if (--stack_depth < 0) stack_depth = 0; + } + + goto PasstoBlock; + } + } +} + +void Hugo::RunInput() { + int i; + + strcpy(parseerr, ""); + + Flushpbuffer(); + + if (icolor==-1) icolor = fcolor; /* check unset input color */ + + hugo_getline(""); + +#if defined (DEBUGGER) + if (debugger_collapsing) return; +#endif + + strcpy(buffer, Rtrim(strlwr(buffer))); + + SeparateWords(); + + for (i=1; i<=words; i++) + { + wd[i] = FindWord(word[i]); + + /* If a word isn't in the dictionary */ + if (wd[i]==UNKNOWN_WORD) + { + wd[i] = 0; + strcpy(parseerr, word[i]); + if (parseerr[0]=='\"') + { + strcpy(parseerr, Right(parseerr, strlen(parseerr)-1)); + if (parseerr[strlen(parseerr)-1]=='\"') + parseerr[strlen(parseerr)-1] = '\0'; + } + } + } + currentpos = 0; /* left margin */ + remaining = 0; +} + +void Hugo::RunMove() { + int obj, p; +#if defined (DEBUGGER) + char out_of_range = 0; +#endif + + switch (MEM(codeptr)) + { + case MOVE_T: + { + codeptr++; + obj = GetValue(); + +#if defined (DEBUGGER) + if (!CheckinRange(obj, objects, "object")) + out_of_range = true; + else +#endif + SaveUndo(MOVE_T, obj, Parent(obj), 0, 0); + + codeptr++; /* skip "to" */ + p = GetValue(); + +#if defined (DEBUGGER) + if (!CheckinRange(p, objects, "object")) + out_of_range = true; + + if (!out_of_range) +#endif + MoveObj(obj, p); + break; + } + + case REMOVE_T: + { + codeptr++; + obj = GetValue(); + +#if defined (DEBUGGER) + if (!CheckinRange(obj, objects, "object")) + out_of_range = true; + + else +#endif + SaveUndo(MOVE_T, obj, Parent(obj), 0, 0); + +#if defined (DEBUGGER) + if (!out_of_range) +#endif + MoveObj(obj, 0); /* move to parent 0 */ + break; + } + } + + if (game_version>=23) codeptr++; /* eol */ +} + +void Hugo::RunPrint() { + char number = 0, hexnumber = 0; + int a; + int i, l; + + codeptr++; + + while (MEM(codeptr) != EOL_T) + { + strcpy(line, ""); + + switch (MEM(codeptr)) + { + case NEWLINE_T: + { + codeptr++; + if (currentpos+hugo_textwidth(pbuffer)!=0) + AP(""); + if (MEM(codeptr)==SEMICOLON_T) codeptr++; + continue; + } + + case TO_T: + { + codeptr++; + +#if !defined (ACTUAL_LINELENGTH) + if ((a = GetValue()) > physical_windowwidth/FIXEDCHARWIDTH) + a = physical_windowwidth/FIXEDCHARWIDTH; +#else + if ((a = GetValue()) > ACTUAL_LINELENGTH()) + { + double ratio; + + ratio = (physical_windowwidth/FIXEDCHARWIDTH) / a; + a = (int)(ACTUAL_LINELENGTH() / ratio); + } +#endif + strcpy(line, ""); + l = 0; + if (a*FIXEDCHARWIDTH > + hugo_textwidth(pbuffer)+currentpos-hugo_charwidth(' ')) + { + for (i=hugo_textwidth(pbuffer)+currentpos; +#ifdef NO_TERMINAL_LINEFEED + i<a*FIXEDCHARWIDTH; +#else + i<a*FIXEDCHARWIDTH && i<physical_windowright; +#endif + i+=hugo_charwidth(' ')) + { + line[l++] = FORCED_SPACE; + line[l] = '\0'; + } + + } + break; + } + + case CAPITAL_T: + { + codeptr++; + capital = 1; + continue; + } + + case NUMBER_T: + { + codeptr++; + number = 1; + continue; + } + + case HEX_T: + { + codeptr++; + number = 1; + hexnumber = 1; + continue; + } + + case STRINGDATA_T: + { + codeptr++; + if (game_version >= 24) + l = PeekWord(codeptr++); + else + l = Peek(codeptr); + for (i=0; i<l; i++) + line[i] = (char)(MEM(++codeptr) - CHAR_TRANSLATION); + line[i] = '\0'; + codeptr++; + break; + } + + /* Anything else is treated as a value */ + default: + { + a = GetValue(); + if (!number) + { + strcpy(line, GetWord(a)); + } + else + { + if (!hexnumber) + { + if (capital) + itoa((unsigned int)a, line, 10); + else + itoa(a, line, 10); + capital = 0; + } + else + sprintf(line, "%X", a); + + number = 0; + hexnumber = 0; + } + break; + } + } + + if (MEM(codeptr)==SEMICOLON_T) + { + codeptr++; + strcat(line, "\\;"); + } + if (capital) + { + capital = 0; + if ((unsigned)line[0]<128) + line[0] = (char)toupper((int)line[0]); + else + { + /* Special conversion for non-Latin1 + (>127) lowercase characters + */ + char diff; + diff = 'a'-'A'; + if ((unsigned)line[0]+diff<=255 && (unsigned)line[0]-diff>127) + line[0] -= diff; + } + } + + AP(line); + } + + codeptr++; +} + +int Hugo::RunRestart() { + unsigned int a; + long i = 0; + Common::SeekableReadStream *file; + +#ifndef LOADGAMEDATA_REPLACED + + remaining = 0; + +#if !defined (GLK) /* with Glk, game is never closed */ + /* Use file instead of game, just in case the call fails */ + if (!(file = HUGO_FOPEN(gamefile, "rb"))) goto RestartError; +#else + file = game; +#endif + + if (hugo_fseek(file, (objtable-gameseg)*16, SEEK_SET)) goto RestartError; + + i = (objtable-gameseg)*16L; + do + { + int val; + + val = hugo_fgetc(file); + SETMEM(i++, (unsigned char)val); + if (val==EOF || hugo_ferror(file)) goto RestartError; + } + while (i < codeend); + +#if !defined (GLK) + if (fclose(file)) FatalError(READ_E); +#endif + +#else + if (!(file = HUGO_FOPEN(gamefile, "rb"))) goto RestartError; + LoadGameData(true); + fclose(file); +#endif /* LOADGAMEDATA_REPLACED */ + + defseg = arraytable; + for (a=0; a<MAXGLOBALS; a++) + var[a] = PeekWord(a*2); + + i = codeptr; + + if (game_version < 22) + { + passlocal[0] = objects; +#if defined (ACTUAL_LINELENGTH) + passlocal[1] = ACTUAL_LINELENGTH(); +#else + passlocal[1] = physical_windowwidth/FIXEDCHARWIDTH; +#endif + } + +#if defined (DEBUGGER) + /* A restart can happen mid-playback from the debugger */ + if (playback) + { + if (hugo_fclose(playback)) + FatalError(READ_E); + playback = NULL; + } + + if (active_screen!=DEBUGGER) +#endif + { + InitGame(); + + SetStackFrame(stack_depth, RUNROUTINE_BLOCK, 0, 0); + + PassLocals(0); +#if defined (DEBUGGER) + DebugRunRoutine((long)mainaddr*address_scale); +#else + RunRoutine((long)mainaddr*address_scale); +#endif + retflag = 0; + + codeptr = i; + + undoptr = 0; + undoturn = 0; + undoinvalid = 1; + } + + return 1; + +RestartError: +#if !defined (GLK) + if (fclose(file)) FatalError(READ_E); +#endif + + return 0; +} + +#ifndef RESTOREGAMEDATA_REPLACED + +int Hugo::RestoreGameData() { + char testid[3], testserial[9]; + int lbyte, hbyte; + int j; + unsigned int k, undosize; + long i; + + /* Check ID */ + testid[0] = (char)hugo_fgetc(save); + testid[1] = (char)hugo_fgetc(save); + testid[2] = '\0'; + if (hugo_ferror(save)) goto RestoreError; + + if (strcmp(testid, id)) + { + AP("Incorrect save file."); + if (hugo_fclose(save)) FatalError(READ_E); + save = NULL; + return 0; + } + + /* Check serial number */ + if (!hugo_fgets(testserial, 9, save)) goto RestoreError; + if (strcmp(testserial, serial)) + { + AP("Save file created by different version."); + if (hugo_fclose(save)) FatalError(READ_E); + save = NULL; + return 0; + } + + /* Restore variables */ + for (k=0; k<MAXGLOBALS+MAXLOCALS; k++) + { + if ((lbyte = hugo_fgetc(save))==EOF || (hbyte = hugo_fgetc(save))==EOF) + goto RestoreError; + var[k] = lbyte + hbyte * 256; + } + + /* Restore objtable and above */ + + if (hugo_fseek(game, objtable*16L, SEEK_SET)) goto RestoreError; + i = 0; + + while (i<codeend-(long)(objtable*16L)) + { + if ((hbyte = hugo_fgetc(save))==EOF && hugo_ferror(save)) goto RestoreError; + + if (hbyte==0) + { + if ((lbyte = hugo_fgetc(save))==EOF && hugo_ferror(save)) goto RestoreError; + SETMEM(objtable*16L+i, (unsigned char)lbyte); + i++; + + /* Skip byte in game file */ + if (hugo_fgetc(game)==EOF) goto RestoreError; + } + else + { + while (hbyte--) + { + /* Get unchanged game file byte */ + if ((lbyte = hugo_fgetc(game))==EOF) goto RestoreError; + SETMEM(objtable*16L+i, (unsigned char)lbyte); + i++; + } + } + } + + /* Restore undo data */ + if ((lbyte = hugo_fgetc(save))==EOF || (hbyte = hugo_fgetc(save))==EOF) + goto RestoreError; + undosize = lbyte + hbyte*256; + + /* We can only restore undo data if it was saved by a port with + the same MAXUNDO as us */ + if (undosize==MAXUNDO) + { + for (k=0; k<MAXUNDO; k++) + { + for (j=0; j<5; j++) + { + if ((lbyte = hugo_fgetc(save))==EOF || (hbyte = hugo_fgetc(save))==EOF) + goto RestoreError; + undostack[k][j] = lbyte + hbyte*256; + } + } + if ((lbyte = hugo_fgetc(save))==EOF || (hbyte = hugo_fgetc(save))==EOF) goto RestoreError; + undoptr = lbyte + hbyte*256; + if ((lbyte = hugo_fgetc(save))==EOF || (hbyte = hugo_fgetc(save))==EOF) goto RestoreError; + undoturn = lbyte + hbyte*256; + if ((lbyte = hugo_fgetc(save))==EOF || (hbyte = hugo_fgetc(save))==EOF) goto RestoreError; + undoinvalid = (unsigned char)lbyte, undorecord = (unsigned char)hbyte; + } + else undoinvalid = true; + + return true; + +RestoreError: + return false; +} + +#endif // RESTOREGAMEDATA_REPLACED + +int Hugo::RunRestore() { +#if !defined (GLK) + save = NULL; + + /* stdio implementation */ + hugo_getfilename("to restore", savefile); + +#if defined (DEBUGGER) + if (debugger_collapsing) return 1; +#endif + if (!strcmp(line, "")) return 0; + if (!(save = HUGO_FOPEN(line, "r+b"))) return 0; + +#else + /* Glk implementation */ + frefid_t savefile; + + save = NULL; + + savefile = glk_fileref_create_by_prompt(fileusage_SavedGame | fileusage_BinaryMode, + filemode_Read, 0); + if (!savefile) return 0; + if (glk_fileref_does_file_exist(savefile)) + save = glk_stream_open_file(savefile, filemode_Read, 0); + else + save = NULL; + glk_fileref_destroy(savefile); + if (!save) return 0; + +#endif /* GLK */ + + if (!RestoreGameData()) goto RestoreError; + + if (hugo_fclose(save)) FatalError(READ_E); + save = NULL; + +#if !defined (GLK) + strcpy(savefile, line); +#endif + + game_reset = true; + + return (1); + +RestoreError: + if ((save) && hugo_fclose(save)) FatalError(READ_E); + save = NULL; + game_reset = false; + return 0; +} + +void Hugo::RunRoutine(long addr) { + int null_count; /* for reading to next address boundary */ + char tempinexpr; + int i, t, len, xpos, ypos; + int initial_stack_depth, tempret; + unsigned int routineptr = 0; + long textaddr; +#if defined (DEBUGGER) + char wascalled = 0; /* distinguish routine calls from 'window' + blocks, etc. */ + const char *called_from; + char trace_comp_prop = 0; + unsigned char param_type; + long param_start; + unsigned int old_currentroutine = 0; + + /* Because <debugdata#> and <label#> in-code data is + not decompiled + */ + int broke_on_nonstatement = 0; +#endif + + /* If routine doesn't exist */ + if (addr==0L) return; + + initial_stack_depth = stack_depth; + inexpr = 0; + +#if !defined (DEBUGGER) +#if defined (DEBUG_CODE) +/* + if (trce) + { + if (codeptr != addr) + { + sprintf(line, "[ROUTINE: $%6s]", PrintHex(addr)); + AP(line); + wascalled = 1; + } + } +*/ +#endif +#endif + +#if defined (DEBUGGER) + +/* + * First see what debugger information has to be set up upon calling + * this block of code + */ + + /* If this is a routine call vs. other block, codeptr will be + different from addr. + */ + if (codeptr != addr) + { + wascalled = 1; + + /* Checking to see if currentroutine is 0 is a way of seeing + if the debugger has started properly. If, for example, a + property routine runs while LoadGame() is searching for + the display object, it may corrupt the uninitialized + debugger arrays. + */ + if ((old_currentroutine = currentroutine)==0) return; + + currentroutine = (unsigned int)(addr/address_scale); + + if (debugger_step_over) + { + step_nest++; + debugger_interrupt = false; + } + else + { + /* Add a blank line if one hasn't been added + already: + */ + if ((window[CODE_WINDOW].count) && (codeline[window[CODE_WINDOW].count-1][0]&0x0FF)!='\0') + AddStringtoCodeWindow(""); + + /* If this is a property routine, the debug_line array + already holds the calling information + */ + if (!trace_complex_prop_routine) + sprintf(debug_line, "Calling: %s", RoutineName(currentroutine)); + else + trace_comp_prop = true; + trace_complex_prop_routine = false; + + call[window[VIEW_CALLS].count].addr = currentroutine; + call[window[VIEW_CALLS].count++].param = (char)arguments_passed; + window[VIEW_CALLS].changed = true; + + /* Revise call history */ + if (window[VIEW_CALLS].count==MAXCALLS) + { + for (i=0; i<MAXCALLS-1; i++) + { + call[i].addr = call[i+1].addr; + call[i].param = call[i+1].param; + } + call[0].addr = 0; + } + + /* If not object.property or an event */ + if (strchr(debug_line, '.')==NULL && strstr(debug_line, "vent ")==NULL) + { + strcat(debug_line, "("); + for (i=0; i<arguments_passed; i++) + { + sprintf(debug_line+strlen(debug_line), "%d", var[MAXGLOBALS+i]); + if (i<arguments_passed-1) + strcat(debug_line, ", "); + } + strcat(debug_line, ")"); + } + AddStringtoCodeWindow(debug_line); + } + } + +#endif /* defined (DEBUGGER) */ + + defseg = gameseg; + codeptr = addr; + +/* + * Get the next token, so long as it isn't a CLOSE_BRACE_T ('}') + * marking the end of this block of code + */ + +ContinueRunning: + + while (MEM(codeptr) != CLOSE_BRACE_T) /* until "}" */ + { + +#if defined (DEBUGGER) + /* Check if we're stepping over, and if we've returned to + the original level of nesting: + */ + if (debugger_step_over && step_nest==0) + { + debugger_step_over = false; + debugger_interrupt = true; + } +#endif + if (var[endflag]) return; + + null_count = 0; + + /* Read the next token */ + while ((t = MEM(codeptr))==0) + { + codeptr++; + + /* Allow for padding zeroes. If address_scale + zeroes are processed, we can't simply be + eating up the null space before an address + boundary. + */ + if (++null_count > address_scale) + FatalError(UNKNOWN_OP_E); + } + +#if !defined (DEBUGGER) +#if defined (DEBUG_CODE) + if (!inwindow) + { + sprintf(line, "[%6s: %s]", PrintHex(codeptr), token[t]); + AP(line); + } +#endif +#endif + if (game_version < 22) if (t==TEXT_T) t = TEXTDATA_T; + +#if defined (DEBUGGER) + if (++runaway_counter>=65535 && runtime_warnings) + { + sprintf(debug_line, "Possible runaway loop (65535 unchecked steps)"); + RuntimeWarning(debug_line); + buffered_code_lines = FORCE_REDRAW; + runaway_counter = 0; + } + + if (t!=DEBUGDATA_T && t!=LABEL_T && !debugger_step_over) + AddLinetoCodeWindow(codeptr); + + if ((i = IsBreakpoint(codeptr))) + { + if (t==DEBUGDATA_T || t==LABEL_T) + broke_on_nonstatement = i; + + debugger_interrupt = true; + + /* '<' for "<Unknown>" */ + if (breakpoint[--i].in[0]=='<') + { + breakpoint[i].in = RoutineName(currentroutine); + window[VIEW_BREAKPOINTS].changed = true; + } + } + + /* Don't add in-code data to the code window */ + if (t==DEBUGDATA_T || t==LABEL_T) goto ProcessToken; + + /* Evaluate (only) any watch expressions set to break + when true: + */ + for (i=0; i<(int)window[VIEW_WATCH].count; i++) + { + if (watch[i].isbreak) + { + SetupWatchEval(i); + if (EvalWatch()) + { + debugger_interrupt = true; + break; + } + } + } + + /* Always update the watch window */ + window[VIEW_WATCH].changed = true; + +/* + * Immediately following is the main Debugger interrupt point. + * No matter where the engine is while running, if debugger_interrupt + * is ever set during the execution of a command, execution is + * paused and control passed to the Debugger. + */ + + if (debugger_interrupt) + { + if (broke_on_nonstatement) + { + broke_on_nonstatement--; + breakpoint[broke_on_nonstatement].addr = codeptr; + window[VIEW_BREAKPOINTS].changed = true; + broke_on_nonstatement = false; + } + + if (debugger_step_over) + { + AddStringtoCodeWindow("..."); + if (t!=DEBUGDATA_T && t!=LABEL_T) + AddLinetoCodeWindow(codeptr); + } + Debugger(); + } + + + /* Now, additional processing for flags that may have been + set while execution was suspended: + */ + + /* Collapsing the RunRoutine() call stack */ + if (debugger_collapsing) return; + + + /* May be necessary to reset this if, for some + reason, the line array was altered (see above) + */ + if (!trace_complex_prop_routine) + sprintf(debug_line, "Calling: %s", RoutineName(currentroutine)); + trace_complex_prop_routine = false; + + + /* Add this statement to the code history */ + if (!debugger_step_back && !(debugger_step_over && step_nest>0)) + { + if (++history_count>=MAX_CODE_HISTORY) + history_count = MAX_CODE_HISTORY; + code_history[history_last] = codeptr; + dbnest_history[history_last] = dbnest; + if (++history_last >= MAX_CODE_HISTORY) + history_last = 0; + } + + /* If skipping next or stepping back */ + if (debugger_skip || debugger_step_back) + { + /* Debugger() has reset codeptr to next_codeptr */ + debugger_skip = false; + continue; + } + +#endif /* defined (DEBUGGER) */ + + +/* + * This is the heart of RunRoutine(): the switch statement + * that executes the next engine operation based on what token + * has been read + */ +#if defined (DEBUGGER) +ProcessToken: +#endif + switch (t) + { + /* First process any encoded, non-executable data: */ + + /* If this is v2.5 or later, the compiler will have + noted the nesting level of this label to + reconcile stack_depth + */ + case LABEL_T: + stack_depth = initial_stack_depth + MEM(++codeptr); + codeptr++; + break; + + case DEBUGDATA_T: + { + switch (MEM(++codeptr)) + { + case VAR_T: /* local variable name */ + { + len = MEM(++codeptr); +#if defined (DEBUGGER) + if (!debugger_has_stepped_back) + { + /* Read the local variable name */ + for (i=0; i<len; i++) + line[i] = MEM(codeptr+i+1); + line[len] = '\0'; + + /* Check to make sure it doesn't already exist, + for instance, if we've looped back to it + */ + for (i=0; i<current_locals; i++) + if (!strcmp(line, localname[i])) break; + + /* If it doesn't exist, add it */ + if (i==current_locals) + { + strcpy(localname[current_locals], line); + if (++current_locals==MAXLOCALS) + current_locals--; + window[VIEW_LOCALS].count = current_locals; + } + } +#endif + codeptr+=(len+1); + break; + } + } + break; + } + + + /* Then the executable statements: */ + + case TEXTDATA_T: /* printed text from file */ + { + textaddr = Peek(codeptr+1)*65536L+(long)PeekWord(codeptr+2); + strcpy(line, GetText(textaddr)); + codeptr += 4; + if (Peek(codeptr)==SEMICOLON_T) + {strcat(line, "\\;"); + codeptr++;} + if (capital) + {line[0] = (char)toupper((int)line[0]); + capital = 0;} + AP(line); + break; + } + + case TEXT_T: + { + if (MEM(++codeptr)==TO_T) + { + codeptr++; +#if defined (DEBUGGER) + param_type = MEM(codeptr); + param_start = codeptr; +#endif + textto = GetValue(); + if (game_version>=23) codeptr++; /* eol */ +#if defined (DEBUGGER) + /* Check if textto is 0 but was not + really "text to 0", but rather + something that evaluated to 0 + */ + if (textto==0 && runtime_warnings) + { + if (param_type!=VALUE_T || param_start!=codeptr-4) + RuntimeWarning("Text array address evaluates to zero"); + } +#endif + } + else + { + SetupDisplay(); + } + break; + } + + case MINUS_T: /* "--" */ + case PLUS_T: /* "++" */ + GetValue(); + codeptr++; /* eol */ + break; + + case PRINT_T: + RunPrint(); + break; + + case PRINTCHAR_T: + { +Printcharloop: + codeptr++; + i = GetValue(); + if (capital) sprintf(line, "%c\\;", toupper(i)); + else sprintf(line, "%c\\;", i); + capital = 0; + AP(line); + if (Peek(codeptr)==COMMA_T) + goto Printcharloop; + if (game_version>=23) codeptr++; /* eol */ + break; + } + + case STRING_T: + RunString(); + break; + + case WINDOW_T: + RunWindow(); + break; + + case LOCATE_T: + { + char adhere_to_bottom = false; + + codeptr++; + + Flushpbuffer(); + + xpos = GetValue(); + if (xpos > physical_windowwidth/FIXEDCHARWIDTH) + xpos = physical_windowwidth/FIXEDCHARWIDTH; + + if (Peek(codeptr)==COMMA_T) + { + codeptr++; + ypos = GetValue(); + } + else + ypos = currentline; + + full = ypos - 1; + + + if (ypos >= physical_windowheight/lineheight) + full = 0; + + if (ypos > physical_windowheight/lineheight) + { + ypos = physical_windowheight/lineheight; + + if (!inwindow && current_text_y && (currentfont & PROP_FONT)) + adhere_to_bottom = true; + } + + hugo_settextpos(xpos, ypos); + + /* An adjustment for non-fixed-width font lineheight */ + if (adhere_to_bottom) + current_text_y = physical_windowbottom - lineheight; + + currentpos = (xpos-1)*FIXEDCHARWIDTH; + currentline = ypos; + + codeptr++; /* skip EOL */ + break; + } + + case SELECT_T: + codeptr++; + break; + + case CASE_T: + case IF_T: + case ELSEIF_T: + case ELSE_T: + case WHILE_T: + case FOR_T: + RunIf(0); + break; + + case DO_T: + RunDo(); + break; + + case RUN_T: + codeptr++; + tempret = ret; + GetValue(); /* object.property to run */ + ret = tempret; + if (game_version>=23) codeptr++; /* eol */ + break; + + case BREAK_T: + { + for (; stack_depth>0; stack_depth--) + { + if (code_block[stack_depth].brk) + { + codeptr = code_block[stack_depth].brk; +#if defined (DEBUGGER) + dbnest = code_block[stack_depth].dbnest; +#endif + --stack_depth; + goto LeaveBreak; + } + } + codeptr++; +LeaveBreak: + break; + } + + case RETURN_T: + { + codeptr++; + i = inexpr; /* don't reuse tempinexpr */ + inexpr = 1; + + /* Let 'return Routine()' or 'return obj.prop' + set tail_recursion + */ + tail_recursion = 0; + tail_recursion_addr = 0; + + SetupExpr(); + inexpr = (char)i; + + /* If either a routine or property routine call has + determined it's valid, we can use tail-recursion + (with tail_recursion_addr having been set up) + */ + if (tail_recursion) + { + HandleTailRecursion(tail_recursion_addr); + break; + } + else + { + /* Clear these to be safe */ + tail_recursion = 0; + tail_recursion_addr = 0; + + ret = EvalExpr(0); + retflag = true; + goto LeaveRunRoutine; + } + } + + case JUMP_T: + { + codeptr = (long)PeekWord(codeptr + 1)*address_scale; +#if defined (DEBUGGER) + if (MEM(codeptr)==LABEL_T) + dbnest = 0; /* prevent "false" nesting */ +#endif + break; + } + + case PARENT_T: + case SIBLING_T: + case CHILD_T: + case YOUNGEST_T: + case ELDEST_T: + case YOUNGER_T: + case ELDER_T: + { + inobj = true; + + /* Note: GetValue() would actually get + the property/attribute to be set + */ + RunSet(GetVal()); + + inobj = false; + break; + } + + case VAR_T: + case OBJECTNUM_T: + case VALUE_T: + case WORD_T: + case ARRAYDATA_T: + case ARRAY_T: + RunSet(-1); + break; + + case ROUTINE_T: + case CALL_T: + { + switch (t) + { + case ROUTINE_T: + { + codeptr++; + routineptr = PeekWord(codeptr); + codeptr += 2; + break; + } + case CALL_T: + { + codeptr++; + routineptr = GetValue(); + } + } + + tempret = ret; + CallRoutine(routineptr); + + if (MEM(codeptr)==DECIMAL_T || MEM(codeptr)==IS_T) + RunSet(ret); + else if ((t==CALL_T) && game_version>=23) + codeptr++; /* eol */ + + ret = tempret; + + break; + } + + case MOVE_T: + case REMOVE_T: + RunMove(); + break; + + case COLOR_T: + case COLOUR_T: + { + codeptr++; + + /* Get foreground color */ + fcolor = (char)GetValue(); + /* If background color is given */ + if (Peek(codeptr)==COMMA_T) + { + codeptr++; + bgcolor = (char)GetValue(); + + /* If input color is given */ + if (Peek(codeptr)==COMMA_T) + { + codeptr++; + icolor = (char)GetValue(); + } + else + icolor = fcolor; + } + else + icolor = fcolor; + + /* Only set the actual pen color now if + there is no text buffered + */ + if (pbuffer[0]=='\0') + { + hugo_settextcolor(fcolor); + hugo_setbackcolor(bgcolor); + } + + if (inwindow) + default_bgcolor = bgcolor; + + codeptr++; /* skip EOL */ + break; + } + + case PAUSE_T: + { + full = 0; + override_full = true; + codeptr++; + Flushpbuffer(); + /* Flush the key buffer first */ + while (hugo_iskeywaiting()) hugo_getkey(); + wd[0] = (unsigned int)hugo_waitforkey(); +#if defined (DEBUGGER) + runaway_counter = 0; +#endif + break; + } + + case RUNEVENTS_T: + codeptr++; + RunEvents(); + break; + + case QUIT_T: + var[endflag] = -1; + break; + + case INPUT_T: + RunInput(); + full = 1; + override_full = true; + codeptr++; + break; + + case SYSTEM_T: + RunSystem(); + if (game_version>=23) codeptr++; /* eol */ + break; + + case CLS_T: + { + hugo_settextcolor(fcolor); + hugo_setbackcolor(bgcolor); + hugo_clearwindow(); + hugo_settextpos(1, physical_windowheight/lineheight); /*+1);*/ + + if (!inwindow) + { + full = 0; + } + default_bgcolor = bgcolor; + + codeptr++; + pbuffer[0] = '\0'; + break; + } + + case WRITEFILE_T: + case READFILE_T: + FileIO(); + break; + + case WRITEVAL_T: + { +Writevalloop: + codeptr++; + i = GetValue(); + if (ioblock) + { + if ((ioblock==2) + || hugo_fputc(i%256, io)==EOF + || hugo_fputc(i/256, io)==EOF) + { + ioerror = true; + retflag = true; + break; + } + } + + if (Peek(codeptr)==COMMA_T) + goto Writevalloop; + + if (game_version>=23) codeptr++; /* eol */ + break; + } + + case PICTURE_T: + DisplayPicture(); + break; + + case MUSIC_T: + PlayMusic(); + break; + + case SOUND_T: + PlaySample(); + break; + + case VIDEO_T: + PlayVideo(); + break; + + case ADDCONTEXT_T: + ContextCommand(); + break; + + /* Didn't match a command token, so throw up an + "Unknown operation" error. + */ + default: + FatalError(UNKNOWN_OP_E); + } + + defseg = gameseg; + + if (retflag) goto LeaveRunRoutine; + } + + + /* Process the closing '}': */ + + codeptr++; + +#if defined (DEBUGGER) + if (--dbnest < 0) dbnest = 0; +#endif + /* Continue executing this iteration of RunRoutine() if the + '}' marks the end of a conditional block, i.e., one that + didn't call RunRoutine() the way, e.g., 'window' does. + Otherwise, get out of RunRoutine(). + */ + if (code_block[stack_depth--].type > RUNROUTINE_BLOCK) + { + /* Skip a following 'elseif', 'else', or 'case' */ + t = MEM(codeptr); + while (t==ELSEIF_T || t==ELSE_T || t==CASE_T) + { + RunIf(1); + t = MEM(codeptr); + } + + if (t==WHILE_T && code_block[stack_depth+1].type==DOWHILE_BLOCK) + { + codeptr+=3; + tempinexpr = inexpr; + inexpr = 1; + SetupExpr(); + inexpr = tempinexpr; + + if (EvalExpr(0)) + codeptr = code_block[++stack_depth].returnaddr; + else + codeptr = code_block[stack_depth+1].brk; + } + + /* Since this isn't a RUNROUTINE_BLOCK, keep running this + iteration of RunRoutine() + */ + goto ContinueRunning; + + } + + if (stack_depth<0) stack_depth = 0; + + if (var[endflag]) return; + + +LeaveRunRoutine: + +#if defined (DEBUGGER) + +/* + * Finally, do any debugger-required cleaning-up + */ + + /* As noted above, wascalled is true if this was a routine call + as opposed to, e.g., a conditional block. In the former case, + it is necessary to print the "Returning from..." message. + */ + if (wascalled) + { + if (debugger_step_over) + { + if (--step_nest<=0) + { + debugger_step_over = false; + debugger_interrupt = true; + + if (debugger_finish || step_nest < 0) + { + debugger_finish = false; + goto ReturnfromRoutine; + } + } + } + + else if (!debugger_step_over) + { +ReturnfromRoutine: + sprintf(debug_line, "(Returning %d", ret); + + /* Since a complex property routine will give "<Routine>" as the + routine name, skip those + */ + called_from = RoutineName(currentroutine); + if (!trace_comp_prop && called_from[0]!='<') + sprintf(debug_line+strlen(debug_line), " from %s", called_from); + + if (old_currentroutine!=mainaddr && old_currentroutine!=initaddr + && currentroutine!=mainaddr && currentroutine!=initaddr) + { + sprintf(debug_line+strlen(debug_line), " to %s", RoutineName(old_currentroutine)); + } + strcat(debug_line, ")"); + AddStringtoCodeWindow(debug_line); + AddStringtoCodeWindow(""); + + if ((signed)--window[VIEW_CALLS].count < 0) + window[VIEW_CALLS].count = 0; + window[VIEW_CALLS].changed = true; + } + + currentroutine = old_currentroutine; + } + +/*#elif defined (DEBUG_CODE) + if (wascalled) + {sprintf(line, "[RETURNING %d]", ret); + AP(line);} +*/ +#endif + + return; +} + +#ifndef SAVEGAMEDATA_REPLACED + +int Hugo::SaveGameData() { + int c, j; + int lbyte, hbyte; + long i; + int samecount = 0; + + /* Write ID */ + if (hugo_fputc(id[0], save)==EOF || hugo_fputc(id[1], save)==EOF) goto SaveError; + + /* Write serial number */ + if (hugo_fputs(serial, save)==EOF) goto SaveError; + + /* Save variables */ + for (c=0; c<MAXGLOBALS+MAXLOCALS; c++) + { + hbyte = (unsigned int)var[c] / 256; + lbyte = (unsigned int)var[c] - hbyte * 256; + if (hugo_fputc(lbyte, save)==EOF || hugo_fputc(hbyte, save)==EOF) goto SaveError; + } + + /* Save objtable to end of code space */ + + if (hugo_fseek(game, objtable*16L, SEEK_SET)) goto SaveError; + + for (i=0; i<=codeend-(long)(objtable*16L); i++) + { + if ((lbyte = hugo_fgetc(game))==EOF) goto SaveError; + hbyte = MEM(objtable*16L+i); + + /* If memory same as original game file */ + if (lbyte==hbyte && samecount<255) samecount++; + + /* If memory differs (or samecount exceeds 1 byte) */ + else + { + if (samecount) + if (hugo_fputc(samecount, save)==EOF) goto SaveError; + + if (lbyte!=hbyte) + { + if (hugo_fputc(0, save)==EOF) goto SaveError; + if (hugo_fputc(hbyte, save)==EOF) goto SaveError; + samecount = 0; + } + else samecount = 1; + } + } + if (samecount) + if (hugo_fputc(samecount, save)==EOF) goto SaveError; + + /* Save undo data */ + + /* Save the number of turns in this port's undo stack */ + hbyte = (unsigned int)MAXUNDO / 256; + lbyte = (unsigned int)MAXUNDO - hbyte*256; + if (hugo_fputc(lbyte, save)==EOF || hugo_fputc(hbyte, save)==EOF) + goto SaveError; + for (c=0; c<MAXUNDO; c++) + { + for (j=0; j<5; j++) + { + hbyte = (unsigned int)undostack[c][j] / 256; + lbyte = (unsigned int)undostack[c][j] - hbyte*256; + if (hugo_fputc(lbyte, save)==EOF || hugo_fputc(hbyte, save)==EOF) + goto SaveError; + } + } + if (hugo_fputc(undoptr-(undoptr/256)*256, save)==EOF || hugo_fputc(undoptr/256, save)==EOF) + goto SaveError; + if (hugo_fputc(undoturn-(undoturn/256)*256, save)==EOF || hugo_fputc(undoturn/256, save)==EOF) + goto SaveError; + if (hugo_fputc(undoinvalid, save)==EOF || hugo_fputc(undorecord, save)==EOF) + goto SaveError; + + return true; + +SaveError: + return false; +} + +#endif // SAVEGAMEDATA_REPLACED + +int Hugo::RunSave() { +#ifdef PALMOS + /* Prevent simultaneous access to the same db record */ + int dummy = MEM(objtable*16L); +#endif + +#if !defined (GLK) + save = NULL; + + /* stdio implementation */ + hugo_getfilename("to save", savefile); + +#if defined (DEBUGGER) + if (debugger_collapsing) return 1; +#endif + if (!strcmp(line, gamefile)) return 0; + if (!strcmp(line, "")) return 0; + if (!hugo_overwrite(line)) return 0; + if (!(save = HUGO_FOPEN(line, "w+b"))) return 0; + +#else + /* Glk implementation */ + frefid_t savefile; + + save = NULL; + + savefile = glk_fileref_create_by_prompt(fileusage_SavedGame | fileusage_BinaryMode, + filemode_Write, 0); + if (!savefile) return 0; + save = glk_stream_open_file(savefile, filemode_Write, 0); + glk_fileref_destroy(savefile); + if (!save) return 0; + +#endif /* GLK */ + + if (!SaveGameData()) goto SaveError; + + if (hugo_fclose(save)) FatalError(WRITE_E); + save = NULL; + +#if !defined (GLK) + strcpy(savefile, line); +#endif + + return(1); + +SaveError: + if ((save) && hugo_fclose(save)) FatalError(WRITE_E); + save = NULL; + return 0; +} + +int Hugo::RunScriptSet() { + remaining = 0; + + switch (Peek(codeptr)) + { + case SCRIPTON_T: + { + if (!script) + { +#if !defined (GLK) + /* stdio implementation */ + hugo_getfilename("to begin transcription (or printer name)", scriptfile); +#if defined (DEBUGGER) + if (debugger_collapsing) return 1; +#endif + if (!strcmp(line, "")) return 0; + if (!hugo_overwrite(line)) return 0; + if (!(script = HUGO_FOPEN(line, "wt"))) + return (0); + strcpy(scriptfile, line); + +#else + /* Glk implementation */ + frefid_t fref; + + fref = glk_fileref_create_by_prompt(fileusage_Transcript | fileusage_TextMode, + filemode_Write, 0); + script = glk_stream_open_file(fref, filemode_Write, 0); + glk_fileref_destroy(fref); + if (!script) return (0); +#endif /* GLK */ + return 1; + } + break; + } + + case SCRIPTOFF_T: + { + if (script) + { + if (hugo_fclose(script)) return (0); + script = NULL; + return 1; + } + break; + } + } + return 0; +} + +int Hugo::RunString() { + int i, pos; + unsigned int aaddr; /* array address */ + unsigned int dword; /* dictionary word */ + unsigned int maxlen = 32767; + + codeptr += 2; /* skip "(" */ + + aaddr = GetValue(); + if (game_version>=22) + { + /* Convert to 16-bit word value */ + aaddr*=2; + + if (game_version>=23) + { + defseg = arraytable; + maxlen = PeekWord(aaddr); + defseg = gameseg; + + /* Space for array length */ + aaddr+=2; + } + } + + if (Peek(codeptr)==COMMA_T) codeptr++; + + dword = GetValue(); + + if (Peek(codeptr)==COMMA_T) codeptr++; + + if (Peek(codeptr)!=CLOSE_BRACKET_T) + maxlen = GetValue(); + if (Peek(codeptr)==CLOSE_BRACKET_T) codeptr++; + + strcpy(line, GetWord(dword)); + + defseg = arraytable; + pos = 0; + for (i=0; i<(int)strlen(line) && i<(int)maxlen; i++, pos++) + { + char a; + + SaveUndo(ARRAYDATA_T, aaddr, i, PeekWord(aaddr+i*2), 0); + + a = line[i]; + if (a=='\\') + ++i, a = SpecialChar(line, &i); + PokeWord(aaddr+pos*2, a); + } + PokeWord(aaddr+pos*2, 0); + + defseg = gameseg; + + return (i); +} + +int Hugo::RunSystem() { + codeptr++; + + /* Since the obsolete form of the system command is unimplemented, + simply get the parameter (in order to skip it), and exit the + function. + */ + if (game_version < 25) + { + GetValue(); + return 0; + } + + /* Otherwise, process the following system calls: */ + + codeptr++; /* skip opening bracket */ + + var[system_status] = 0; + + Flushpbuffer(); + + switch (GetValue()) + { + case 11: /* READ_KEY */ + if (!hugo_iskeywaiting()) + return 0; + else + { + full = 0; + return hugo_getkey(); + } + + case 21: /* NORMALIZE_RANDOM */ +#if !defined (RANDOM) + _random.setSeed(1); +#else + SRANDOM(1); +#endif + break; + case 22: /* INIT_RANDOM */ + { +#if !defined (RANDOM) + _random.setSeed(g_system->getMillis()); +#else + time_t seed; + SRANDOM((unsigned int)time((time_t *)&seed)); +#endif + break; + } + case 31: /* PAUSE_SECOND */ + if (!hugo_timewait(1)) + var[system_status] = STAT_UNAVAILABLE; + break; + + case 32: /* PAUSE_100TH_SECOND */ + if (!hugo_timewait(100)) + var[system_status] = STAT_UNAVAILABLE; + break; + + case 41: /* GAME_RESET */ + { + if (game_reset) + { + game_reset = 0; + return true; + } + return false; + } + + case 51: /* SYSTEM_TIME */ + { +#ifndef NO_STRFTIME + TimeDate td; + g_system->getTimeAndDate(td); + sprintf(parseerr, "%Y-%m-%d %H:%M:%S", td.tm_year, td.tm_mon, td.tm_mday, + td.tm_hour, td.tm_min, td.tm_sec); +#else + hugo_gettimeformatted(parseerr); +#endif + return true; + } + + case 61: /* MINIMAL_INTERFACE */ +#ifdef MINIMAL_INTERFACE + return true; +#else + return false; +#endif + + default: + var[system_status] = STAT_UNAVAILABLE; + } + + return 0; +} + +void Hugo::SaveWindowData(SAVED_WINDOW_DATA *spw) { + spw->left = physical_windowleft; + spw->top = physical_windowtop; + spw->right = physical_windowright; + spw->bottom = physical_windowbottom; + spw->width = physical_windowwidth; + spw->height = physical_windowheight; + spw->currentfont = currentfont; + spw->charwidth = charwidth; + spw->lineheight = lineheight; + spw->currentpos = currentpos; + spw->currentline = currentline; +} + +void Hugo::RestoreWindowData(SAVED_WINDOW_DATA *spw) { + physical_windowleft = spw->left; + physical_windowtop = spw->top; + physical_windowright = spw->right; + physical_windowbottom = spw->bottom; + physical_windowwidth = spw->width; + physical_windowheight = spw->height; + + charwidth = spw->charwidth; + lineheight = spw->lineheight; + currentpos = spw->currentpos; + currentline = spw->currentline; + +/* if (currentfont!=spw->currentfont) hugo_font((currentfont = spw->currentfont)); */ +} + +void Hugo::RunWindow() { + int top, bottom, left, right; + struct SAVED_WINDOW_DATA restorewindow; + int temp_current_text_y; + char restore_default_bgcolor; + int tempfull; + int temp_stack_depth = stack_depth; + HUGO_FILE tempscript; +#ifdef MINIMAL_WINDOWING + int last_lowest_windowbottom = lowest_windowbottom; +#endif + +#if defined (DEBUGGER) + unsigned char param_type; + int tempdbnest; + long param_start; +#endif + + Flushpbuffer(); + tempfull = full; + full = 0; + override_full = false; + + temp_current_text_y = current_text_y; + + tempscript = script; + script = false; + restore_default_bgcolor = default_bgcolor; + + /* v2.4 is the first version to support proper windowing */ + if (game_version>=24) + { + /* Set up default top, left, etc. as character coordinates, + and save the current physical window data + */ + left = physical_windowleft/FIXEDCHARWIDTH + 1; + top = physical_windowtop/FIXEDLINEHEIGHT + 1; + right = physical_windowright/FIXEDCHARWIDTH + 1; + bottom = physical_windowbottom/FIXEDLINEHEIGHT + 1; + + SaveWindowData(&restorewindow); + + /* if "window x1, y1, x2, y2" or "window n"... */ + if (MEM(++codeptr)!=EOL_T) + { +#if defined (DEBUGGER) + param_type = MEM(codeptr); + param_start = codeptr; +#endif + left = GetValue(); + if (MEM(codeptr++)==COMMA_T) + { + top = GetValue(); + if (MEM(codeptr++)==COMMA_T) + { + right = GetValue(); + if (MEM(codeptr++)==COMMA_T) + { + bottom = GetValue(); + codeptr++; + } + } + } + + /* if only one parameter, i.e., "window n" */ + else + { + if (left!=0) + { + bottom = left; + top = 1; + left = 1; + right = SCREENWIDTH/FIXEDCHARWIDTH; + } + + /* "window 0" restores full screen without + running a code block + */ + else + { +#if defined (DEBUGGER) + /* Here, check to see if left was 0 but the + statement wasn't really "window 0", but + rather something that evaluated to zero + */ + if (runtime_warnings) + { + if (param_type!=VALUE_T || param_start!=codeptr-4) + RuntimeWarning("Window size evaluates to zero"); + } +#endif + left = 1, top = 1; + right = SCREENWIDTH/FIXEDCHARWIDTH; + bottom = SCREENHEIGHT/FIXEDLINEHEIGHT; + physical_lowest_windowbottom = lowest_windowbottom = 0; + hugo_settextwindow(left, top, right, bottom); + goto LeaveWindow; + } + } + } + + /* ...or just "window", so use last window defaults */ + else + { + codeptr++; /* skip EOL */ + + left = last_window_left; + top = last_window_top; + right = last_window_right; + bottom = last_window_bottom; + } + + /* Remember, these are character/text coordinates */ + if (top < 1) top = 1; + if (left < 1) left = 1; + if (bottom < 1) bottom = 1; + if (right < 1) right = 1; + if (top > SCREENHEIGHT/FIXEDLINEHEIGHT) + top = SCREENHEIGHT/FIXEDLINEHEIGHT; + if (left > SCREENWIDTH/FIXEDCHARWIDTH) + left = SCREENWIDTH/FIXEDCHARWIDTH; + if (bottom > SCREENHEIGHT/FIXEDLINEHEIGHT) + bottom = SCREENHEIGHT/FIXEDLINEHEIGHT; + if (right > SCREENWIDTH/FIXEDCHARWIDTH) + right = SCREENWIDTH/FIXEDCHARWIDTH; + + /* Set the new text window */ + inwindow = true; + hugo_settextwindow(left, top, right, bottom); + hugo_settextpos(1, 1); + +#if defined (DEBUGGER) + tempdbnest = dbnest++; +#endif + SetStackFrame(stack_depth, RUNROUTINE_BLOCK, 0, 0); + + RunRoutine(codeptr); + +#if defined (DEBUGGER) + dbnest = tempdbnest; +#endif + stack_depth = temp_stack_depth; + + Flushpbuffer(); + + /* Restore the old window parameters */ + last_window_top = top; + last_window_bottom = bottom; + last_window_left = left; + last_window_right = right; + + /* Figure out what the lowest window bottom is that we need + to protect from scrolling + */ + if (bottom > lowest_windowbottom) + lowest_windowbottom = bottom; + +#ifdef MINIMAL_WINDOWING + if (minimal_windowing && illegal_window) + lowest_windowbottom = last_lowest_windowbottom; +#endif + /* (error situation--shouldn't happen) */ + if (lowest_windowbottom>=SCREENHEIGHT/FIXEDLINEHEIGHT) + lowest_windowbottom = 0; + + /* Restore the old text window */ + RestoreWindowData(&restorewindow); + + inwindow = false; + hugo_settextwindow(physical_windowleft/FIXEDCHARWIDTH + 1, + lowest_windowbottom + 1, + physical_windowright/FIXEDCHARWIDTH + 1, + physical_windowbottom/FIXEDLINEHEIGHT + 1); + + physical_lowest_windowbottom = lowest_windowbottom*FIXEDLINEHEIGHT; + } + + /* v2.3 and earlier supported a very simple version of + windowing: mainly just moving the top/scroll-off line + of the printable area to the bottom of the text printed + in the "window" block + */ + else + { + inwindow = true; + hugo_settextwindow(1, 1, + SCREENWIDTH/FIXEDCHARWIDTH, + SCREENHEIGHT/FIXEDLINEHEIGHT); + hugo_settextpos(1, 1); + + SetStackFrame(stack_depth, RUNROUTINE_BLOCK, 0, 0); + RunRoutine(++codeptr); + Flushpbuffer(); + + inwindow = false; + + stack_depth = temp_stack_depth; + + hugo_settextwindow(1, full+1, + SCREENWIDTH/FIXEDCHARWIDTH, + SCREENHEIGHT/FIXEDLINEHEIGHT); + + physical_lowest_windowbottom = full*lineheight; + } + +LeaveWindow: + + current_text_y = temp_current_text_y; + +#ifndef PALMOS + if (!current_text_y) + hugo_settextpos(1, physical_windowheight/lineheight); +#endif + current_text_x = 0; + currentpos = 0; + + default_bgcolor = restore_default_bgcolor; + script = tempscript; + + if (!override_full) + full = tempfull; + override_full = false; + + just_left_window = true; +} + +} // End of namespace Hugo +} // End of namespace Glk diff --git a/engines/glk/hugo/hugo.cpp b/engines/glk/hugo/hugo.cpp index f7d39a7068..bd8575b080 100644 --- a/engines/glk/hugo/hugo.cpp +++ b/engines/glk/hugo/hugo.cpp @@ -68,7 +68,10 @@ Hugo::Hugo(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gam debugger_finish(false), debugger_run(false), debugger_interrupt(false), debugger_skip(false), runtime_error(false), currentroutine(false), complex_prop_breakpoint(false), trace_complex_prop_routine(false), routines(0), - properties(0), current_locals(0), this_codeptr(0), debug_workspace(0), attributes(0) + properties(0), current_locals(0), this_codeptr(0), debug_workspace(0), attributes(0), + original_dictcount(0), buffered_code_lines(0), debugger_has_stepped_back(false), + debugger_step_back(false), debugger_collapsing(0), runaway_counter(0), history_count(0), + active_screen(0), step_nest(0), history_last(0) #endif { // heexpr @@ -107,6 +110,8 @@ Hugo::Hugo(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gam Common::fill(&propertyname[0], &propertyname[MAX_PROPERTY], (char *)nullptr); Common::fill(&codeline[0][0], &codeline[9][100], 0); Common::fill(&localname[0][0], &localname[9][100], 0); + Common::fill(&code_history[0], &code_history[MAX_CODE_HISTORY], 0); + Common::fill(&dbnest_history[0], &dbnest_history[MAX_CODE_HISTORY], 0); #endif } @@ -122,7 +127,7 @@ void Hugo::runGame() { LoadGame(); - PlayGame(); + playGame(); hugo_cleanup_screen(); diff --git a/engines/glk/hugo/hugo.h b/engines/glk/hugo/hugo.h index aa4deee21a..d022b1f2b0 100644 --- a/engines/glk/hugo/hugo.h +++ b/engines/glk/hugo/hugo.h @@ -24,6 +24,7 @@ #define GLK_HUGO_HUGO #include "common/scummsys.h" +#include "common/str.h" #include "glk/glk_api.h" #include "glk/hugo/htokens.h" #include "glk/hugo/hugo_defines.h" @@ -177,8 +178,8 @@ private: char parse_called_twice; char reparse_everything; char punc_string[64]; ///< punctuation string + bool full_buffer; - char full_buffer; /** * to MatchObject() * Necessary for proper disambiguation when addressing a character; @@ -223,13 +224,12 @@ private: bool debugger_interrupt; bool debugger_skip; bool runtime_error; - int currentroutine; + uint currentroutine; bool complex_prop_breakpoint; bool trace_complex_prop_routine; char *objectname[MAX_OBJECT]; char *propertyname[MAX_PROPERTY]; -// CODE code[999]; - CALL call[999]; + CALL call[MAXCALLS]; int routines; int properties; WINDOW window[99]; @@ -239,6 +239,20 @@ private: long this_codeptr; int debug_workspace; int attributes; + int original_dictcount; + int buffered_code_lines; + bool debugger_has_stepped_back; + bool debugger_step_back; + int debugger_collapsing; + int runaway_counter; + int history_count; + int active_screen; + int step_nest; + BREAKPOINT breakpoint[MAXBREAKPOINTS]; + BREAKPOINT watch[MAXBREAKPOINTS]; + int code_history[MAX_CODE_HISTORY]; + int dbnest_history[MAX_CODE_HISTORY]; + int history_last; #endif private: /** @@ -822,6 +836,86 @@ private: /**@}*/ /** + * \defgroup herun + * @{ + */ + + void RunDo(); + + void RunEvents(); + + void playGame(); + + void RunIf(char override); + + void RunInput(); + + /** + * (All the debugger range-checking is important because invalid memory writes due + * to invalid object location calculations are a good way to crash the system.) + */ + void RunMove(); + + void RunPrint(); + + int RunRestart(); + + int RestoreGameData(); + + int RunRestore(); + + /** + * This is the main loop for running each line of code in sequence; + * the main switch statement is based on the first token in each line. + * + * This routine is relatively complex, especially given the addition of debugger control. + * Basically it is structured like this: + * + * 1. If this is the debugger build, see what debugger information has to be set up upon + * calling this block of code + * + * 2. Get the next token, and as long as it isn't CLOSE_BRACE_T ('}')... + * + * 3. ...If this is the debugger build, see if there is a standing debugger_interrupt + * to pass control back to the debugger, and perform all operations for stepping + * tracing, breakpoints, etc. + * + * 4. ...See what token we're dealing with and execute accordingly + * + * 5. ...Loop back to (2) + * + * 6. If this is the debugger build, do whatever is necessary to tidy up after finishing + * this block of code + * + * There's a bit of a trick involved since the original language design uses "{...}" + * structures for both conditionals and blocks that necessitate another (i.e., nested) call + * to RunRoutine(). The call_block structure array and stack_depth variable are the + * navigation guides. + */ + void RunRoutine(long addr); + + int SaveGameData(); + + int RunSave(); + + int RunScriptSet(); + + /** + * As in 'x = string(<array>, "<string>"[, maxlen]'. + */ + int RunString(); + + int RunSystem(); + + void SaveWindowData(SAVED_WINDOW_DATA *spw); + + void RestoreWindowData(SAVED_WINDOW_DATA *spw); + + void RunWindow(); + + /**@}*/ + + /** * \defgroup Miscellaneous * @{ */ @@ -833,10 +927,71 @@ private: int hugo_fgetc(Common::SeekableReadStream *s) { return s->readByte(); } + int hugo_fgetc(strid_t s) { + Common::SeekableReadStream *ws = *s; + return hugo_fgetc(ws); + } + + int hugo_fputc(int c, Common::WriteStream *s) { + s->writeByte(c); + return s->err() ? EOF : 0; + } + int hugo_fputc(int c, strid_t s) { + Common::WriteStream *ws = *s; + return hugo_fputc(c, ws); + } + + char *hugo_fgets(char *buf, int max, Common::SeekableReadStream *s) { + char *ptr = buf; + char c; + while (s->pos() < s->size() && (c = hugo_fgetc(s)) != '\n') + *ptr++ = c; + return buffer; + } + char *hugo_fgets(char *buf, int max, strid_t s) { + Common::SeekableReadStream *rs = *s; + return hugo_fgets(buf, max, rs); + } + + size_t hugo_fread(void *ptr, size_t size, size_t count, Common::SeekableReadStream *s) { + return s->read(ptr, size * count); + } + + int hugo_fprintf(Common::WriteStream *s, const char *fmt, ...) { + va_list va; + va_start(va, fmt); + Common::String text = Common::String::vformat(fmt, va); + va_end(va); + + s->write(text.c_str(), text.size()); + return s->err() ? -1 : 0; + } + int hugo_fprintf(strid_t s, const char *fmt, ...) { + va_list va; + va_start(va, fmt); + Common::String text = Common::String::vformat(fmt, va); + va_end(va); + + Common::WriteStream *str = *s; + str->write(text.c_str(), text.size()); + return str->err() ? -1 : 0; + } + + int hugo_fputs(const char *str, Common::WriteStream *s) { + return s->write(str, strlen(str)) == strlen(str) ? 0 : -1; + } + int hugo_fputs(const char *str, strid_t s) { + Common::WriteStream *ws = *s; + return hugo_fputs(str, ws); + } bool hugo_ferror(Common::SeekableReadStream *s) const { return s->err(); } + bool hugo_ferror(strid_t s) const { + Common::SeekableReadStream *rs = *s; + return hugo_ferror(rs); + } long hugo_ftell(Common::SeekableReadStream *s) { return s->pos(); @@ -851,10 +1006,6 @@ private: error("%s", line); } - size_t hugo_fread(void *ptr, size_t size, size_t count, Common::SeekableReadStream *s) { - return s->read(ptr, size * count); - } - uint hugo_rand() { return _random.getRandomNumber(0xffffff); } @@ -894,9 +1045,23 @@ private: void SwitchtoDebugger() {} + void Debugger() {} + + void UpdateDebugScreen() {} + + void SwitchtoGame() {} + void DebuggerFatal(DEBUGGER_ERROR err) { error("Debugger error"); } + void AddLinetoCodeWindow(int lineNum) {} + void RecoverLastGood() {} + + void SetupWatchEval(int num) {} + + bool EvalWatch() { return false; } + + void RunSet(int v) {} #endif public: /** @@ -925,20 +1090,16 @@ public: virtual Common::Error saveGameData(strid_t file, const Common::String &desc) override; // TODO: Stubs to be Properly implemented - void PlayGame() {} void hugo_closefiles() {} - void RunRoutine(long v) {} unsigned int FindWord(const char *a) { return 0; } void hugo_stopsample() {} void hugo_stopmusic() {} int hugo_hasgraphics() { return 0; } int hugo_writetoscript(const char *s) { return 0; } - short RunSave() { return 0; } - short RunRestore() { return 0; } - short RunScriptSet() { return 0; } - short RunRestart() { return 0; } - short RunString() { return 0; } - short RunSystem() { return 0; } + void DisplayPicture() {} + void PlayMusic() {} + void PlaySample() {} + void PlayVideo() {} }; } // End of namespace Hugo diff --git a/engines/glk/hugo/hugo_defines.h b/engines/glk/hugo/hugo_defines.h index 5d69c847c2..02cfab1882 100644 --- a/engines/glk/hugo/hugo_defines.h +++ b/engines/glk/hugo/hugo_defines.h @@ -32,7 +32,7 @@ namespace Hugo { #define HEREVISION 3 #define HEINTERIM ".0" #define GLK -#define DEBUGGER +#define DEBUGGER 1 #define MAXOBJLIST 32 #define MAX_CONTEXT_COMMANDS 32 @@ -44,9 +44,10 @@ namespace Hugo { #define MAX_MOBJ 16 /* maximum number of matchable object words */ #define MAXBUFFER 255 #define MAXUNDO 1024 - +#define MAXCALLS 99 +#define MAXBREAKPOINTS 99 +#define MAX_CODE_HISTORY 99 #define CHARWIDTH 1 -#define STAT_UNAVAILABLE (-1) #define HUGO_FILE strid_t #define MAXPATH 256 @@ -149,13 +150,20 @@ browsing. #define TAIL_RECURSION_ROUTINE (-1) #define TAIL_RECURSION_PROPERTY (-2) +#define STAT_UNAVAILABLE ((short)-1) + +#define PRINTFATALERROR(a) error("%s", a) + #if defined (DEBUGGER) #define VIEW_CALLS 0 #define VIEW_LOCALS 1 #define CODE_WINDOW 2 -#endif +#define VIEW_BREAKPOINTS 3 +#define VIEW_WATCH 4 -#define PRINTFATALERROR(a) error("%s", a) +#define FORCE_REDRAW 1 + +#endif } // End of namespace Hugo } // End of namespace Glk diff --git a/engines/glk/hugo/hugo_types.h b/engines/glk/hugo/hugo_types.h index dacde03d54..fcdaa8ed27 100644 --- a/engines/glk/hugo/hugo_types.h +++ b/engines/glk/hugo/hugo_types.h @@ -100,6 +100,13 @@ struct pobject_structure { pobject_structure() : obj(0), type(0) {} }; +struct SAVED_WINDOW_DATA { + int left, top, right, bottom; + int width, height, charwidth, lineheight; + int currentpos, currentline; + int currentfont; +}; + /** * Structure used for navigating {...} blocks: */ @@ -133,8 +140,19 @@ struct CALL { struct WINDOW { int count; + bool changed; + + WINDOW() : count(99), changed(false) {} +}; + +struct BREAKPOINT { + bool isbreak; + long addr; + const char *in; + int count; - WINDOW() : count(99) {} + BREAKPOINT() : isbreak(false), addr(0), in(nullptr), count(0) { + } }; #endif diff --git a/engines/glk/module.mk b/engines/glk/module.mk index 00f5e35d56..00641dd854 100644 --- a/engines/glk/module.mk +++ b/engines/glk/module.mk @@ -77,6 +77,7 @@ MODULE_OBJS := \ hugo/hemisc.o \ hugo/heobject.o \ hugo/heparse.o \ + hugo/herun.o \ hugo/htokens.o \ hugo/hugo.o \ hugo/stringfn.o \ |