diff options
-rw-r--r-- | engines/glk/hugo/heglk.cpp | 4 | ||||
-rw-r--r-- | engines/glk/hugo/hemisc.cpp | 1899 | ||||
-rw-r--r-- | engines/glk/hugo/htokens.cpp | 79 | ||||
-rw-r--r-- | engines/glk/hugo/htokens.h | 100 | ||||
-rw-r--r-- | engines/glk/hugo/hugo.cpp | 45 | ||||
-rw-r--r-- | engines/glk/hugo/hugo.h | 255 | ||||
-rw-r--r-- | engines/glk/hugo/hugo_defines.h | 19 | ||||
-rw-r--r-- | engines/glk/hugo/hugo_types.h | 36 | ||||
-rw-r--r-- | engines/glk/hugo/stringfn.cpp | 110 | ||||
-rw-r--r-- | engines/glk/hugo/stringfn.h | 65 | ||||
-rw-r--r-- | engines/glk/module.mk | 3 |
11 files changed, 2599 insertions, 16 deletions
diff --git a/engines/glk/hugo/heglk.cpp b/engines/glk/hugo/heglk.cpp index 86be90c268..1d4586c9ab 100644 --- a/engines/glk/hugo/heglk.cpp +++ b/engines/glk/hugo/heglk.cpp @@ -82,8 +82,8 @@ void Hugo::hugo_getline(const char *prmpt) { /* Copy the input to the script file (if open) */ if (script) { - Common::String line = Common::String::format("%s%s\n", prompt, buffer); - script->putBuffer(line.c_str(), line.size()); + Common::String text = Common::String::format("%s%s\n", prompt, buffer); + script->putBuffer(text.c_str(), text.size()); } } diff --git a/engines/glk/hugo/hemisc.cpp b/engines/glk/hugo/hemisc.cpp new file mode 100644 index 0000000000..ac3a0d4dd0 --- /dev/null +++ b/engines/glk/hugo/hemisc.cpp @@ -0,0 +1,1899 @@ +/* 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::AP(const char *a) { + char sticky = false, skipspchar = false, startofline = 0; + int i, alen, plen, cwidth; + char c = 0; /* current character */ +#ifdef USE_SMARTFORMATTING + char lastc = 0; /* for smart formatting */ +#endif + + static int lastfcolor = 16, lastbgcolor = 17; + static int lastfont = NORMAL_FONT; + static int thisline = 0; /* width in pixels or characters */ + static int linebreaklen = 0, linebreak = 0; + int tempfont; + char printed_something = false; +#ifdef USE_TEXTBUFFER + int bufferfont = currentfont; +#endif + + /* Shameless little trick to override control characters in engine- + printed text, such as MS-DOS filenames that contain '\'s: + */ + if (a[0] == NO_CONTROLCHAR) + { + skipspchar = true; + a++; + } + + /* Semi-colon overrides LF */ + if ((strlen(a) >= 2) && a[strlen(a) - 1] == ';' && a[strlen(a) - 2] == '\\') + { + sticky = true; + } + + if (hugo_strlen(pbuffer)) + printed_something = true; + + plen = strlen(pbuffer); + if (plen == 0) + { + thisline = 0; + linebreak = 0; + linebreaklen = 0; + lastfont = currentfont; + startofline = true; +#ifdef USE_TEXTBUFFER + bufferbreak = 0; + bufferbreaklen = 0; +#endif +#ifdef USE_SMARTFORMATTING + leftquote = true; +#endif + } + + /* Check for color changes */ + if ((a[0]) && (lastfcolor != fcolor || lastbgcolor != bgcolor || startofline)) + { + if (plen >= MAXBUFFER * 2 - 3) FatalError(OVERFLOW_E); + pbuffer[plen++] = COLOR_CHANGE; + pbuffer[plen++] = (char)(fcolor + 1); + pbuffer[plen++] = (char)(bgcolor + 1); + pbuffer[plen] = '\0'; + lastfcolor = fcolor; + lastbgcolor = bgcolor; + } + + /* Check for font changes--since fonts can only get changed + by printing, we don't check lastfont */ + if ((a[0]) && startofline) + { + if (plen >= MAXBUFFER * 2 - 2) FatalError(OVERFLOW_E); + pbuffer[plen++] = FONT_CHANGE; + pbuffer[plen++] = (char)(currentfont + 1); + pbuffer[plen] = '\0'; + lastfont = currentfont; + } + + /* Begin by looping through the entire provided string: */ + + alen = (int)strlen(a); + if (sticky) + alen -= 2; + + /* Not printing any actual text, so we won't need to go through + queued font changes + */ + if (alen == 0) + lastfont = currentfont; + + for (i = 0; i<alen; i++) + { + c = a[i]; + + /* Left-justification */ + if (thisline == 0 && c == ' ' && !textto && currentpos == 0) + continue; + + /* First check control characters */ + if (c == '\\' && !skipspchar) + { + c = a[++i]; + + switch (c) + { + case 'n': + { + c = '\n'; + break; + } + case 'B': + { + currentfont |= BOLD_FONT; + goto AddFontCode; + } + case 'b': + { + currentfont &= ~BOLD_FONT; + goto AddFontCode; + } + case 'I': + { + currentfont |= ITALIC_FONT; + goto AddFontCode; + } + case 'i': + { + currentfont &= ~ITALIC_FONT; + goto AddFontCode; + } + case 'P': + { + currentfont |= PROP_FONT; + goto AddFontCode; + } + case 'p': + { + currentfont &= ~PROP_FONT; + goto AddFontCode; + } + case 'U': + { + currentfont |= UNDERLINE_FONT; + goto AddFontCode; + } + case 'u': + { + currentfont &= ~UNDERLINE_FONT; + AddFontCode: + if (!textto) + { + int m, n; + int newfull; + double ratio; + + if (!printed_something) + { + for (m = 0; m<plen;) + { + if (pbuffer[m] == FONT_CHANGE) + { + for (n = m; n<plen - 2; n++) + { + pbuffer[n] = pbuffer[n + 2]; + } + plen -= 2; + pbuffer[plen] = '\0'; + lastfont = currentfont; + } + else if (pbuffer[m] == COLOR_CHANGE) + m += 3; + else + break; + } + } +#ifdef USE_TEXTBUFFER + if (hugo_strlen(pbuffer + bufferbreak) == 0) + bufferfont = currentfont; +#endif + if (plen >= MAXBUFFER * 2 - 2) FatalError(OVERFLOW_E); + pbuffer[plen + 2] = '\0'; + pbuffer[plen + 1] = (char)(currentfont + 1); + pbuffer[plen] = FONT_CHANGE; + plen += 2; + + /* Convert full if font height changes, since + the amount of used screen real estate for + this particular font (in terms of lines) + will have changed: + */ + ratio = (double)lineheight; + hugo_font(currentfont); + ratio /= (double)lineheight; + newfull = (int)(((double)full)*ratio + 0.5); + if (newfull) full = newfull; + } + continue; + } + case '_': /* forced space */ + { + if (textto) c = ' '; + else c = FORCED_SPACE; + break; + } + default: + c = SpecialChar(a, &i); + } + } + else if (game_version <= 22) + { + if (c == '~') + c = '\"'; + else if (c == '^') + c = '\n'; + } + + /* Add the new character */ + + /* Text may be sent to an address in the array table instead + of being output to the screen + */ + if (textto) + { + /* space for array length */ + int n = (game_version>23) ? 2 : 0; + + if (c == '\n') + { + SETMEM(arraytable * 16L + textto * 2 + n, 0); + textto++; + } + else if ((unsigned char)c >= ' ') + { + SETMEM(arraytable * 16L + textto * 2 + n, c); + textto++; + } + /* Add a terminating zero in case we don't + print any more to the array */ + SETMEM(arraytable * 16L + textto * 2 + n, 0); + + if (i >= alen) return; + + continue; /* back to for (i=0; i<slen; i++) */ + } + + printed_something = true; + + /* Handle in-text newlines */ + if (c == '\n') + { + hugo_font(currentfont = lastfont); +#ifdef USE_TEXTBUFFER + TB_AddWord(pbuffer + bufferbreak, + current_text_x + bufferbreaklen, + current_text_y, + current_text_x + thisline - 1, + current_text_y + lineheight - 1); +#endif + Printout(pbuffer); + lastfont = currentfont; +#ifdef USE_TEXTBUFFER + bufferfont = currentfont; + bufferbreak = 0; + bufferbreaklen = 0; +#endif + strcpy(pbuffer, ""); + plen = 0; + linebreak = 0; + linebreaklen = 0; + thisline = 0; +#ifdef USE_SMARTFORMATTING + leftquote = true; + lastc = '\n'; +#endif + pbuffer[plen++] = COLOR_CHANGE; + pbuffer[plen++] = (char)(fcolor + 1); + pbuffer[plen++] = (char)(bgcolor + 1); + pbuffer[plen] = '\0'; + + + continue; + } + +#ifdef USE_SMARTFORMATTING + /* Smart formatting only for non-fixed fonts */ + if ((currentfont & PROP_FONT) && smartformatting) + { + if ((!strncmp(a + i, "--", 2)) && lastc != '-' && strncmp(a + i, "---", 3)) + { + lastc = '-'; + c = (char)151; + i++; + leftquote = false; + } + else if (c == '\"') + { + if (leftquote) + c = (char)147; + else + c = (char)148; + leftquote = false; + lastc = c; + } + else if (c == '\'') + { + if (leftquote) + c = (char)145; + else + c = (char)146; + leftquote = false; + lastc = c; + } + else + { + if (c == ' ') + leftquote = true; + else + leftquote = false; + lastc = c; + } + } +#endif + + /* Add the new character to the printing buffer */ + if (plen >= MAXBUFFER * 2 - 1) FatalError(OVERFLOW_E); + pbuffer[plen + 1] = '\0'; + pbuffer[plen] = c; + plen++; + + cwidth = hugo_charwidth(c); + + /* Check to see if we've overrun the current line */ + + if (thisline + cwidth + currentpos > physical_windowwidth) + { + char t; + + if (!linebreak) + { + linebreak = plen - 1; + linebreaklen = thisline; + } + + t = pbuffer[linebreak]; + pbuffer[linebreak] = '\0'; + + tempfont = currentfont; + hugo_font(currentfont = lastfont); + Printout(pbuffer); + lastfont = currentfont; + hugo_font(currentfont = tempfont); + + pbuffer[linebreak] = t; + plen = strlen(pbuffer + linebreak); + memmove(pbuffer, pbuffer + linebreak, plen + 1); + thisline = thisline - linebreaklen; + linebreak = 0; + linebreaklen = 0; + startofline = 0; +#ifdef USE_TEXTBUFFER + bufferbreak = 0; + bufferbreaklen = 0; +#endif + } + + thisline += cwidth; + +#ifdef USE_TEXTBUFFER + if ((c == ' ' || c == FORCED_SPACE) || + (c == '/' && a[i + 1] != '/') || (c == '-' && a[i + 1] != '-')) + { + TB_AddWord(pbuffer + bufferbreak, + current_text_x + bufferbreaklen, + current_text_y, + current_text_x + thisline - 1, + current_text_y + lineheight - 1); + + bufferbreak = plen; + bufferbreaklen = thisline; + bufferfont = currentfont; + } +#endif + if ((c == ' ') || (c == '/' && a[i + 1] != '/') || (c == '-' && a[i + 1] != '-')) + { + linebreak = plen, linebreaklen = thisline; + } + } + +#ifdef USE_TEXTBUFFER + if (!sticky || alen > 1) + { + tempfont = currentfont; + currentfont = bufferfont; + + TB_AddWord(pbuffer + bufferbreak, + current_text_x + bufferbreaklen, + current_text_y, + current_text_x + thisline - 1, + current_text_y + lineheight - 1); + + bufferbreak = plen; + bufferbreaklen = thisline; + currentfont = tempfont; + } +#endif + if (!sticky) + { + hugo_font(currentfont = lastfont); + Printout(pbuffer); + lastfont = currentfont; + strcpy(pbuffer, ""); + linebreak = 0; + linebreaklen = 0; + thisline = 0; + plen = 0; +#ifdef USE_TEXTBUFFER + bufferbreak = 0; + bufferbreaklen = 0; +#endif +#ifdef USE_SMARTFORMATTING + leftquote = true; +#endif + } + } + +int Hugo::CallRoutine(unsigned int addr) { + int arg, i; + int val; + int templocals[MAXLOCALS], temppass[MAXLOCALS]; + int temp_stack_depth; + long tempptr; + int potential_tail_recursion = tail_recursion; +#if defined (DEBUGGER) + int tempdbnest; +#endif + arg = 0; + tail_recursion = 0; + + /* Pass local variables to routine, if specified */ + if (MEM(codeptr) == OPEN_BRACKET_T) + { + codeptr++; + while (MEM(codeptr) != CLOSE_BRACKET_T) + { + if (arg) + { + for (i = 0; i<arg; i++) + temppass[i] = passlocal[i]; + } + + passlocal[arg++] = GetValue(); + + if (arg > 1) + { + for (i = 0; i<arg - 1; i++) + passlocal[i] = temppass[i]; + } + + if (MEM(codeptr) == COMMA_T) codeptr++; + } + codeptr++; + } + + /* TAIL_RECURSION_ROUTINE if we came from a routine call immediately + following a 'return' statement... + */ + tail_recursion = potential_tail_recursion; + if (tail_recursion == TAIL_RECURSION_ROUTINE && MEM(codeptr) == EOL_T) + { + tail_recursion_addr = (long)addr*address_scale; + PassLocals(arg); + return 0; + } + /* ...but if we're not immediately followed by and end-of-line marker, + cancel the pending tail-recursion + */ + else + { + tail_recursion = 0; + } + + for (i = 0; i<MAXLOCALS; i++) + templocals[i] = var[MAXGLOBALS + i]; + PassLocals(arg); + + temp_stack_depth = stack_depth; + + SetStackFrame(stack_depth, RUNROUTINE_BLOCK, 0, 0); + + tempptr = codeptr; /* store calling address */ + ret = 0; + +#if defined (DEBUGGER) + tempdbnest = dbnest; + DebugRunRoutine((long)addr*address_scale); + dbnest = tempdbnest; +#else + RunRoutine((long)addr*address_scale); +#endif + retflag = 0; + val = ret; + codeptr = tempptr; + + stack_depth = temp_stack_depth; + + for (i = 0; i<MAXLOCALS; i++) + var[MAXGLOBALS + i] = templocals[i]; + + return val; + } + +void Hugo::ContextCommand() { + unsigned int n; + + ContextCommandLoop: + + codeptr++; + + n = GetValue(); +#if !defined (COMPILE_V25) + if (n == 0) + { + context_commands = 0; + } + else if (context_commands < MAX_CONTEXT_COMMANDS) + { + char *cc; + + strncpy(context_command[context_commands], cc = GetWord(n), 64); + context_command[context_commands][63] = '\0'; + if (strlen(cc) >= 64) + sprintf(context_command[context_commands] + 60, "..."); + context_commands++; + } +#endif + if (Peek(codeptr) == COMMA_T) goto ContextCommandLoop; + codeptr++; + } + +unsigned int Hugo::Dict() { + int i, len = 256; + unsigned int arr; + unsigned int pos = 2, loc; + + codeptr += 2; /* "(" */ + + if (MEM(codeptr) == PARSE_T || MEM(codeptr) == WORD_T) + strcpy(line, GetWord(GetValue())); + else + { + /* Get the array address to read the to-be- + created dictionary entry from: + */ + arr = GetValue(); + if (game_version >= 22) + { + /* Convert the address to a word + value: + */ + arr *= 2; + + if (game_version >= 23) + /* space for array length */ + arr += 2; + } + + defseg = arraytable; + for (i = 0; i<len && PeekWord(arr + i * 2) != 0; i++) + line[i] = (char)PeekWord(arr + i * 2); + defseg = gameseg; + line[i] = '\0'; + } + + if (Peek(codeptr) == COMMA_T) codeptr++; + len = GetValue(); + + if ((loc = FindWord(line)) != UNKNOWN_WORD) return loc; + + defseg = dicttable; + + for (i = 1; i <= dictcount; i++) + pos += Peek(pos) + 1; + + loc = pos - 2; + + if ((long)(pos + strlen(line)) > (long)(codeend - dicttable * 16L)) + { +#ifdef DEBUGGER + sprintf(debug_line, "$MAXDICTEXTEND dictionary space exceeded"); + RuntimeWarning(debug_line); +#endif + defseg = gameseg; + return 0; + } + + Poke(pos++, (unsigned char)strlen(line)); + for (i = 0; i<(int)strlen(line) && i<len; i++) + Poke(pos++, (unsigned char)(line[i] + CHAR_TRANSLATION)); + PokeWord(0, ++dictcount); + + defseg = gameseg; + + SaveUndo(DICT_T, strlen(line), 0, 0, 0); + + return loc; + } + +void Hugo::FatalError(int n){ + char fatalerrorline[64]; + + hugo_cleanup_screen(); + + switch (n) + { + case MEMORY_E: + {sprintf(line, "Out of memory\n"); + break; } + + case OPEN_E: + {sprintf(line, "Cannot open file\n"); + break; } + + case READ_E: + {sprintf(line, "Cannot read from file\n"); + break; } + + case WRITE_E: + {sprintf(line, "Cannot write to save file\n"); + break; } + + case EXPECT_VAL_E: + {sprintf(line, "Expecting value at $%s\n", PrintHex(codeptr)); + break; } + + case UNKNOWN_OP_E: + {sprintf(line, "Unknown operation at $%s\n", PrintHex(codeptr)); + break; } + + case ILLEGAL_OP_E: + {sprintf(line, "Illegal operation at $%s\n", PrintHex(codeptr)); + break; } + + case OVERFLOW_E: + {sprintf(line, "Overflow at $%s\n", PrintHex(codeptr)); + break; } + + case DIVIDE_E: + {sprintf(line, "Divide by zero at $%s\n", PrintHex(codeptr)); + break; } + } + + sprintf(fatalerrorline, "\nFatal Error: %s", line); + error(fatalerrorline); +} + +void Hugo::FileIO() { + char fileiopath[MAXPATH]; + char iotype; + unsigned int fnameval; + long skipaddr; + int i, temp_stack_depth = stack_depth; + + iotype = MEM(codeptr++); + skipaddr = (long)PeekWord(codeptr)*address_scale; + codeptr += 2; + fnameval = GetValue(); + if (game_version >= 23) codeptr++; /* eol */ + + ioerror = 0; + + /* Make sure the filename is legal, 8 alphanumeric characters or less */ + strcpy(line, GetWord(fnameval)); + if (strlen(line) > 8) goto LeaveFileIO; + for (i = 0; i<(int)strlen(line); i++) + { + if ((line[i] >= '0' && line[i] <= '9') || (line[i] >= 'A' && line[i] <= 'Z') || + (line[i] >= 'a' && line[i] <= 'z')) + { + continue; + } + else + goto LeaveFileIO; + } + + if (ioblock) goto LeaveFileIO; /* can't nest file operations */ + + strcpy(fileiopath, GetWord(fnameval)); + + if (iotype == WRITEFILE_T) /* "writefile" */ + { + /* Glk implementation */ + frefid_t fref = nullptr; + + fref = glk_fileref_create_by_name(fileusage_Data | fileusage_BinaryMode, + fileiopath, 0); + io = glk_stream_open_file(fref, filemode_Write, 0); + glk_fileref_destroy(fref); + if (io == nullptr) goto LeaveFileIO; + + ioblock = 1; + } + else /* "readfile" */ + { + /* Glk implementation */ + frefid_t fref = nullptr; + + fref = glk_fileref_create_by_name(fileusage_Data | fileusage_BinaryMode, + fileiopath, 0); + if (glk_fileref_does_file_exist(fref)) + io = glk_stream_open_file(fref, filemode_Read, 0); + else + io = nullptr; + glk_fileref_destroy(fref); + if (io == nullptr) goto LeaveFileIO; + + ioblock = 2; + } + + SetStackFrame(stack_depth, RUNROUTINE_BLOCK, 0, 0); + + RunRoutine(codeptr); + + stack_depth = temp_stack_depth; + + if (ioerror) retflag = 0; + + delete io; + io = nullptr; + ioblock = 0; + + LeaveFileIO: + ioerror = 0; + codeptr = skipaddr; + } + +void Hugo::Flushpbuffer() { + if (pbuffer[0] == '\0') return; + +#ifdef USE_TEXTBUFFER + /* For (single) characters left over from AP(), when Flushpbuffer() gets + called, say, from RunWindow() + */ + if (bufferbreaklen && pbuffer + bufferbreaklen && !(currentfont&PROP_FONT)) + { + TB_AddWord(pbuffer + bufferbreak, + current_text_x + bufferbreaklen, + current_text_y, + current_text_x + bufferbreaklen + FIXEDCHARWIDTH, + current_text_y + lineheight - 1); + } +#endif + + pbuffer[strlen(pbuffer) + 1] = '\0'; + pbuffer[strlen(pbuffer)] = (char)NO_NEWLINE; + Printout(Ltrim(pbuffer)); + currentpos = hugo_textwidth(pbuffer); /* -charwidth; */ + strcpy(pbuffer, ""); + } + +void Hugo::GetCommand() { + char a[256]; +#ifdef USE_TEXTBUFFER + int start, width, i, y; +#endif + Flushpbuffer(); + AP(""); + + hugo_settextcolor(fcolor); + hugo_setbackcolor(bgcolor); + if (icolor == -1) + icolor = fcolor; /* check unset input color */ + + strncpy(a, GetWord(var[prompt]), 255); + during_player_input = true; + full = 0; +#ifdef USE_TEXTBUFFER + /* Add the prompt to the textbuffer (using TB_Add()) */ + y = current_text_y; + width = hugo_textwidth(GetWord(var[prompt])); + TB_AddWord(GetWord(var[prompt]), physical_windowleft, y, + physical_windowleft + width, y + lineheight - 1); + + hugo_getline(a); + + /* If hugo_scrollwindowup() called by hugo_getline() shifted things */ + if (current_text_y > y) + { + y += (current_text_y - y); + } + + /* Add each word in the input buffer */ + start = 0; + for (i = 0; i<(int)strlen(buffer); i++) + { + if (buffer[i] == ' ') + { + buffer[i] = '\0'; + TB_AddWord(buffer + start, physical_windowleft + width, y - lineheight, + physical_windowleft + width + hugo_textwidth(buffer + start), y - 1); + width += hugo_textwidth(buffer + start) + hugo_textwidth(" "); + start = i + 1; + buffer[i] = ' '; + } + } + /* Add the final word */ + TB_AddWord(buffer + start, physical_windowleft + width, y - lineheight, + physical_windowleft + width + hugo_textwidth(buffer + start), y - 1); +#else + hugo_getline(a); +#endif + during_player_input = false; + strcpy(buffer, Rtrim(buffer)); + + strcpy(parseerr, ""); + + full = 1; + remaining = 0; + } + +char *Hugo::GetString(long addr) { + static char a[256]; + int i, length; + + length = Peek(addr); + + for (i = 1; i <= length; i++) + a[i - 1] = (char)(Peek(addr + i) - CHAR_TRANSLATION); + a[i - 1] = '\0'; + + return a; +} + +char *Hugo::GetText(long textaddr) { + static char g[1025]; + int i, a; + int tdatal, tdatah, tlen; /* low byte, high byte, length */ + + + /* Read the string from memory... */ + if (loaded_in_memory) + { + tlen = MEM(codeend + textaddr) + MEM(codeend + textaddr + 1) * 256; + for (i = 0; i<tlen; i++) + { + g[i] = (char)(MEM(codeend + textaddr + 2 + i) - CHAR_TRANSLATION); + } + g[i] = '\0'; + + return g; + } + + /* ...Or load the string from disk */ + if (game->seek(codeend + textaddr)) FatalError(READ_E); + + tdatal = game->readByte(); + tdatah = game->readByte(); + if (tdatal == EOF || tdatah == EOF || game->err()) FatalError(READ_E); + + tlen = tdatal + tdatah * 256; + + for (i = 0; i<tlen; i++) + { + if ((a = game->readByte()) == EOF) FatalError(READ_E); + g[i] = (char)(a - CHAR_TRANSLATION); + } + g[i] = '\0'; + + return g; +} + +char *Hugo::GetWord(unsigned int w) { + static char *b; + unsigned short a; + + a = w; + + if (a == 0) return ""; + + if (a == PARSE_STRING_VAL) return parseerr; + if (a == SERIAL_STRING_VAL) return serial; + + /* bounds-checking to avoid some sort of memory arena error */ + if ((long)(a + dicttable * 16L) > codeend) + { + b = ""; + return b; + } + + defseg = dicttable; + b = GetString((long)a + 2); + defseg = gameseg; + + return b; +} + +void Hugo::HandleTailRecursion(long addr) { + codeptr = addr; + + /* Set up proper default return value for property or routine */ + if (tail_recursion == TAIL_RECURSION_PROPERTY) + ret = 1; + else + ret = 0; + + /* Unstack until we get to any routine call that got us here */ + while (stack_depth) + { + if (code_block[stack_depth].type == RUNROUTINE_BLOCK) + break; + stack_depth--; + } + +#ifdef DEBUGGER + currentroutine = (unsigned int)(codeptr / address_scale); + call[window[VIEW_CALLS].count - 1].addr = currentroutine; + call[window[VIEW_CALLS].count - 1].param = true; + + sprintf(debug_line, "Calling: %s", RoutineName(currentroutine)); + /* Don't duplicate blank separator line in code window */ + if (codeline[window[CODE_WINDOW].count - 1][0] != 0) + AddStringtoCodeWindow(""); + AddStringtoCodeWindow(debug_line); + + /* Adjust for very long runs */ + dbnest--; +#endif + + tail_recursion = 0; + tail_recursion_addr = 0; +} + +void Hugo::InitGame() { + int i; + + /* Stop any audio if this is a restart */ + hugo_stopsample(); + hugo_stopmusic(); + +#if !defined (COMPILE_V25) + hugo_stopvideo(); + context_commands = 0; +#endif + game_reset = false; + + SetStackFrame(stack_depth, RUNROUTINE_BLOCK, 0, 0); + + /* Figure out which objects have either a noun or an adjective property; + store it in obj_parselist, one bit per object */ + if ((!obj_parselist) && (obj_parselist = (char *)hugo_blockalloc(sizeof(char)*((objects + 7) / 8)))) + { + + for (i = 0; i<objects; i++) + { + if (i % 8 == 0) obj_parselist[i / 8] = 0; + + if (PropAddr(i, adjective, 0) || PropAddr(i, noun, 0)) + obj_parselist[i / 8] |= 1 << (i % 8); + else + obj_parselist[i / 8] &= ~(1 << (i % 8)); + } + } + +#if defined (DEBUGGER) + for (i = 0; i<MAXLOCALS; i++) strcpy(localname[i], ""); + window[VIEW_LOCALS].count = current_locals = 0; + + PassLocals(0); + DebugRunRoutine((long)initaddr*address_scale); +#else + PassLocals(0); + RunRoutine((long)initaddr*address_scale); +#endif + + ret = 0; + retflag = 0; + var[actor] = var[player]; +} + +void Hugo::LoadGame() { + int i, data; + unsigned int synptr; + size_t ccount; + int check_version; + long textbank, filelength; +#ifndef LOADGAMEDATA_REPLACED + long c; +#endif + game = &_gameFile; + filelength = game->size(); + game->seek(0); + + if (game->err()) FatalError(READ_E); + if ((game_version = game->readByte()) == EOF) FatalError(OPEN_E); + + /* Earlier versions of the compiler wrote the version code as + 1 or 2 instead of 10 or 20. + */ + if (game_version == 1 || game_version == 2) + game_version *= 10; + + if (game_version < 21) object_size = 12; + + if (game_version < 31) address_scale = 4; + + check_version = HEVERSION * 10 + HEREVISION; + + defseg = gameseg; + + if (game_version < HEVERSION) + { + sprintf(line, "Hugo Compiler v%d.%d or later required.\n", HEVERSION, HEREVISION); + if (game_version>0) + sprintf(line + strlen(line), "File \"%s\" is v%d.%d.\n", gamefile, game_version / 10, game_version % 10); + + error(line); + } + else if (game_version > check_version) + { + error("File \"%s\" is incorrect or unknown version.\n", gamefile); + } + + hugo_settextpos(1, physical_windowheight / lineheight); + + if (game_version >= 25) + game->seek(H_TEXTBANK, SEEK_SET); + else + /* Because pre-v2.5 didn't have performaddr in the header */ + game->seek(H_TEXTBANK - 2L, SEEK_SET); + + data = game->readByte(); + textbank = game->readByte(); + if (data == EOF || textbank == EOF || game->err()) FatalError(READ_E); + textbank = (textbank * 256L + (long)data) * 16L; + codeend = textbank; + + /* Use a 1024-byte read block */ + ccount = 1024; + + if (game->seek(0, SEEK_SET)) FatalError(READ_E); + +#ifndef LOADGAMEDATA_REPLACED + /* Allocate as much memory as is required */ + if ((!loaded_in_memory) || (mem = (unsigned char *)hugo_blockalloc(filelength)) == nullptr) + { + loaded_in_memory = 0; + if ((mem = (unsigned char *)hugo_blockalloc(codeend)) == nullptr) + FatalError(MEMORY_E); + } + + c = 0; + + /* Load either the entire file or just up to the start of the + text bank + */ + while (c < (loaded_in_memory ? filelength : codeend)) + { + /* Complicated, but basically just makes sure that + the last read (whether loaded_in_memory or not) + doesn't override the end of the file. Shouldn't + normally be a problem for fread(), but it caused + a crash under MSVC++. + */ + i = game->read((unsigned char *)&mem[c], + (loaded_in_memory) ? + ((filelength - c>(long)ccount) ? ccount : (size_t)(filelength - c)) : + ((codeend - c>(long)ccount) ? ccount : (size_t)(codeend - c))); + + if (!i) break; + c += i; + } +#else + if (!LoadGameData(false)) FatalError(READ_E); +#endif + + if (game->err()) FatalError(READ_E); + + defseg = gameseg; + + /* Read header: */ + + id[0] = Peek(H_ID); + id[1] = Peek(H_ID + 1); + id[2] = '\0'; + + for (i = 0; i<8; i++) + serial[i] = Peek(H_SERIAL + i); + serial[8] = '\0'; + + codestart = PeekWord(H_CODESTART); + objtable = PeekWord(H_OBJTABLE) + gameseg; + proptable = PeekWord(H_PROPTABLE) + gameseg; + eventtable = PeekWord(H_EVENTTABLE) + gameseg; + arraytable = PeekWord(H_ARRAYTABLE) + gameseg; + dicttable = PeekWord(H_DICTTABLE) + gameseg; + syntable = PeekWord(H_SYNTABLE) + gameseg; + + initaddr = PeekWord(H_INIT); + mainaddr = PeekWord(H_MAIN); + parseaddr = PeekWord(H_PARSE); + parseerroraddr = PeekWord(H_PARSEERROR); + findobjectaddr = PeekWord(H_FINDOBJECT); + endgameaddr = PeekWord(H_ENDGAME); + speaktoaddr = PeekWord(H_SPEAKTO); + performaddr = PeekWord(H_PERFORM); + + + /* Read totals: */ + + defseg = objtable; + objects = PeekWord(0); + + defseg = eventtable; + events = PeekWord(0); + + defseg = dicttable; + dictcount = PeekWord(0); + + defseg = syntable; + syncount = PeekWord(0); + + + /* Additional information to be found: */ + + /* display object */ + if (game_version >= 24) + { + data = FindWord("(display)"); + + for (i = 0; i<objects; i++) + { + if (GetProp(i, 0, 1, true) == data) + { + display_object = i; + break; + } + } + } + + /* build punctuation string (additional user-specified punctuation) */ + synptr = 2; + strcpy(punc_string, ""); + for (i = 1; i <= syncount; i++) + { + defseg = syntable; + if (Peek(synptr) == 3) /* 3 = punctuation */ + { + strcpy(line, GetWord(PeekWord(synptr + 1))); + if (strlen(line) + strlen(punc_string) > 63) break; + strcat(punc_string, line); + } + synptr += 5; + } +} + +void Hugo::PassLocals(int n) { + int i; + + for (i = 0; i<MAXLOCALS; i++) + { + var[MAXGLOBALS + i] = passlocal[i]; + passlocal[i] = 0; + } + arguments_passed = n; +} + +const char *Hugo::PrintHex(long a) { + static char hex[7]; + int h = 0; + + strcpy(hex, ""); + + if (a < 0L) a = 0; + + hex[h++] = '0'; + if (a < 65536L) hex[h++] = '0'; + if (a < 4096L) hex[h++] = '0'; + if (a < 256L) hex[h++] = '0'; + if (a < 16L) hex[h++] = '0'; + + sprintf(hex + h, "%lX", a); + + return hex; +} + +void Hugo::Printout(char *a) { + char b[2], sticky = 0, trimmed = 0; + char tempfcolor; + int i, l; + int n; + int last_printed_font = currentfont; + + /* hugo_font() should do this if necessary, but just in case */ + if (lineheight < FIXEDLINEHEIGHT) + lineheight = FIXEDLINEHEIGHT; + + tempfcolor = fcolor; + + /* The before-check of the linecount: */ + if (full) + { + /* -1 here since it's before printing */ + if (full >= physical_windowheight / lineheight - 1) + PromptMore(); + } + + if ((a[0] != '\0') && a[strlen(a) - 1] == (char)NO_NEWLINE) + { + a[strlen(a) - 1] = '\0'; + sticky = true; + } + + b[0] = b[1] = '\0'; + + + /* The easy part is just skimming <a> and processing each code + or printed character, as the case may be: + */ + + l = 0; /* physical length of string */ + + for (i = 0; i<(int)strlen(a); i++) + { + if ((a[i] == ' ') && !trimmed && currentpos == 0) + { + continue; + } + + if ((unsigned char)a[i] > ' ' || a[i] == FORCED_SPACE) + { + trimmed = true; + last_printed_font = currentfont; + } + + switch (b[0] = a[i]) + { + case FONT_CHANGE: + n = (int)(a[++i] - 1); + if (currentfont != n) + hugo_font(currentfont = n); + break; + + case COLOR_CHANGE: + fcolor = (char)(a[++i] - 1); + hugo_settextcolor((int)fcolor); + hugo_setbackcolor((int)(a[++i] - 1)); + hugo_font(currentfont); + break; + + default: + if (b[0] == FORCED_SPACE) b[0] = ' '; + l += hugo_charwidth(b[0]); + + /* A minor adjustment for font changes and RunWindow() to make + sure we're not printing unnecessarily downscreen + */ + if ((just_left_window) && current_text_y > physical_windowbottom - lineheight) + { + current_text_y = physical_windowbottom - lineheight; + } + just_left_window = false; + + hugo_print(b); + } + + if (script && (unsigned char)b[0] >= ' ') + script->putBuffer(b, strlen(b)); + +#if defined (SCROLLBACK_DEFINED) + if (!inwindow && (unsigned char)b[0] >= ' ') + { +#ifdef USE_SMARTFORMATTING + /* Undo smart-formatting for ASCII scrollback */ + switch ((unsigned char)b[0]) + { + case 151: + hugo_sendtoscrollback("--"); + continue; + case 145: + case 146: + b[0] = '\''; + break; + case 147: + case 148: + b[0] = '\"'; + } +#endif + hugo_sendtoscrollback(b); + } +#endif + } + + /* If we've got a linefeed and didn't hit the right edge of the + window + */ +#ifdef NO_TERMINAL_LINEFEED + if (!sticky) +#else + if (!sticky && currentpos + l < physical_windowwidth) +#endif + { + /* The background color may have to be temporarily set if we're + not in a window--the reason is that full lines of the + current background color might be printed by the OS-specific + scrolling function. (This behavior is overridden by the + Hugo Engine for in-window printing, which always adds new + lines in the current background color when scrolling.) + */ + hugo_setbackcolor((inwindow) ? bgcolor : default_bgcolor); + hugo_print("\r"); + + i = currentfont; + hugo_font(currentfont = last_printed_font); + +#ifndef GLK + if (currentline > physical_windowheight / lineheight) + { + int full_limit = physical_windowheight / lineheight; + + hugo_scrollwindowup(); + + if ((current_text_y) + && full >= full_limit - 3 + && physical_windowbottom - current_text_y - lineheight > lineheight / 2) + { + PromptMore(); + } + currentline = full_limit; + } + + /* Don't scroll single-line windows before PromptMore() */ + else if (physical_windowheight / lineheight > 1) +#endif + { + hugo_print("\n"); + } + + hugo_font(currentfont = i); + hugo_setbackcolor(bgcolor); + } + +#if defined (AMIGA) + else + { + if (currentpos + l >= physical_windowwidth) + AmigaForceFlush(); + } +#endif + just_left_window = false; + + /* If no newline is to be printed after the current line: */ + if (sticky) + { + currentpos += l; + } + + /* Otherwise, take care of all the line-feeding, line-counting, + etc. + */ + else + { + currentpos = 0; + if (currentline++ > physical_windowheight / lineheight) + currentline = physical_windowheight / lineheight; + + if (!playback) skipping_more = false; + + ++full; + + /* The after-check of the linecount: */ + if ((full) && full >= physical_windowheight / lineheight) + { + PromptMore(); + } + + if (script) + { + script->putBuffer("\n", 1); + } + +#if defined (SCROLLBACK_DEFINED) + if (!inwindow) hugo_sendtoscrollback("\n"); +#endif + } + + fcolor = tempfcolor; +} + +int Hugo::RecordCommands() { + remaining = 0; + skipping_more = false; + + switch (Peek(codeptr)) + { + case RECORDON_T: + { + if (!record && !playback) + { + /* Glk implementation */ + frefid_t fref = nullptr; + + fref = glk_fileref_create_by_prompt(fileusage_Transcript | fileusage_TextMode, + filemode_Write, 0); + record = glk_stream_open_file(fref, filemode_Write, 0); + glk_fileref_destroy(fref); + if (!record) + return 0; + + return 1; + } + break; + } + + case RECORDOFF_T: + { + if (playback) return 1; + + if (record) + { + delete record; + record = nullptr; + return 1; + } + break; + } + + case PLAYBACK_T: + { + if (!playback) + { + /* Glk implementation */ + frefid_t fref = nullptr; + + fref = glk_fileref_create_by_prompt(fileusage_InputRecord | fileusage_TextMode, + filemode_Read, 0); + if (glk_fileref_does_file_exist(fref)) + playback = glk_stream_open_file(fref, filemode_Read, 0); + else + playback = nullptr; + glk_fileref_destroy(fref); + if (!playback) + return 0; + + return 1; + } + break; + } + } + return 0; +} + +void Hugo::SaveUndo(int a, int b, int c, int d, int e) { + int tempptr; + + if (undorecord) + { + undostack[undoptr][0] = a; /* save the operation */ + undostack[undoptr][1] = b; + undostack[undoptr][2] = c; + undostack[undoptr][3] = d; + undostack[undoptr][4] = e; + + /* Put zeroes at end of this operation in case + the stack wraps around */ + tempptr = undoptr; + if (++undoptr == MAXUNDO) undoptr = 0; + undostack[undoptr][0] = 0; + undostack[undoptr][1] = 0; + undoptr = tempptr; + + if (++undoturn == MAXUNDO) /* turn too complex */ + { + undoptr = 0; + undoturn = MAXUNDO; + undoinvalid = 1; + } + + if (++undoptr == MAXUNDO) undoptr = 0; + } +} + +void Hugo::SetStackFrame(int depth, int type, long brk, long returnaddr) { + if (depth == RESET_STACK_DEPTH) stack_depth = 0; + else if (++stack_depth >= MAXSTACKDEPTH) FatalError(MEMORY_E); + + code_block[stack_depth].type = type; + code_block[stack_depth].brk = brk; + code_block[stack_depth].returnaddr = returnaddr; +} + +void Hugo::SetupDisplay() { + hugo_settextmode(); + + hugo_settextwindow(1, 1, + SCREENWIDTH / FIXEDCHARWIDTH, SCREENHEIGHT / FIXEDLINEHEIGHT); + + last_window_left = 1; + last_window_top = 1; + last_window_right = SCREENWIDTH / FIXEDCHARWIDTH; + last_window_bottom = SCREENHEIGHT / FIXEDLINEHEIGHT; + + hugo_settextcolor(16); + hugo_setbackcolor(17); + hugo_clearfullscreen(); +} + +char Hugo::SpecialChar(const char *a, int *i) { + char r, s, skipbracket = 0; + + r = a[*i]; + s = r; + + if (r == '\"') return r; + + /* For a couple of versions, Hugo allowed Inform-style + punctuation control characters; I don't remember + exactly why. + */ + if (game_version <= 22) + if (r == '~' || r == '^') return r; + + if (r == '(') + { + r = a[++*i]; + skipbracket = true; + } + + switch (r) + { + case '`': /* accent grave */ + { + /* Note that the "s = '...'" characters are + Latin-1 and may not display properly under, + e.g., DOS */ + + s = a[++*i]; +#ifndef NO_LATIN1_CHARSET + switch (s) + { + case 'a': s = (char)0xe0; break; /* ? */ + case 'e': s = (char)0xe8; break; /* ? */ + case 'i': s = (char)0xec; break; /* ? */ + case 'o': s = (char)0xf2; break; /* ? */ + case 'u': s = (char)0xf9; break; /* ? */ + case 'A': s = (char)0xc0; break; /* ? */ + case 'E': s = (char)0xc8; break; /* ? */ + case 'I': s = (char)0xcc; break; /* ? */ + case 'O': s = (char)0xd2; break; /* ? */ + case 'U': s = (char)0xd9; break; /* ? */ + } +#endif + break; + } + case '\'': /* accent acute */ + { + s = a[++*i]; +#ifndef NO_LATIN1_CHARSET + switch (s) + { + case 'a': s = (char)0xe1; break; /* ? */ + case 'e': s = (char)0xe9; break; /* ? */ + case 'i': s = (char)0xed; break; /* ? */ + case 'o': s = (char)0xf3; break; /* ? */ + case 'u': s = (char)0xfa; break; /* ? */ + case 'y': s = (char)0xfd; break; + case 'A': s = (char)0xc1; break; /* ? */ + case 'E': s = (char)0xc9; break; /* ? */ + case 'I': s = (char)0xcd; break; /* ? */ + case 'O': s = (char)0xd3; break; /* ? */ + case 'U': s = (char)0xda; break; /* ? */ + case 'Y': s = (char)0xdd; break; /* ? */ + } +#endif + break; + } + case '~': /* tilde */ + { + s = a[++*i]; +#ifndef NO_LATIN1_CHARSET + switch (s) + { + case 'a': s = (char)0xe3; break; /* ? */ + case 'n': s = (char)0xf1; break; /* ? */ + case 'o': s = (char)0xf5; break; /* ? */ + case 'A': s = (char)0xc3; break; /* ? */ + case 'N': s = (char)0xd1; break; /* ? */ + case 'O': s = (char)0xd5; break; /* ? */ + } +#endif + break; + } + case '^': /* circumflex */ + { + s = a[++*i]; +#ifndef NO_LATIN1_CHARSET + switch (s) + { + case 'a': s = (char)0xe2; break; /* ? */ + case 'e': s = (char)0xea; break; /* ? */ + case 'i': s = (char)0xee; break; /* ? */ + case 'o': s = (char)0xf4; break; /* ? */ + case 'u': s = (char)0xfb; break; /* ? */ + case 'A': s = (char)0xc2; break; /* ? */ + case 'E': s = (char)0xca; break; /* ? */ + case 'I': s = (char)0xce; break; /* ? */ + case 'O': s = (char)0xd4; break; /* ? */ + case 'U': s = (char)0xdb; break; /* ? */ + } +#endif + break; + } + case ':': /* umlaut */ + { + s = a[++*i]; +#ifndef NO_LATIN1_CHARSET + switch (s) + { + case 'a': s = (char)0xe4; break; /* ? */ + case 'e': s = (char)0xeb; break; /* ? */ + case 'i': s = (char)0xef; break; /* ? */ + case 'o': s = (char)0xf6; break; /* ? */ + case 'u': s = (char)0xfc; break; /* ? */ + /* case 'y': s = (char)0xff; break; */ /* ? */ + case 'A': s = (char)0xc4; break; /* ? */ + case 'E': s = (char)0xcb; break; /* ? */ + case 'I': s = (char)0xcf; break; /* ? */ + case 'O': s = (char)0xd6; break; /* ? */ + case 'U': s = (char)0xdc; break; /* ? */ + } +#endif + break; + } + case ',': /* cedilla */ + { + s = a[++*i]; +#ifndef NO_LATIN1_CHARSET + switch (s) + { + case 'C': s = (char)0xc7; break; /* ? */ + case 'c': s = (char)0xe7; break; /* ? */ + } +#endif + break; + } + case '<': /* Spanish left quotation marks */ +#ifndef NO_LATIN1_CHARSET + s = (char)0xab; /* ? */ +#endif + break; + case '>': /* Spanish right quotation marks */ +#ifndef NO_LATIN1_CHARSET + s = (char)0xbb; /* ? */ + break; +#endif + case '!': /* upside-down exclamation mark */ +#ifndef NO_LATIN1_CHARSET + s = (char)0xa1; /* ? */ +#endif + break; + case '?': /* upside-down question mark */ +#ifndef NO_LATIN1_CHARSET + s = (char)0xbf; /* ? */ +#endif + break; + case 'a': /* ae ligature */ +#ifndef NO_LATIN1_CHARSET + s = (char)0xe6; ++*i; /* ? */ +#else + s = 'e'; ++*i; +#endif + break; + case 'A': /* AE ligature */ +#ifndef NO_LATIN1_CHARSET + s = (char)0xc6; ++*i; /* ? */ +#else + s = 'E'; ++*i; +#endif + break; + case 'c': /* cents symbol */ +#ifndef NO_LATIN1_CHARSET + s = (char)0xa2; /* ? */ +#endif + break; + case 'L': /* British pound */ +#ifndef NO_LATIN1_CHARSET + s = (char)0xa3; /* ? */ +#endif + break; + case 'Y': /* Japanese Yen */ +#ifndef NO_LATIN1_CHARSET + s = (char)0xa5; /* ? */ +#endif + break; + case '-': /* em dash */ +#ifndef NO_LATIN1_CHARSET + /* s = (char)0x97; */ /* ? */ +#endif + break; + case '#': /* 3-digit decimal code */ + { + s = (char)((a[++*i] - '0') * 100); + s += (a[++*i] - '0') * 10; + s += (a[++*i] - '0'); +#ifdef NO_LATIN1_CHARSET + if ((unsigned)s>127) s = '?'; +#endif + } + } + + if (skipbracket) + { + ++*i; + if (a[*i + 1] == ')')++*i; + if (s == ')') s = r; + } + + return s; +} + +int Hugo::Undo() { + int count = 0, n; + int turns, turncount, tempptr; + int obj, prop, attr, v; + unsigned int addr; + + if (--undoptr < 0) undoptr = MAXUNDO - 1; + + if (undostack[undoptr][1] != 0) + { + /* Get the number of operations to be undone for + the last turn. + */ + if ((turns = undostack[undoptr][1]) >= MAXUNDO) + goto CheckUndoFailed; + + turns--; + + turncount = 0; + tempptr = undoptr; + + /* Count the number of operations available to see if there + are enough to undo the last turn (as per the number + required in <turns>. + */ + do + { + if (--undoptr < 0) undoptr = MAXUNDO - 1; + turncount++; + + /* if end of turn */ + if (undostack[undoptr][0] == 0) + break; + } while (true); + + if (turncount<turns) goto CheckUndoFailed; + + undoptr = tempptr; + + if (--undoptr < 0) undoptr = MAXUNDO - 1; + + while (undostack[undoptr][0] != 0) + { + switch (undostack[undoptr][0]) + { + case MOVE_T: + { + MoveObj(undostack[undoptr][1], undostack[undoptr][2]); + count++; + break; + } + + case PROP_T: + { + obj = undostack[undoptr][1]; + prop = undostack[undoptr][2]; + n = undostack[undoptr][3]; + v = undostack[undoptr][4]; + + if ((addr = PropAddr(obj, prop, 0)) != 0) + { + defseg = proptable; + + if (n == PROP_ROUTINE) + { + Poke(addr + 1, PROP_ROUTINE); + n = 1; + } + + /* Use this new prop count number if the + existing one is too low or a prop routine + */ + else if (Peek(addr + 1) == PROP_ROUTINE || Peek(addr + 1)<(unsigned char)n) + Poke(addr + 1, (unsigned char)n); + + /* property length */ + if (n <= (int)Peek(addr + 1)) + PokeWord(addr + 2 + (n - 1) * 2, v); + } + count++; + break; + } + + case ATTR_T: + { + obj = undostack[undoptr][1]; + attr = undostack[undoptr][2]; + n = undostack[undoptr][3]; + SetAttribute(obj, attr, n); + count++; + break; + } + + case VAR_T: + { + n = undostack[undoptr][1]; + v = undostack[undoptr][2]; + var[n] = v; + count++; + break; + } + + case ARRAYDATA_T: + { + defseg = arraytable; + addr = undostack[undoptr][1]; + n = undostack[undoptr][2]; + v = undostack[undoptr][3]; + + /* The array length was already accounted for before calling + SaveUndo(), so there is no adjustment of + +2 here. + */ + PokeWord(addr + n * 2, v); + count++; + break; + } + + case DICT_T: + { + defseg = dicttable; + PokeWord(0, --dictcount); + count++; + break; + } + case WORD_T: + { + n = undostack[undoptr][1]; + v = undostack[undoptr][2]; + wd[n] = v; + word[n] = GetWord(wd[n]); + count++; + } + } + defseg = gameseg; + + if (--undoptr < 0) undoptr = MAXUNDO - 1; + } + } + +CheckUndoFailed: + if (!count) + { + undoinvalid = 1; + game_reset = false; + return 0; + } + + game_reset = true; + + undoptr++; + return 1; +} + +} // End of namespace Hugo +} // End of namespace Glk diff --git a/engines/glk/hugo/htokens.cpp b/engines/glk/hugo/htokens.cpp new file mode 100644 index 0000000000..fd913b260e --- /dev/null +++ b/engines/glk/hugo/htokens.cpp @@ -0,0 +1,79 @@ +/* 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/htokens.h" +#include "Common/algorithm.h" + +namespace Glk { +namespace Hugo { + +const char *const HTokens::token[] = { + /* 0x00 - 0x0f */ + "", "(", ")", ".", ":", "=", "-", "+", + "*", "/", "|", ";", "{", "}", "[", "]", + + /* 0x10 - 0x1f */ + "#", "~", ">=", "<=", "~=", "&", ">", "<", + "if", ",", "else", "elseif", "while", "do", "select", "case", + + /* 0x20 - 0x2f */ + "for", "return", "break", "and", "or", "jump", "run", "is", + "not", "true", "false", "local", "verb", "xverb", "held", "multi", + + /* 0x30 - 0x3f */ + "multiheld", "newline", "anything", "print", + "number", "capital", "text", "graphics", + "color", "remove", "move", "to", + "parent", "sibling", "child", "youngest", + + /* 0x40 - 0x4f */ + "eldest", "younger", "elder", "prop#", + "attr#", "var#", "dictentry#", "textdata#", + "routine#","debugdata#","objectnum#", "value#", + "eol#", "system", "notheld", "multinotheld", + + /* 0x50 - 0x5f */ + "window", "random", "word", "locate", + "parse$", "children", "in", "pause", + "runevents", "arraydata#", "call", "stringdata#", + "save", "restore", "quit", "input", + + /* 0x60 - 0x6f */ + "serial$", "cls", "scripton", "scriptoff", + "restart", "hex", "object", "xobject", + "string", "array", "printchar", "undo", + "dict", "recordon", "recordoff", "writefile", + + /* 0x70 - */ + "readfile", "writeval", "readval", "playback", + "colour", "picture", "label#", "sound", + "music", "repeat", "addcontext", "video" +}; + +int HTokens::token_hash[TOKENS + 1]; + +HTokens::HTokens() { + Common::fill(&token_hash[0], &token_hash[TOKENS + 1], 0); +} + +} // End of namespace Hugo +} // End of namespace Glk diff --git a/engines/glk/hugo/htokens.h b/engines/glk/hugo/htokens.h new file mode 100644 index 0000000000..08961c7bc0 --- /dev/null +++ b/engines/glk/hugo/htokens.h @@ -0,0 +1,100 @@ +/* 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. + * + */ + +#ifndef GLK_HUGO_HTOKENS +#define GLK_HUGO_HTOKENS + +namespace Glk { +namespace Hugo { + +/* + * This file contains token definitions for the Hugo Compiler and Engine + * The enum constants of type TOKEN_T reflect the token names given in the token array. + * Token names followed by a # are for system use. + */ + +/* i.e., highest numbered token */ +#define TOKENS 0x7B + +/* arbitrary */ +#define HASH_KEY 1023 + +enum TOKEN_T { + /* 0x00 - 0x0f */ + NULL_T, OPEN_BRACKET_T, CLOSE_BRACKET_T, DECIMAL_T, + COLON_T, EQUALS_T, MINUS_T, PLUS_T, + ASTERISK_T, FORWARD_SLASH_T, PIPE_T, SEMICOLON_T, + OPEN_BRACE_T, CLOSE_BRACE_T, OPEN_SQUARE_T, CLOSE_SQUARE_T, + + /* 0x10 - 0x1f */ + POUND_T, TILDE_T, GREATER_EQUAL_T, LESS_EQUAL_T, + NOT_EQUAL_T, AMPERSAND_T, GREATER_T, LESS_T, + IF_T, COMMA_T, ELSE_T, ELSEIF_T, + WHILE_T, DO_T, SELECT_T, CASE_T, + + /* 0x20 - 0x2f */ + FOR_T, RETURN_T, BREAK_T, AND_T, + OR_T, JUMP_T, RUN_T, IS_T, + NOT_T, TRUE_T, FALSE_T, LOCAL_T, + VERB_T, XVERB_T, HELD_T, MULTI_T, + + /* 0x30 - 0x3f */ + MULTIHELD_T, NEWLINE_T, ANYTHING_T, PRINT_T, + NUMBER_T, CAPITAL_T, TEXT_T, GRAPHICS_T, + COLOR_T, REMOVE_T, MOVE_T, TO_T, + PARENT_T, SIBLING_T, CHILD_T, YOUNGEST_T, + + /* 0x40 - 0x4f */ + ELDEST_T, YOUNGER_T, ELDER_T, PROP_T, + ATTR_T, VAR_T, DICTENTRY_T, TEXTDATA_T, + ROUTINE_T, DEBUGDATA_T, OBJECTNUM_T, VALUE_T, + EOL_T, SYSTEM_T, NOTHELD_T, MULTINOTHELD_T, + + /* 0x50 - 0x5f */ + WINDOW_T, RANDOM_T, WORD_T, LOCATE_T, + PARSE_T, CHILDREN_T, IN_T, PAUSE_T, + RUNEVENTS_T, ARRAYDATA_T, CALL_T, STRINGDATA_T, + SAVE_T, RESTORE_T, QUIT_T, INPUT_T, + + /* 0x60 - 0x6f */ + SERIAL_T, CLS_T, SCRIPTON_T, SCRIPTOFF_T, + RESTART_T, HEX_T, OBJECT_T, XOBJECT_T, + STRING_T, ARRAY_T, PRINTCHAR_T, UNDO_T, + DICT_T, RECORDON_T, RECORDOFF_T, WRITEFILE_T, + + /* 0x70 - */ + READFILE_T, WRITEVAL_T, READVAL_T, PLAYBACK_T, + COLOUR_T, PICTURE_T, LABEL_T, SOUND_T, + MUSIC_T, REPEAT_T, ADDCONTEXT_T, VIDEO_T +}; + +struct HTokens { + static const char *const token[]; + static int token_hash[]; + + HTokens(); +}; + +} // End of namespace Hugo +} // End of namespace Glk + +#endif diff --git a/engines/glk/hugo/hugo.cpp b/engines/glk/hugo/hugo.cpp index afc788e59c..8e8113421a 100644 --- a/engines/glk/hugo/hugo.cpp +++ b/engines/glk/hugo/hugo.cpp @@ -40,29 +40,54 @@ Hugo::Hugo(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gam physical_windowwidth(0), physical_windowheight(0), physical_windowtop(0), physical_windowleft(0), physical_windowbottom(0), physical_windowright(0), inwindow(0), charwidth(0), lineheight(0), current_text_x(0), current_text_y(0), - undoptr(0), undoturn(0), undoinvalid(0), undorecord(0), context_commands(0), - in_valid_window(false), glk_fcolor(DEF_FCOLOR), glk_bgcolor(DEF_BGCOLOR), - mainwin_bgcolor(0), glk_current_font(0), just_cleared_screen(false), secondwin_bottom(0) { + skipping_more(false), undoptr(0), undoturn(0), undoinvalid(0), undorecord(0), + context_commands(0), in_valid_window(false), glk_fcolor(DEF_FCOLOR), glk_bgcolor(DEF_BGCOLOR), + mainwin_bgcolor(0), glk_current_font(0), just_cleared_screen(false), secondwin_bottom(0), + // heobject + display_object(-1), display_needs_repaint(0), display_pointer_x(0), display_pointer_y(0), + // heparse + words(0), parsed_number(0), remaining(0), xverb(0), starts_with_verb(0), + grammaraddr(0), obj_parselist(nullptr), domain(0), odomain(0), objcount(0), + parse_allflag(false), pobjcount(0), pobj(0), obj_match_state(0), object_is_number(0), + objgrammar(0), objstart(0), objfinish(0), addflag(0), speaking(0), oopscount(0), + parse_called_twice(0), reparse_everything(0), full_buffer(false), recursive_call(false), + parse_location(0), + + // herun + arguments_passed(0), ret(0), retflag(0), during_player_input(false), override_full(0), + game_reset(false), stack_depth(0), tail_recursion(0), tail_recursion_addr(0), + last_window_top(0), last_window_bottom(0), last_window_left(0), last_window_right(0), + lowest_windowbottom(0), physical_lowest_windowbottom(0), just_left_window(false) { Common::fill(&context_command[0][0], &context_command[MAX_CONTEXT_COMMANDS][64], 0); Common::fill(&id[0], &id[3], '\0'); Common::fill(&serial[0], &serial[9], '\0'); Common::fill(&pbuffer[0], &pbuffer[MAXBUFFER * 2 + 1], 0); Common::fill(&undostack[0][0], &undostack[MAXUNDO][5], 0); - Common::fill(&buffer[0], &buffer[MAXBUFFER + MAXWORDS], '\0'); Common::fill(&var[0], &var[MAXLOCALS + MAXGLOBALS], 0); -} + + // heparse + Common::fill(&buffer[0], &buffer[MAXBUFFER + MAXWORDS], '\0'); + Common::fill(&errbuf[0], &errbuf[MAXBUFFER + 1], 0); + Common::fill(&line[0], &line[1025], 0); + Common::fill(&word[0], &word[MAXWORDS + 1], nullptr); + Common::fill(&wd[0], &wd[MAXWORDS + 1], 0); + Common::fill(&parseerr[0], &parseerr[MAXBUFFER + 1], '\0'); + Common::fill(&parsestr[0], &parsestr[MAXBUFFER + 1], '\0'); + Common::fill(&objlist[0], &objlist[MAXOBJLIST], 0); + Common::fill(&objword_cache[0], &objword_cache[MAXWORDS], 0); + Common::fill(&oops[0], &oops[MAXBUFFER + 1], '\0'); + Common::fill(&punc_string[0], &punc_string[64], '\0'); -// TODO: Proper method implementations -void SetupDisplay() {} -void LoadGame() {} -void PlayGame() {} -void hugo_closefiles() {} + // herun + Common::fill(&passlocal[0], &passlocal[MAXLOCALS], 0); +} void Hugo::runGame() { hugo_init_screen(); SetupDisplay(); + strcpy(gamefile, getFilename().c_str()); strcpy(pbuffer, ""); gameseg = 0; diff --git a/engines/glk/hugo/hugo.h b/engines/glk/hugo/hugo.h index 7d12646e7a..a33c5998c8 100644 --- a/engines/glk/hugo/hugo.h +++ b/engines/glk/hugo/hugo.h @@ -25,8 +25,10 @@ #include "common/scummsys.h" #include "glk/glk_api.h" +#include "glk/hugo/htokens.h" #include "glk/hugo/hugo_defines.h" #include "glk/hugo/hugo_types.h" +#include "glk/hugo/stringfn.h" namespace Glk { namespace Hugo { @@ -34,7 +36,7 @@ namespace Hugo { /** * Hugo game interpreter */ -class Hugo : public GlkAPI { +class Hugo : public GlkAPI, public HTokens, public StringFunctions { private: winid_t mainwin, currentwin; winid_t secondwin, auxwin; @@ -46,9 +48,12 @@ private: */ int address_scale; + // hemisc + char gamefile[255]; int game_version; int object_size; - HUGO_FILE game; + //HUGO_FILE game; + Common::SeekableReadStream *game; HUGO_FILE script; HUGO_FILE save; HUGO_FILE playback; @@ -100,6 +105,7 @@ private: int inwindow; int charwidth, lineheight, FIXEDCHARWIDTH, FIXEDLINEHEIGHT; int current_text_x, current_text_y; + bool skipping_more; int undostack[MAXUNDO][5]; int undoptr; int undoturn; @@ -113,11 +119,83 @@ private: bool just_cleared_screen; int secondwin_bottom; + // heexpr + int var[MAXLOCALS + MAXGLOBALS]; + + // heobject + int display_object; ///< i.e., non-existent (yet) + char display_needs_repaint; ///< for display object + int display_pointer_x, display_pointer_y; + // heparse char buffer[MAXBUFFER + MAXWORDS]; + char errbuf[MAXBUFFER + 1]; ///< last invalid input + char line[1025]; ///< line buffer + + int words; ///< parsed word count + char *word[MAXWORDS + 1]; ///< breakdown into words + unsigned int wd[MAXWORDS + 1]; ///< " " dict. entries + unsigned int parsed_number; ///< needed for numbers in input + + signed char remaining; ///< multiple commands in input + char parseerr[MAXBUFFER + 1]; ///< for passing to RunPrint, etc. + char parsestr[MAXBUFFER + 1]; ///< for passing quoted string + char xverb; ///< flag; 0 = regular verb + char starts_with_verb; ///< input line; 0 = no verb word + unsigned int grammaraddr; ///< address in grammar + char *obj_parselist; ///< objects with noun/adjective + int domain, odomain; ///< of object(s) + int objlist[MAXOBJLIST]; ///< for objects of verb + char objcount; ///< of objlist + char parse_allflag; ///< for "all" in MatchObject + pobject_structure pobjlist[MAXPOBJECTS]; ///< for possible objects + int pobjcount; ///< of pobjlist + int pobj; ///< last remaining suspect + int obj_match_state; ///< see MatchCommand() for details + char objword_cache[MAXWORDS]; ///< for MatchWord() xobject, etc. + char object_is_number; ///< number used in player command + unsigned int objgrammar; ///< for 2nd pass + int objstart; ///< " " " + int objfinish; ///< " " " + char addflag; ///< true if adding to objlist[] + int speaking; ///< if command is addressed to obj. + + char oops[MAXBUFFER + 1]; ///< illegal word + int oopscount; ///< # of corrections in a row + + char parse_called_twice; + char reparse_everything; + char punc_string[64]; ///< punctuation string + + char full_buffer; + /** + * to MatchObject() + * Necessary for proper disambiguation when addressing a character; + * i.e., when 'held' doesn't refer to held by the player, etc. + */ + char recursive_call; + int parse_location; ///< usually var[location] + + // herun + int passlocal[MAXLOCALS]; ///< locals passed to routine + int arguments_passed; ///< when calling routine + int ret; ///< return value and returning flag + char retflag; + bool during_player_input; + char override_full; + bool game_reset; ///< for restore, undo, etc. + + CODE_BLOCK code_block[MAXSTACKDEPTH]; + int stack_depth; + int tail_recursion; + long tail_recursion_addr; + + // Used by RunWindow for setting current window dimensions: + int last_window_top, last_window_bottom, last_window_left, last_window_right; + int lowest_windowbottom, ///< in text lines + physical_lowest_windowbottom; ///< in pixels or text lines + bool just_left_window; - // heexpr - int var[MAXLOCALS + MAXGLOBALS]; private: /** * \defgroup heglk @@ -260,6 +338,161 @@ private: void hugo_stopvideo(); /**@}*/ + + /** + * \defgroup hemisc + * @{ + */ + + /** + * The all-purpose printing routine that takes care of word-wrapping. + */ + void AP(const char *a); + + /** + * Used whenever a routine is called, assumes the routine address and begins + * with the arguments (if any). + */ + int CallRoutine(unsigned int addr); + + /** + * Adds a command to the context command list. A zero value (i.e., an empty string) + * resets the list. + */ + void ContextCommand(); + + /** + * Dynamically creates a new dictionary entry. + */ + unsigned int Dict(); + + /** + * Generates a fatal error + */ + void FatalError(int n); + + void FileIO(); + + void Flushpbuffer(); + + void GetCommand(); + + /** + * From any address <addr>; the segment must be defined prior to calling the function. + */ + char *GetString(long addr); + + /** + * Get text block from position <textaddr> in the text bank. If the game was not fully loaded + * in memory, i.e., if loaded_in_memory is not true, the block is read from disk. + */ + char *GetText(long textaddr); + + /** + * From the dictionary table. + */ + char *GetWord(unsigned int w); + + void HandleTailRecursion(long addr); + + void InitGame(); + + void LoadGame(); + + /** + * Must be called before running every new routine, i.e. before calling RunRoutine(). + * Unfortunately, the current locals must be saved in a temp array prior to calling. + * The argument n gives the number of arguments passed. + */ + void PassLocals(int n); + + inline unsigned char Peek(long a) { + return MEM(defseg * 16L + a); + } + + inline unsigned int PeekWord(long a) { + return (unsigned char)MEM(defseg * 16L + a) + (unsigned char)MEM(defseg * 16L + a + 1) * 256; + } + + inline void Poke(unsigned int a, unsigned char v) { + SETMEM(defseg * 16L + a, v); + } + + inline void PokeWord(unsigned int a, unsigned int v) { + SETMEM(defseg * 16L + a, (char)(v % 256)); + SETMEM(defseg * 16L + a + 1, (char)(v / 256)); + } + + /** + * Returns <a> as a hex-number string in XXXXXX format. + */ + static const char *PrintHex(long a); + + /** + * Print to client display taking into account cursor relocation, + * font changes, color setting, and window scrolling. + */ + void Printout(char *a); + + int RecordCommands(); + + /** + * Formats: + * + * end of turn: (0, undoturn, 0, 0, 0) + * move obj.: (MOVE_T, obj., parent, 0, 0) + * property: (PROP_T, obj., prop., # or PROP_ROUTINE, val.) + * attribute: (ATTR_T, obj., attr., 0 or 1, 0) + * variable: (VAR_T, var., value, 0, 0) + * array: (ARRAYDATA_T, array addr., element, val., 0) + * dict: (DICT_T, entry length, 0, 0, 0) + * word setting: (WORD_T, word number, new word, 0, 0) + */ + void SaveUndo(int a, int b, int c, int d, int e); + + /** + * Properly sets up the code_block structure for the current stack depth depending + * on if this is a called block (RUNROUTINE_BLOCK) or otherwise. + */ + void SetStackFrame(int depth, int type, long brk, long returnaddr); + + void SetupDisplay(); + + /** + * The method is passed <a> as the string and <*i> as the position in the string. + * The character(s) at a[*i], a[*(i+1)], etc. are converted into a single Latin-1 + * (i.e., greater than 127) character value. + * + * Assume that the AP() has already encountered a control character ('\'), + * and that a[*i]... is one of: + * + * `a accent grave on following character (e.g., 'a') + * 'a accent acute on following character (e.g., 'a') + * ~n tilde on following (e.g., 'n' or 'N') + * :a umlaut on following (e.g., 'a') + * ^a circumflex on following (e.g., 'a') + * ,c cedilla on following (e.g., 'c' or 'C') + * < Spanish left quotation marks + * > Spanish right quotation marks + * ! upside-down exclamation mark + * ? upside-down question mark + * ae ae ligature + * AE AE ligature + * c cents symbol + * L British pound + * Y Japanese Yen + * - em (long) dash + * #nnn character value given by nnn + * + * Note that the return value is a single character--which will be either unchanged + * or a Latin-1 character value. + */ + char SpecialChar(const char *a, int *i); + + int Undo(); + + + /**@}*/ private: /** * Allocate memory block @@ -292,6 +525,20 @@ public: * Save the game to the passed stream */ virtual Common::Error saveGameData(strid_t file, const Common::String &desc) override; + + // TODO: Stubs to be Properly implemented + int GetValue() { return 0; } + void PlayGame() {} + void hugo_closefiles() {} + void RunRoutine(long v) {} + unsigned int FindWord(const char *a) { return 0; } + void PromptMore() {} + void hugo_stopsample() {} + void hugo_stopmusic() {} + void SetAttribute(int obj, int attr, int c) {} + unsigned int PropAddr(int obj, int p, unsigned int offset) { return 0; } + int GetProp(int obj, int p, int n, char s) { return 0; } + void MoveObj(int obj, int p) {} }; } // End of namespace Hugo diff --git a/engines/glk/hugo/hugo_defines.h b/engines/glk/hugo/hugo_defines.h index caa1755b82..260586eab3 100644 --- a/engines/glk/hugo/hugo_defines.h +++ b/engines/glk/hugo/hugo_defines.h @@ -28,6 +28,11 @@ namespace Glk { namespace Hugo { +#define HEVERSION 3 +#define HEREVISION 3 +#define HEINTERIM ".0" + +#define MAXOBJLIST 32 #define MAX_CONTEXT_COMMANDS 32 #define MAXBUFFER 255 #define MAXUNDO 1024 @@ -121,6 +126,20 @@ browsing. #define PROP_END 255 #define PROP_ROUTINE 255 +#define MEM(addr) (mem[addr]) +#define SETMEM(addr, n) (mem[addr] = n) +#define GETMEMADDR(addr) (&mem[addr]) +#define HUGO_PTR + +#define RESET_STACK_DEPTH (-1) + +#define RUNROUTINE_BLOCK 1 +#define CONDITIONAL_BLOCK 2 +#define DOWHILE_BLOCK 3 + +#define TAIL_RECURSION_ROUTINE (-1) +#define TAIL_RECURSION_PROPERTY (-2) + } // 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 43b2899f93..9298510504 100644 --- a/engines/glk/hugo/hugo_types.h +++ b/engines/glk/hugo/hugo_types.h @@ -75,6 +75,42 @@ enum ObjectProperties { pointer_y = 12 }; +/** + * Fatal errors + */ +enum ERROR_TYPE { + MEMORY_E = 1, ///< out of memory + OPEN_E, ///< error opening file + READ_E, ///< error reading from file + WRITE_E, ///< error writing to file + EXPECT_VAL_E, ///< expecting value + UNKNOWN_OP_E, ///< unknown operation + ILLEGAL_OP_E, ///< illegal operation + OVERFLOW_E, ///< overflow + DIVIDE_E ///< divide by zero +}; + +/** + * A structure used for disambiguation in MatchObject() + */ +struct pobject_structure { + int obj; ///< the actual object number + char type; ///< referred to by noun or adjective + + pobject_structure() : obj(0), type(0) {} +}; + +/** + * Structure used for navigating {...} blocks: + */ +struct CODE_BLOCK { + int type; ///< see #defines, below + long brk; ///< break address, or 0 to indicate NOP + long returnaddr; ///< used only for do-while loops + + CODE_BLOCK() : type(0), brk(0), returnaddr(0) {} +}; + } // End of namespace Hugo } // End of namespace Glk diff --git a/engines/glk/hugo/stringfn.cpp b/engines/glk/hugo/stringfn.cpp new file mode 100644 index 0000000000..79886aca87 --- /dev/null +++ b/engines/glk/hugo/stringfn.cpp @@ -0,0 +1,110 @@ +/* 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/stringfn.h" + +namespace Glk { +namespace Hugo { + + +/* LEFT */ + +char *StringFunctions::Left(char a[], int l) { + static char *temp; + int i; + + temp = GetTempString(); + + if (l > (int)strlen(a)) + l = strlen(a); + for (i = 0; i<l; i++) + temp[i] = a[i]; + temp[i] = '\0'; + return temp; +} + +char *StringFunctions::Ltrim(char a[]) { + static char *temp; + + temp = GetTempString(); + strcpy(temp, a); + while (temp[0]==' ' || temp[0]=='\t') + strcpy(temp, temp+1); + return temp; +} + +char *StringFunctions::Mid(char a[], int pos, int n) { + static char *temp; + int i; + + temp = GetTempString(); + pos--; + if (pos+n > (int)strlen(a)) + n = strlen(a)-pos; + for (i = 0; i<n; i++) + temp[i] = a[pos+i]; + temp[i] = '\0'; + return temp; +} + +char *StringFunctions::Right(char a[], int l) { + static char *temp; + int i; + + temp = GetTempString(); + if (l > (int)strlen(a)) + l = strlen(a); + for (i = 0; i<l; i++) + temp[i] = a[strlen(a)-l+i]; + temp[i] = '\0'; + return temp; +} + +char *StringFunctions::Rtrim(char a[]) { + static char *temp; + int len; + + temp = GetTempString(); + strcpy(temp, a); + while (((len = strlen(temp))) && (temp[len-1]==' ' || temp[len-1]=='\t')) + strcpy(temp, Left(temp, len-1)); + return temp; +} + +char *StringFunctions::hugo_strcpy(char *s, const char *t) { + char *r = s; + while ((*s++ = *t++) != 0) ; + return r; +} + +char *StringFunctions::GetTempString() { + static char *r; + + r = &_tempString[_tempstringCount][0]; + if (++_tempstringCount >= NUM_TEMPSTRINGS) + _tempstringCount = 0; + + return r; +} + +} // End of namespace Hugo +} // End of namespace Glk diff --git a/engines/glk/hugo/stringfn.h b/engines/glk/hugo/stringfn.h new file mode 100644 index 0000000000..bb339fc047 --- /dev/null +++ b/engines/glk/hugo/stringfn.h @@ -0,0 +1,65 @@ +/* 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. + * + */ + +#ifndef GLK_HUGO_STRINGFN +#define GLK_HUGO_STRINGFN + +#include "common/algorithm.h" + +namespace Glk { +namespace Hugo { + +#define NUM_TEMPSTRINGS 2 + +/** + * The following string-manipulation functions closely mimic BASIC-language string functionality. + * They do not alter the provided string; instead, they return a pointer to a static (modified) copy. + */ +class StringFunctions { +private: + char _tempString[NUM_TEMPSTRINGS][1025]; + int _tempstringCount; + + char *GetTempString(); +public: + StringFunctions() : _tempstringCount(0) { + Common::fill(&_tempString[0][0], &_tempString[NUM_TEMPSTRINGS][1025], '\0'); + } + + char *Left(char a[], int l); + + char *Ltrim(char a[]); + + char *Mid(char a[], int pos, int n); + + char *Right(char a[], int l); + + char *Rtrim(char a[]); + + char *hugo_strcpy(char *s, const char *t); +}; + + +} // End of namespace Hugo +} // End of namespace Glk + +#endif diff --git a/engines/glk/module.mk b/engines/glk/module.mk index dd12d23f12..74ab60f9a8 100644 --- a/engines/glk/module.mk +++ b/engines/glk/module.mk @@ -73,7 +73,10 @@ MODULE_OBJS := \ glulxe/vm.o \ hugo/detection.o \ hugo/heglk.o \ + hugo/hemisc.o \ + hugo/htokens.o \ hugo/hugo.o \ + hugo/stringfn.o \ magnetic/detection.o \ magnetic/emu.o \ magnetic/graphics.o \ |