aboutsummaryrefslogtreecommitdiff
path: root/engines
diff options
context:
space:
mode:
authorPaul Gilbert2019-04-15 21:36:46 -0700
committerPaul Gilbert2019-04-17 20:46:06 -0700
commit5965b02bcac9dbad6eb2c9b30a59edba0a3b1b5d (patch)
tree2a57326f02bbd0c20b7f1790170d3ba527c55adc /engines
parent1c4649018388e7df15bc0787ed66b6dc7755db1d (diff)
downloadscummvm-rg350-5965b02bcac9dbad6eb2c9b30a59edba0a3b1b5d.tar.gz
scummvm-rg350-5965b02bcac9dbad6eb2c9b30a59edba0a3b1b5d.tar.bz2
scummvm-rg350-5965b02bcac9dbad6eb2c9b30a59edba0a3b1b5d.zip
GLK: GLULXE: Add string methods
Diffstat (limited to 'engines')
-rw-r--r--engines/glk/glk_types.h2
-rw-r--r--engines/glk/glulxe/exec.cpp4
-rw-r--r--engines/glk/glulxe/glulxe.cpp4
-rw-r--r--engines/glk/glulxe/glulxe.h103
-rw-r--r--engines/glk/glulxe/glulxe_types.h25
-rw-r--r--engines/glk/glulxe/string.cpp828
-rw-r--r--engines/glk/module.mk1
7 files changed, 942 insertions, 25 deletions
diff --git a/engines/glk/glk_types.h b/engines/glk/glk_types.h
index 7095334344..84d3e151a1 100644
--- a/engines/glk/glk_types.h
+++ b/engines/glk/glk_types.h
@@ -230,7 +230,7 @@ union gluniversal_union {
byte _uch; ///< Cu
int8 _sch; ///< Cs
char _ch; ///< Cn
- const char *_charstr; ///< S
+ char *_charstr; ///< S
uint32 *_unicharstr; ///< U
void *_array; ///< all # arguments
uint32 _ptrflag; ///< [ ... ] or *?
diff --git a/engines/glk/glulxe/exec.cpp b/engines/glk/glulxe/exec.cpp
index 766e8b6042..4fefe05e54 100644
--- a/engines/glk/glulxe/exec.cpp
+++ b/engines/glk/glulxe/exec.cpp
@@ -543,13 +543,13 @@ void Glulxe::execute_loop() {
case op_streamchar:
profile_in(0xE0000001, stackptr, false);
value = inst[0].value & 0xFF;
- (*stream_char_handler)(value);
+ (this->*stream_char_handler)(value);
profile_out(stackptr);
break;
case op_streamunichar:
profile_in(0xE0000002, stackptr, false);
value = inst[0].value;
- (*stream_unichar_handler)(value);
+ (this->*stream_unichar_handler)(value);
profile_out(stackptr);
break;
case op_streamnum:
diff --git a/engines/glk/glulxe/glulxe.cpp b/engines/glk/glulxe/glulxe.cpp
index 9db784c969..576b961872 100644
--- a/engines/glk/glulxe/glulxe.cpp
+++ b/engines/glk/glulxe/glulxe.cpp
@@ -42,7 +42,9 @@ Glulxe::Glulxe(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst,
// heap
heap_start(0), alloc_count(0), heap_head(nullptr), heap_tail(nullptr),
// serial
- max_undo_level(8), undo_chain_size(0), undo_chain_num(0), undo_chain(nullptr), ramcache(nullptr) {
+ max_undo_level(8), undo_chain_size(0), undo_chain_num(0), undo_chain(nullptr), ramcache(nullptr),
+ // string
+ iosys_mode(0), iosys_rock(0), tablecache_valid(false), glkio_unichar_han_ptr(nullptr) {
g_vm = this;
}
diff --git a/engines/glk/glulxe/glulxe.h b/engines/glk/glulxe/glulxe.h
index 8402a1b75e..4044074e37 100644
--- a/engines/glk/glulxe/glulxe.h
+++ b/engines/glk/glulxe/glulxe.h
@@ -30,11 +30,18 @@
namespace Glk {
namespace Glulxe {
+class Glulxe;
+typedef void (Glulxe::*CharHandler)(unsigned char);
+typedef void (Glulxe::*UnicharHandler)(uint);
+
/**
* Glulxe game interpreter
*/
class Glulxe : public GlkAPI {
public:
+ CharHandler stream_char_handler;
+ UnicharHandler stream_unichar_handler, glkio_unichar_han_ptr;
+
bool vm_exited_cleanly;
strid_t gamefile;
uint gamefile_start, gamefile_len;
@@ -60,9 +67,6 @@ public:
uint protectstart, protectend;
uint prevpc;
- void (*Glulxe::stream_char_handler)(unsigned char ch);
- void (*Glulxe::stream_unichar_handler)(uint ch);
-
/**
* \defgroup accel fields
* @{
@@ -143,6 +147,26 @@ public:
/**@}*/
+ /**
+ * \defgroup string fields
+ * @{
+ */
+
+ uint iosys_mode;
+ uint iosys_rock;
+
+ /**
+ * The current string-decoding tables, broken out into a fast and easy-to-use form.
+ */
+ bool tablecache_valid;
+ cacheblock_t tablecache;
+
+ /* This misbehaves if a Glk function has more than one S argument. */
+ #define STATIC_TEMP_BUFSIZE (127)
+ char temp_buf[STATIC_TEMP_BUFSIZE + 1];
+
+ /**@}*/
+
protected:
/**
* \defgroup glkop fields
@@ -312,6 +336,25 @@ protected:
int write_buffer(dest_t *dest, const byte *ptr, uint len);
/**@}*/
+
+ /**
+ * \defgroup string support methods
+ * @{
+ */
+
+ void stream_setup_unichar(void);
+
+ void nopio_char_han(unsigned char ch);
+ void filio_char_han(unsigned char ch);
+ void nopio_unichar_han(uint ch);
+ void filio_unichar_han(uint ch);
+ void glkio_unichar_nouni_han(uint val);
+
+ void dropcache(cacheblock_t *cablist);
+ void buildcache(cacheblock_t *cablist, uint nodeaddr, int depth, int mask);
+ void dumpcache(cacheblock_t *cablist, int count, int indent);
+
+ /**@}*/
public:
/**
* Constructor
@@ -476,24 +519,6 @@ public:
/**@}*/
/**
- * \defgroup Strings access methods
- * @{
- */
-
- void stream_num(int val, int inmiddle, int charnum);
- void stream_string(uint addr, int inmiddle, int bitnum);
- uint stream_get_table(void);
- void stream_set_table(uint addr);
- void stream_get_iosys(uint *mode, uint *rock);
- void stream_set_iosys(uint mode, uint rock);
- char *make_temp_string(uint addr);
- uint *make_temp_ustring(uint addr);
- void free_temp_string(const char *str);
- void free_temp_ustring(const uint *str);
-
- /**@}*/
-
- /**
* \defgroup Heap access methods
* @{
*/
@@ -838,6 +863,42 @@ public:
uint perform_verify();
/**@}*/
+
+
+ /**
+ * \defgroup Strings access methods
+ * @{
+ */
+
+ /**
+ * Write a signed integer to the current output stream.
+ */
+ void stream_num(int val, int inmiddle, int charnum);
+
+ /**
+ * Write a Glulx string object to the current output stream. inmiddle is zero if we are beginning
+ * a new string, or nonzero if restarting one (E0/E1/E2, as appropriate for the string type).
+ */
+ void stream_string(uint addr, int inmiddle, int bitnum);
+
+ /**
+ * Get the current table address.
+ */
+ uint stream_get_table(void);
+
+ /**
+ * Set the current table address, and rebuild decoding cache.
+ */
+ void stream_set_table(uint addr);
+
+ void stream_get_iosys(uint *mode, uint *rock);
+ void stream_set_iosys(uint mode, uint rock);
+ char *make_temp_string(uint addr);
+ uint *make_temp_ustring(uint addr);
+ void free_temp_string(char *str);
+ void free_temp_ustring(uint *str);
+
+ /**@}*/
};
extern Glulxe *g_vm;
diff --git a/engines/glk/glulxe/glulxe_types.h b/engines/glk/glulxe/glulxe_types.h
index 5b14a6aaf8..9200ec27eb 100644
--- a/engines/glk/glulxe/glulxe_types.h
+++ b/engines/glk/glulxe/glulxe_types.h
@@ -379,6 +379,31 @@ struct dest_struct {
};
typedef dest_struct dest_t;
+/**
+ * These constants are defined in the Glulx spec.
+ */
+enum iosys {
+ iosys_None = 0,
+ iosys_Filter = 1,
+ iosys_Glk = 2
+};
+
+#define CACHEBITS (4)
+#define CACHESIZE (1 << CACHEBITS)
+#define CACHEMASK (15)
+
+struct cacheblock_struct {
+ int depth; /* 1 to 4 */
+ int type;
+ union {
+ struct cacheblock_struct *branches;
+ unsigned char ch;
+ uint uch;
+ uint addr;
+ } u;
+};
+typedef cacheblock_struct cacheblock_t;
+
} // End of namespace Glulxe
} // End of namespace Glk
diff --git a/engines/glk/glulxe/string.cpp b/engines/glk/glulxe/string.cpp
new file mode 100644
index 0000000000..2c95c3e851
--- /dev/null
+++ b/engines/glk/glulxe/string.cpp
@@ -0,0 +1,828 @@
+/* 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 "engines/glk/glulxe/glulxe.h"
+
+namespace Glk {
+namespace Glulxe {
+
+void Glulxe::stream_get_iosys(uint *mode, uint *rock) {
+ *mode = iosys_mode;
+ *rock = iosys_rock;
+}
+
+void Glulxe::stream_setup_unichar() {
+#ifdef GLK_MODULE_UNICODE
+
+ if (glk_gestalt(gestalt_Unicode, 0))
+ glkio_unichar_han_ptr = &Glulxe::glk_put_char_uni;
+ else
+ glkio_unichar_han_ptr = &Glulxe::glkio_unichar_nouni_han;
+
+#else /* GLK_MODULE_UNICODE */
+
+ glkio_unichar_han_ptr = glkio_unichar_nouni_han;
+
+#endif /* GLK_MODULE_UNICODE */
+}
+
+void Glulxe::stream_set_iosys(uint mode, uint rock) {
+ switch (mode) {
+ default:
+ mode = 0;
+ /* ...and fall through to next case (no-op I/O). */
+ case iosys_None:
+ rock = 0;
+ stream_char_handler = &Glulxe::nopio_char_han;
+ stream_unichar_handler = &Glulxe::nopio_unichar_han;
+ break;
+ case iosys_Filter:
+ stream_char_handler = &Glulxe::filio_char_han;
+ stream_unichar_handler = &Glulxe::filio_unichar_han;
+ break;
+ case iosys_Glk:
+ if (!glkio_unichar_han_ptr)
+ stream_setup_unichar();
+ rock = 0;
+ stream_char_handler = &Glulxe::glk_put_char;
+ stream_unichar_handler = glkio_unichar_han_ptr;
+ break;
+ }
+
+ iosys_mode = mode;
+ iosys_rock = rock;
+}
+
+void Glulxe::nopio_char_han(unsigned char ch) {
+}
+
+void Glulxe::nopio_unichar_han(uint ch) {
+}
+
+void Glulxe::filio_char_han(unsigned char ch) {
+ uint val = ch;
+ push_callstub(0, 0);
+ enter_function(iosys_rock, 1, &val);
+}
+
+void Glulxe::filio_unichar_han(uint val) {
+ push_callstub(0, 0);
+ enter_function(iosys_rock, 1, &val);
+}
+
+void Glulxe::glkio_unichar_nouni_han(uint val) {
+ /* Only used if the Glk library has no Unicode functions */
+ if (val > 0xFF)
+ val = '?';
+ glk_put_char(val);
+}
+
+void Glulxe::stream_num(int val, int inmiddle, int charnum) {
+ int ix = 0;
+ int res, jx;
+ char buf[16];
+ uint ival;
+
+ if (val == 0) {
+ buf[ix] = '0';
+ ix++;
+ }
+ else {
+ if (val < 0)
+ ival = -val;
+ else
+ ival = val;
+
+ while (ival != 0) {
+ buf[ix] = (ival % 10) + '0';
+ ix++;
+ ival /= 10;
+ }
+
+ if (val < 0) {
+ buf[ix] = '-';
+ ix++;
+ }
+ }
+
+ switch (iosys_mode) {
+
+ case iosys_Glk:
+ ix -= charnum;
+ while (ix > 0) {
+ ix--;
+ glk_put_char(buf[ix]);
+ }
+ break;
+
+ case iosys_Filter:
+ if (!inmiddle) {
+ push_callstub(0x11, 0);
+ inmiddle = true;
+ }
+ if (charnum < ix) {
+ ival = buf[(ix-1)-charnum] & 0xFF;
+ pc = val;
+ push_callstub(0x12, charnum+1);
+ enter_function(iosys_rock, 1, &ival);
+ return;
+ }
+ break;
+
+ default:
+ break;
+
+ }
+
+ if (inmiddle) {
+ res = pop_callstub_string(&jx);
+ if (res)
+ fatal_error("String-on-string call stub while printing number.");
+ }
+}
+
+void Glulxe::stream_string(uint addr, int inmiddle, int bitnum) {
+ int ch;
+ int type;
+ int alldone = false;
+ int substring = (inmiddle != 0);
+ uint ival;
+
+ if (!addr)
+ fatal_error("Called stream_string with null address.");
+
+ while (!alldone) {
+
+ if (inmiddle == 0) {
+ type = Mem1(addr);
+ if (type == 0xE2)
+ addr+=4;
+ else
+ addr++;
+ bitnum = 0;
+ }
+ else {
+ type = inmiddle;
+ }
+
+ if (type == 0xE1) {
+ if (tablecache_valid) {
+ int bits, numbits;
+ int readahead;
+ uint tmpaddr;
+ cacheblock_t *cablist;
+ int done = 0;
+
+ /* bitnum is already set right */
+ bits = Mem1(addr);
+ if (bitnum)
+ bits >>= bitnum;
+ numbits = (8 - bitnum);
+ readahead = false;
+
+ if (tablecache.type != 0) {
+ /* This is a bit of a cheat. If the top-level block is not
+ a branch, then it must be a string-terminator -- otherwise
+ the string would be an infinite repetition of that block.
+ We check for this case and bail immediately. */
+ done = 1;
+ }
+
+ cablist = tablecache.u.branches;
+ while (!done) {
+ cacheblock_t *cab;
+
+ if (numbits < CACHEBITS) {
+ /* readahead is certainly false */
+ int newbyte = Mem1(addr+1);
+ bits |= (newbyte << numbits);
+ numbits += 8;
+ readahead = true;
+ }
+
+ cab = &(cablist[bits & CACHEMASK]);
+ numbits -= cab->depth;
+ bits >>= cab->depth;
+ bitnum += cab->depth;
+ if (bitnum >= 8) {
+ addr += 1;
+ bitnum -= 8;
+ if (readahead) {
+ readahead = false;
+ }
+ else {
+ int newbyte = Mem1(addr);
+ bits |= (newbyte << numbits);
+ numbits += 8;
+ }
+ }
+
+ switch (cab->type) {
+ case 0x00: /* non-leaf node */
+ cablist = cab->u.branches;
+ break;
+ case 0x01: /* string terminator */
+ done = 1;
+ break;
+ case 0x02: /* single character */
+ switch (iosys_mode) {
+ case iosys_Glk:
+ glk_put_char(cab->u.ch);
+ break;
+ case iosys_Filter:
+ ival = cab->u.ch & 0xFF;
+ if (!substring) {
+ push_callstub(0x11, 0);
+ substring = true;
+ }
+ pc = addr;
+ push_callstub(0x10, bitnum);
+ enter_function(iosys_rock, 1, &ival);
+ return;
+ }
+ cablist = tablecache.u.branches;
+ break;
+ case 0x04: /* single Unicode character */
+ switch (iosys_mode) {
+ case iosys_Glk:
+ (this->*glkio_unichar_han_ptr)(cab->u.uch);
+ break;
+ case iosys_Filter:
+ ival = cab->u.uch;
+ if (!substring) {
+ push_callstub(0x11, 0);
+ substring = true;
+ }
+ pc = addr;
+ push_callstub(0x10, bitnum);
+ enter_function(iosys_rock, 1, &ival);
+ return;
+ }
+ cablist = tablecache.u.branches;
+ break;
+ case 0x03: /* C string */
+ switch (iosys_mode) {
+ case iosys_Glk:
+ for (tmpaddr=cab->u.addr; (ch=Mem1(tmpaddr)) != '\0'; tmpaddr++)
+ glk_put_char(ch);
+ cablist = tablecache.u.branches;
+ break;
+ case iosys_Filter:
+ if (!substring) {
+ push_callstub(0x11, 0);
+ substring = true;
+ }
+ pc = addr;
+ push_callstub(0x10, bitnum);
+ inmiddle = 0xE0;
+ addr = cab->u.addr;
+ done = 2;
+ break;
+ default:
+ cablist = tablecache.u.branches;
+ break;
+ }
+ break;
+ case 0x05: /* C Unicode string */
+ switch (iosys_mode) {
+ case iosys_Glk:
+ for (tmpaddr=cab->u.addr; (ival=Mem4(tmpaddr)) != 0; tmpaddr+=4)
+ (this->*glkio_unichar_han_ptr)(ival);
+ cablist = tablecache.u.branches;
+ break;
+ case iosys_Filter:
+ if (!substring) {
+ push_callstub(0x11, 0);
+ substring = true;
+ }
+ pc = addr;
+ push_callstub(0x10, bitnum);
+ inmiddle = 0xE2;
+ addr = cab->u.addr;
+ done = 2;
+ break;
+ default:
+ cablist = tablecache.u.branches;
+ break;
+ }
+ break;
+ case 0x08:
+ case 0x09:
+ case 0x0A:
+ case 0x0B:
+ {
+ uint oaddr;
+ int otype;
+ oaddr = cab->u.addr;
+ if (cab->type >= 0x09)
+ oaddr = Mem4(oaddr);
+ if (cab->type == 0x0B)
+ oaddr = Mem4(oaddr);
+ otype = Mem1(oaddr);
+ if (!substring) {
+ push_callstub(0x11, 0);
+ substring = true;
+ }
+ if (otype >= 0xE0 && otype <= 0xFF) {
+ pc = addr;
+ push_callstub(0x10, bitnum);
+ inmiddle = 0;
+ addr = oaddr;
+ done = 2;
+ }
+ else if (otype >= 0xC0 && otype <= 0xDF) {
+ uint argc;
+ uint *argv;
+ if (cab->type == 0x0A || cab->type == 0x0B) {
+ argc = Mem4(cab->u.addr+4);
+ argv = pop_arguments(argc, cab->u.addr+8);
+ }
+ else {
+ argc = 0;
+ argv = NULL;
+ }
+ pc = addr;
+ push_callstub(0x10, bitnum);
+ enter_function(oaddr, argc, argv);
+ return;
+ }
+ else {
+ fatal_error("Unknown object while decoding string indirect reference.");
+ }
+ }
+ break;
+ default:
+ fatal_error("Unknown entity in string decoding (cached).");
+ break;
+ }
+ }
+ if (done > 1) {
+ continue; /* restart the top-level loop */
+ }
+ }
+ else { /* tablecache not valid */
+ uint node;
+ int byte1;
+ int nodetype;
+ int done = 0;
+
+ if (!stringtable)
+ fatal_error("Attempted to print a compressed string with no table set.");
+ /* bitnum is already set right */
+ byte1 = Mem1(addr);
+ if (bitnum)
+ byte1 >>= bitnum;
+ node = Mem4(stringtable+8);
+ while (!done) {
+ nodetype = Mem1(node);
+ node++;
+ switch (nodetype) {
+ case 0x00: /* non-leaf node */
+ if (byte1 & 1)
+ node = Mem4(node+4);
+ else
+ node = Mem4(node+0);
+ if (bitnum == 7) {
+ bitnum = 0;
+ addr++;
+ byte1 = Mem1(addr);
+ }
+ else {
+ bitnum++;
+ byte1 >>= 1;
+ }
+ break;
+ case 0x01: /* string terminator */
+ done = 1;
+ break;
+ case 0x02: /* single character */
+ ch = Mem1(node);
+ switch (iosys_mode) {
+ case iosys_Glk:
+ glk_put_char(ch);
+ break;
+ case iosys_Filter:
+ ival = ch & 0xFF;
+ if (!substring) {
+ push_callstub(0x11, 0);
+ substring = true;
+ }
+ pc = addr;
+ push_callstub(0x10, bitnum);
+ enter_function(iosys_rock, 1, &ival);
+ return;
+ }
+ node = Mem4(stringtable+8);
+ break;
+ case 0x04: /* single Unicode character */
+ ival = Mem4(node);
+ switch (iosys_mode) {
+ case iosys_Glk:
+ (this->*glkio_unichar_han_ptr)(ival);
+ break;
+ case iosys_Filter:
+ if (!substring) {
+ push_callstub(0x11, 0);
+ substring = true;
+ }
+ pc = addr;
+ push_callstub(0x10, bitnum);
+ enter_function(iosys_rock, 1, &ival);
+ return;
+ }
+ node = Mem4(stringtable+8);
+ break;
+ case 0x03: /* C string */
+ switch (iosys_mode) {
+ case iosys_Glk:
+ for (; (ch=Mem1(node)) != '\0'; node++)
+ glk_put_char(ch);
+ node = Mem4(stringtable+8);
+ break;
+ case iosys_Filter:
+ if (!substring) {
+ push_callstub(0x11, 0);
+ substring = true;
+ }
+ pc = addr;
+ push_callstub(0x10, bitnum);
+ inmiddle = 0xE0;
+ addr = node;
+ done = 2;
+ break;
+ default:
+ node = Mem4(stringtable+8);
+ break;
+ }
+ break;
+ case 0x05: /* C Unicode string */
+ switch (iosys_mode) {
+ case iosys_Glk:
+ for (; (ival=Mem4(node)) != 0; node+=4)
+ (this->*glkio_unichar_han_ptr)(ival);
+ node = Mem4(stringtable+8);
+ break;
+ case iosys_Filter:
+ if (!substring) {
+ push_callstub(0x11, 0);
+ substring = true;
+ }
+ pc = addr;
+ push_callstub(0x10, bitnum);
+ inmiddle = 0xE2;
+ addr = node;
+ done = 2;
+ break;
+ default:
+ node = Mem4(stringtable+8);
+ break;
+ }
+ break;
+ case 0x08:
+ case 0x09:
+ case 0x0A:
+ case 0x0B:
+ {
+ uint oaddr;
+ int otype;
+ oaddr = Mem4(node);
+ if (nodetype == 0x09 || nodetype == 0x0B)
+ oaddr = Mem4(oaddr);
+ otype = Mem1(oaddr);
+ if (!substring) {
+ push_callstub(0x11, 0);
+ substring = true;
+ }
+ if (otype >= 0xE0 && otype <= 0xFF) {
+ pc = addr;
+ push_callstub(0x10, bitnum);
+ inmiddle = 0;
+ addr = oaddr;
+ done = 2;
+ }
+ else if (otype >= 0xC0 && otype <= 0xDF) {
+ uint argc;
+ uint *argv;
+ if (nodetype == 0x0A || nodetype == 0x0B) {
+ argc = Mem4(node+4);
+ argv = pop_arguments(argc, node+8);
+ }
+ else {
+ argc = 0;
+ argv = NULL;
+ }
+ pc = addr;
+ push_callstub(0x10, bitnum);
+ enter_function(oaddr, argc, argv);
+ return;
+ }
+ else {
+ fatal_error("Unknown object while decoding string indirect reference.");
+ }
+ }
+ break;
+ default:
+ fatal_error("Unknown entity in string decoding.");
+ break;
+ }
+ }
+ if (done > 1) {
+ continue; /* restart the top-level loop */
+ }
+ }
+ }
+ else if (type == 0xE0) {
+ switch (iosys_mode) {
+ case iosys_Glk:
+ while (1) {
+ ch = Mem1(addr);
+ addr++;
+ if (ch == '\0')
+ break;
+ glk_put_char(ch);
+ }
+ break;
+ case iosys_Filter:
+ if (!substring) {
+ push_callstub(0x11, 0);
+ substring = true;
+ }
+ ch = Mem1(addr);
+ addr++;
+ if (ch != '\0') {
+ ival = ch & 0xFF;
+ pc = addr;
+ push_callstub(0x13, 0);
+ enter_function(iosys_rock, 1, &ival);
+ return;
+ }
+ break;
+ }
+ }
+ else if (type == 0xE2) {
+ switch (iosys_mode) {
+ case iosys_Glk:
+ while (1) {
+ ival = Mem4(addr);
+ addr+=4;
+ if (ival == 0)
+ break;
+ (this->*glkio_unichar_han_ptr)(ival);
+ }
+ break;
+ case iosys_Filter:
+ if (!substring) {
+ push_callstub(0x11, 0);
+ substring = true;
+ }
+ ival = Mem4(addr);
+ addr+=4;
+ if (ival != 0) {
+ pc = addr;
+ push_callstub(0x14, 0);
+ enter_function(iosys_rock, 1, &ival);
+ return;
+ }
+ break;
+ }
+ }
+ else if (type >= 0xE0 && type <= 0xFF) {
+ fatal_error("Attempt to print unknown type of string.");
+ }
+ else {
+ fatal_error("Attempt to print non-string.");
+ }
+
+ if (!substring) {
+ /* Just get straight out. */
+ alldone = true;
+ }
+ else {
+ /* Pop a stub and see what's to be done. */
+ addr = pop_callstub_string(&bitnum);
+ if (addr == 0) {
+ alldone = true;
+ }
+ else {
+ inmiddle = 0xE1;
+ }
+ }
+ }
+}
+
+uint Glulxe::stream_get_table() {
+ return stringtable;
+}
+
+void Glulxe::stream_set_table(uint addr) {
+ if (stringtable == addr)
+ return;
+
+ /* Drop cache. */
+ if (tablecache_valid) {
+ if (tablecache.type == 0)
+ dropcache(tablecache.u.branches);
+ tablecache.u.branches = NULL;
+ tablecache_valid = false;
+ }
+
+ stringtable = addr;
+
+ if (stringtable) {
+ /* Build cache. We can only do this if the table is entirely in ROM. */
+ uint tablelen = Mem4(stringtable);
+ uint rootaddr = Mem4(stringtable+8);
+ int cache_stringtable = (stringtable+tablelen <= ramstart);
+ /* cache_stringtable = true; ...for testing only */
+ /* cache_stringtable = false; ...for testing only */
+ if (cache_stringtable) {
+ buildcache(&tablecache, rootaddr, CACHEBITS, 0);
+ /* dumpcache(&tablecache, 1, 0); */
+ tablecache_valid = true;
+ }
+ }
+}
+
+void Glulxe::buildcache(cacheblock_t *cablist, uint nodeaddr, int depth, int mask) {
+ int ix, type;
+
+ type = Mem1(nodeaddr);
+
+ if (type == 0 && depth == CACHEBITS) {
+ cacheblock_t *list, *cab;
+ list = (cacheblock_t *)glulx_malloc(sizeof(cacheblock_t) * CACHESIZE);
+ buildcache(list, nodeaddr, 0, 0);
+ cab = &(cablist[mask]);
+ cab->type = 0;
+ cab->depth = CACHEBITS;
+ cab->u.branches = list;
+ return;
+ }
+
+ if (type == 0) {
+ uint leftaddr = Mem4(nodeaddr+1);
+ uint rightaddr = Mem4(nodeaddr+5);
+ buildcache(cablist, leftaddr, depth+1, mask);
+ buildcache(cablist, rightaddr, depth+1, (mask | (1 << depth)));
+ return;
+ }
+
+ /* Leaf node. */
+ nodeaddr++;
+ for (ix = mask; ix < CACHESIZE; ix += (1 << depth)) {
+ cacheblock_t *cab = &(cablist[ix]);
+ cab->type = type;
+ cab->depth = depth;
+ switch (type) {
+ case 0x02:
+ cab->u.ch = Mem1(nodeaddr);
+ break;
+ case 0x04:
+ cab->u.uch = Mem4(nodeaddr);
+ break;
+ case 0x03:
+ case 0x05:
+ case 0x0A:
+ case 0x0B:
+ cab->u.addr = nodeaddr;
+ break;
+ case 0x08:
+ case 0x09:
+ cab->u.addr = Mem4(nodeaddr);
+ break;
+ }
+ }
+}
+
+#if 0
+#include <stdio.h>
+void Glulxe::dumpcache(cacheblock_t *cablist, int count, int indent) {
+ int ix, jx;
+
+ for (ix=0; ix<count; ix++) {
+ cacheblock_t *cab = &(cablist[ix]);
+ for (jx=0; jx<indent; jx++)
+ printf(" ");
+ printf("%X: ", ix);
+ switch (cab->type) {
+ case 0:
+ printf("...\n");
+ dumpcache(cab->u.branches, CACHESIZE, indent+1);
+ break;
+ case 1:
+ printf("<EOS>\n");
+ break;
+ case 2:
+ printf("0x%02X", cab->u.ch);
+ if (cab->u.ch < 32)
+ printf(" ''\n");
+ else
+ printf(" '%c'\n", cab->u.ch);
+ break;
+ default:
+ printf("type %02X, address %06lX\n", cab->type, cab->u.addr);
+ break;
+ }
+ }
+}
+#endif /* 0 */
+
+void Glulxe::dropcache(cacheblock_t *cablist) {
+ int ix;
+ for (ix=0; ix<CACHESIZE; ix++) {
+ cacheblock_t *cab = &(cablist[ix]);
+ if (cab->type == 0) {
+ dropcache(cab->u.branches);
+ cab->u.branches = NULL;
+ }
+ }
+ glulx_free(cablist);
+}
+
+char *Glulxe::make_temp_string(uint addr) {
+ int ix, len;
+ uint addr2;
+ char *res;
+
+ if (Mem1(addr) != 0xE0)
+ fatal_error("String argument to a Glk call must be unencoded.");
+ addr++;
+
+ for (addr2=addr; Mem1(addr2); addr2++) { };
+ len = (addr2 - addr);
+ if (len < STATIC_TEMP_BUFSIZE) {
+ res = temp_buf;
+ }
+ else {
+ res = (char *)glulx_malloc(len+1);
+ if (!res)
+ fatal_error("Unable to allocate space for string argument to Glk call.");
+ }
+
+ for (ix=0, addr2=addr; ix<len; ix++, addr2++) {
+ res[ix] = Mem1(addr2);
+ }
+ res[len] = '\0';
+
+ return res;
+}
+
+uint *Glulxe::make_temp_ustring(uint addr) {
+ int ix, len;
+ uint addr2;
+ uint *res;
+
+ if (Mem1(addr) != 0xE2)
+ fatal_error("Ustring argument to a Glk call must be unencoded.");
+ addr+=4;
+
+ for (addr2=addr; Mem4(addr2); addr2+=4) { };
+ len = (addr2 - addr) / 4;
+ if ((len+1)*4 < STATIC_TEMP_BUFSIZE) {
+ res = (uint *)temp_buf;
+ }
+ else {
+ res = (uint *)glulx_malloc((len+1)*4);
+ if (!res)
+ fatal_error("Unable to allocate space for ustring argument to Glk call.");
+ }
+
+ for (ix=0, addr2=addr; ix<len; ix++, addr2+=4) {
+ res[ix] = Mem4(addr2);
+ }
+ res[len] = 0;
+
+ return res;
+}
+
+void Glulxe::free_temp_string(char *str) {
+ if (str && str != temp_buf)
+ glulx_free(str);
+}
+
+void Glulxe::free_temp_ustring(uint *str) {
+ if (str && str != (uint *)temp_buf)
+ glulx_free(str);
+}
+
+} // End of namespace Glulxe
+} // End of namespace Glk
diff --git a/engines/glk/module.mk b/engines/glk/module.mk
index 6cd298e3fa..247f1d837e 100644
--- a/engines/glk/module.mk
+++ b/engines/glk/module.mk
@@ -69,6 +69,7 @@ MODULE_OBJS := \
glulxe/operand.o \
glulxe/search.o \
glulxe/serial.o \
+ glulxe/string.o \
magnetic/detection.o \
magnetic/magnetic.o \
scott/detection.o \